|
| 1 | +--- |
| 2 | +"@objectstack/metadata-protocol": minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(metadata): `saveMeta` persists the operator spellings the spec normalized (objectui#2945) |
| 6 | + |
| 7 | +`ViewFilterRuleSchema.operator` is `z.preprocess(normalizeFilterOperator, …)`, so |
| 8 | +a stored `notEquals` / `gt` / `isNull` is folded to its canonical form during |
| 9 | +save-time validation — and then the result was thrown away. `saveMetaItem` |
| 10 | +persists the authored body verbatim, deliberately: `parsed.data` strips the |
| 11 | +Studio-only auxiliary fields (`isPinned`, `isDefault`, `sortOrder`) that ride |
| 12 | +along with an overlay document (ADR-0005 §Validation). |
| 13 | + |
| 14 | +The consequence is that **every save mints new legacy-alias rows.** The ~30 |
| 15 | +entries in `VIEW_FILTER_OPERATOR_ALIASES` are documented as *"a migration bridge |
| 16 | +[that] may be dropped in a future major"*, but there is no point at which the |
| 17 | +last alias row is behind you, so the bridge can never be dismantled — a |
| 18 | +migration that rewrote every existing row would be obsolete the moment the next |
| 19 | +console personalization PUT landed. That is prerequisite 2 of the vocabulary |
| 20 | +consolidation blocked in objectstack-ai/objectui#2945. |
| 21 | + |
| 22 | +`graftNormalizedOperators` grafts the normalization back on without giving up the |
| 23 | +verbatim body. It walks the authored value and `parsed.data` in lockstep **by |
| 24 | +structure** and copies across exactly one thing: an `operator` whose parsed value |
| 25 | +differs from the authored one. |
| 26 | + |
| 27 | +- **No key list to maintain.** `ViewFilterRule[]` appears at five declared sites |
| 28 | + today (view `filter`, `ViewTab.filter`, page `filterBy`, and two |
| 29 | + `component.zod.ts` block props) and the structural walk covers all of them, |
| 30 | + plus any added later. Enumerating paths would have reproduced in this file the |
| 31 | + exact duplication #2945 exists to remove. |
| 32 | +- **Nothing else moves.** Only an `operator` string is rewritten, and only where |
| 33 | + both sides are strings — so a `$`-token `FilterCondition`, a different operator |
| 34 | + vocabulary entirely, cannot be reshaped by accident. No key is added, removed, |
| 35 | + reordered or defaulted; the unary `{field, operator}` form does not acquire a |
| 36 | + `value` even though the schema's own output would give it one. |
| 37 | +- **Nothing is allocated when nothing changed**, so a body already written in |
| 38 | + canonical form is returned by identity. |
| 39 | + |
| 40 | +Behaviour change worth stating plainly: a `GET` after a `PUT` now returns the |
| 41 | +canonical spelling rather than the one the author sent. That is the spelling the |
| 42 | +spec defines, every renderer accepts it (objectstack-ai/objectui#2974, |
| 43 | +objectstack-ai/objectui#2989 pinned all three of objectui's translation tables to |
| 44 | +the full vocabulary), and it is the point of the change. Existing rows are not |
| 45 | +touched — this stops the bleeding, it is not the migration. |
| 46 | + |
| 47 | +Verified: 11 new tests, including one that drives **every** alias the spec still |
| 48 | +folds through the real `ViewMetadataSchema` and asserts the persisted body comes |
| 49 | +out canonical; full `@objectstack/metadata-protocol` suite 110 tests / 18 files |
| 50 | +green. |
0 commit comments