Skip to content

app-shell: a draft carrying the retired formula key still round-trips to a hard 422, and the two ways out trade against each other #6526

Description

@claude

Found while implementing objectui#6519 (extending object-fields-io's retired-key strip set). Filed, not fixed: the two ways out trade against each other and the choice is a maintainer call, not a mechanical edit.

What is measured

packages/app-shell/src/views/metadata-admin/previews/object-fields-io.ts is the single read door for draft.fields across the object designer, and writeFields writes each def back verbatim. objectui#6519 extended its strip set to referenceTo and isSystem. It deliberately did NOT add formula, and that leaves the card's harm live for one key:

  • FieldSchema refuses formula by name on the installed @objectstack/spec 17.2.0 ("Did you mean `formula` -> `expression`?"), reported through the whole document at path fields.amount, i.e. the hard 422 INVALID_METADATA that blocks EVERY later save of that object.
  • The premise holds: the Field Designer's formula textarea wrote the key until objectui#6043 retired the control, so stored objects can carry it.

Why objectui#6519 did not just strip it

ObjectFieldInspector is this platform's sanctioned migration surface for that exact key:

value={readPredicate(def.expression??def.formula)}onChange={(v)=>patchDef({expression: writePredicate(def.expression??def.formula,v),formula: undefined, ... })}

The legacy value seeds the linting CEL editor and the first edit commits the spec key and clears the alias. Stripping at the read door empties that editor. Measured on the objectui#6519 branch, with formula added to RETIRED_FIELD_KEYS:

FAIL packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.test.tsx
> commits edits to `expression` and migrates the legacy `formula` key
expect(element).toHaveValue(record.amount)
Received: ""
Tests 1 failed | 42 passed (43)

And objectui#6043 refused a blind rename in plugin-designerprecisely because this linting editor exists to migrate the value properly — a strip at the read door would discard what that ruling preserved, on the next save, with no author-visible surface.

The two options

A — strip formula at the read door (finishes objectui#6519's pattern). Every object saves again immediately. Cost: the authored expression text is dropped on the next save, silently; the inspector's migration path and its pin go with it.

B — leave it, and make the blocked state actionable instead. Client validation already parses object drafts against the real ObjectSchema (clientValidation.ts LOADERS), so the author can be told which field carries the key and pointed at the formula editor, where one edit migrates it. Cost: until they do that, the object stays unsaveable, and today the diagnostic does not say what to do about it.

Recommendation: B, on all four axes

  • Real business need — the population is objects whose formula fields were authored before objectui#6043. For them the authored expression IS the business content; A destroys it to unblock a save, B keeps it and asks for one edit.
  • Long-term soundness — B keeps one adjudicated story (objectui#6043: the legacy text is migrated through a linting editor, never laundered or dropped blindly). A creates a second, contradictory rule at a different layer for the same key.
  • Making AI-written metadata hard to get wrong — a strip that silently deletes authored source is the failure mode this platform keeps closing elsewhere; a loud, specific diagnostic is the shape that teaches.
  • Startup scope discipline — B is a diagnostic message on a gate that already runs, not new surface. A is cheaper to type and more expensive to own.

If the maintainer prefers A, the tombstone on RETIRED_FIELD_KEYS and the ObjectFieldInspector pin must be updated in the same PR — they currently record the opposite.

Refs: objectui#6519 (the strip set this came out of) - objectui#6043 (the refused rename) - objectui#4644 (the indexed precedent).


Generated by Claude Code


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions