Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell): strip the retired referenceTo / isSystem field keys on read (#6519) - #6529
Conversation
…ys on read `previews/object-fields-io.ts` is the single read door for `draft.fields` across the object designer, and `writeFields` writes each def back verbatim. Its strip set named one key (`indexed`) while `FieldSchema` refuses five by name, so a stored field carrying a sibling round-tripped straight back out to `PUT /api/v1/meta/object/:name`. Measured on the installed `@objectstack/spec` 17.2.0, through the whole document that endpoint validates, the refusal lands at `["fields", <name>]` as `unrecognized_keys` — the hard 422 `INVALID_METADATA` that blocks every later save of the object, with the control that wrote the key retired and no UI path left to clear it. Same shape objectui#4644 closed in this file for `indexed`. Both added keys were verified reachable rather than assumed: `referenceTo` was emitted by both designer writers until objectui#6041, and `isSystem` was a declared server-field key the designer read back until objectui#6044. The spec spellings `reference` and `system` are separate accepted keys and are not stripped, so nothing the server would store is lost. Two keys `FieldSchema` also refuses stay out of the list, each for its own measured reason, both recorded on the tombstone: `formula`, because `ObjectFieldInspector` migrates it through a linting CEL editor and stripping empties that editor (measured red pin) — the trade objectui#6043 explicitly preserved; and `sortOrder`, because no writer on this tree ever populated a field-level one, so a strip would be dead code that reads like a measurement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-support-ai
commented
Aug 26, 2026
ACCEPT — objectui#6519 ( Headline: the card named four missing keys and asked for them to be stripped. Measured per key, they gave four different answers — and two of them say do less, not more. The PR delivers two, falsifies one outright, and escalates one rather than implementing it. That is the right outcome and the diff alone could not tell you so. Why the three-key list is a measurement, not an oversightA
Confirmed at ⭐⭐⭐ |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6519
What changed
previews/object-fields-io.tsis the single read door fordraft.fieldsacross the whole object designer (inspector, form designer, design surface, settings / validations / API panels), andwriteFieldswrites each def back verbatim. Its strip set named ONE key whileFieldSchemarefuses five by name, so a stored field carrying a sibling round-tripped straight back out toPUT /api/v1/meta/object/:name.RETIRED_FIELD_KEYSnow reads[indexed, referenceTo, isSystem]. Same refusal objectui#4644 applied toindexedin this same file, applied to the siblings that were left open.File face — nothing under
packages/plugin-designer/, so #6489 keeps sole ownership ofMetadataFieldsPage.tsx:packages/app-shell/src/views/metadata-admin/previews/object-fields-io.tspackages/app-shell/src/views/metadata-admin/previews/object-fields-io.retiredKeys.test.tspackages/app-shell/src/views/metadata-admin/previews/object-fields-io.spec-keys.test.ts.changeset/6519-retired-field-key-strip.mdThe premise was verified per key, and it answered differently for each
The card asked whether each surviving key can actually appear in a draft this door reads. It reads documents fetched through
MetadataClient.get(object, name)— the same store the two sibling writers write to — so the question is whether a shipped build ever wrote the key.referenceToMetadataService.toFieldPayload(referenceTo: field.referenceTo) andMetadataFieldsPage.fromDesignerField(referenceTo: designed.referenceTo)isSystemMetadataFieldsPage'sServerFieldSchemaand read back (isSystem: raw.isSystem) until objectui#6044formulasortOrdertoFieldPayloademittedsortOrder: undefinedandJSON.stringifydropped it — "the key never reached the wire"Nothing is lost on the way out:
referenceandsystemare realFieldSchemakeys, are NOT stripped, and ride through untouched.Why
formulais not in the list, measured rather than reasonedObjectFieldInspectoris the platform's sanctioned migration surface for that key — the legacy value seeds its linting CEL editor (readPredicate(def.expression ?? def.formula)) and the first edit commitsexpressionand clears the alias. Stripping at the read door empties that editor. Withformulaadded to the list:objectui#6043 refused a blind rename in
plugin-designerprecisely because that linting editor exists; a strip here would discard what the ruling preserved, on the next save, with no author-visible surface. That leaves the card's harm live for that one key, which is a maintainer trade rather than a dev's — raised with both options and a four-axis recommendation as #6526, not taken here.Both absences are recorded in full on the tombstone, so the next reader does not "finish the set" defensively.
Reverse verification
Predicted RED, observed RED, on the emitted body rather than a helper's return value: the new cases in
object-fields-io.spec-keys.test.tsbuild the object documentPUTvalidates and parse it with the realObjectSchema. For each stripped key, the un-round-tripped draft is refused at pathfields.amountwithunrecognized_keys(the 422's own shape), and the round-tripped body parses green. Controls: the document is otherwise accepted, and the existing negative controls (bogus key refused; the instrument is strict) still stand — if any control passes, the result is void.The
formulacollision above is the second measured direction: predicted red, observed red, and it is what shrank the fix.No package
dist/sits between mutation and assertion: the subject is imported relatively (./object-fields-io), and the only prebuilt artifact in the loop is@objectstack/spec, the unmutated oracle. (The dependency closure was built once —pnpm --filter @object-ui/app-shell^... build— becausetype-checkneeds it in a fresh worktree.)Tests and gates — all on final commit
273a7d4d1, tree cleanpnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/previews/ packages/app-shell/src/views/metadata-admin/inspectors/->Test Files 101 passed (101),Tests 1144 passed | 1 skipped (1145)pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/studio-design/ .../clientValidation .../ResourceEditPage(the read door's other consumers) ->Test Files 51 passed (51),Tests 363 passed (363)pnpm --filter @object-ui/app-shell run type-check-> exit 0. Not the not-measured case:tsc -p tsconfig.test.json --listFilesshows 1 hit each for the three edited files out of 4458.pnpm check:designer-field-key-parity->designer-field-key-parity: OK(this seam's gate, objectui#5761 coverage note 3)pnpm check:control-bytes->OK (scanned 5395 tracked text file(s); skipped 85 binary);check:self-import,check:esm-specifiers,check:vi-mock-specifiers-> exit 0node scripts/check-changeset-presence.mjs->3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)eslint .insidepackages/app-shell-> 988 files, 0 errors, and the fourobject-fields-io*files contribute 0 warnings (count read from--format json, universe read from the root config's ownfiles: **/*.{ts,tsx}+ ignores). The narrowing excludes nothing relevant: the rooteslint.config.jsenables no type-aware linting (noproject/projectService), so this diff cannot move a verdict on an untouched file. CI runs the full farm regardless.Every exit code was captured before any pipe; each verdict above is quoted from the gate's own line.
Scope
List unification is deliberately OUT — it spans
plugin-designer/src/MetadataFieldsPage.tsx, which #6489 owns in flight. Filed as #6527 (Blocked-by #6489) with the measurement showing the three lists are not simply nested, so a naive union would regress. One more finding filed rather than folded in: #6528.Generated by Claude Code