Skip to content

finding(react): the form-view bridge's #2545 "never silently drop spec configuration" promise is false — 18 spec keys are neither mapped nor explained #5898

Description

@claude

Found while implementing #5652 (the three drifted key TYPES). Filed unassigned as an
observation, for PM triage — #5652's fence was the drifted arms, and this is deliberately not
folded into it.

The promise

packages/react/src/spec-bridge/bridges/form-view.ts carries this docstring, and
FormViewSpecConformance.test.ts exists to enforce it:

Every serializable spec key is either mapped onto the object-form node or listed here with
an explicit reason for being ignored — the bridge must never silently drop spec configuration
(#2545).

Deriving the drifted keys in #5652 made the contract's full key set visible next to the
declaration, and the promise does not hold: 18 spec keys are neither mapped nor explained.
The conformance test does not catch them because its fixture is a hand-written list of keys
someone remembered, not the contract's key set — a key absent from the fixture is a key the
completeness loop never asks about.

Measured against the installed @objectstack/spec

Key sets read through the TypeScript checker off the spec's own .d.ts (keyof FormView,
keyof FormSection, keyof FormFieldInput), then diffed against what the bridge declares.

FormView — 2 dropped: buttons, defaults.
(defaultSort and aria are correctly absent AND explained — they are retired-key tombstones,
pinned by FormViewRetiredKeys.test.ts. They are the shape the other 18 should have.)

FormSection — 2 dropped: visibleOn, pane.

FormFieldInput — 14 dropped: publicPicker, maxLength, minLength, min, max,
precision, scale, multiple, immutable, span, language, keyField, disclosure,
fields.

Most of them are dropped configuration, not harmless omissions

This is what makes it worth filing rather than noting. The receiving layer already reads them:

  • FormSection.pane is read at plugin-form/src/ObjectForm.tsx:302 (pane: s.pane) and is
    the whole subject of SplitForm.tsx's per-section placement. A spec-authored split form loses
    its explicit pane placement at this bridge and silently falls back to the legacy positional
    rule (first section primary, the rest secondary) — so reordering sections moves them across
    the divider, which is the exact failure pane was added to prevent.
  • 13 of the 14 field keys are copied onto the runtime field by normalizeSectionField
    (plugin-form/src/sectionFields.ts): maxLength, minLength, min, max, precision,
    scale, multiple, immutable, span, language, keyField, disclosure, fields. The
    bridge is the only thing between the authored document and that reader, so an authored
    maxLength / precision / composite fields never arrives. publicPicker is the one with
    no reader found here.
  • FormSection.visibleOn — the field path already reads visibleWhen ?? visibleOn; the
    section path reads only visibleWhen, so the deprecated section spelling is dropped on a
    document that has not been through the spec's normaliser (the same never-parsed input class
    the bridge keeps reading groups and field visibleOn for).

buttons and defaults need a triage answer rather than a mechanical copy — there may be no
node slot for either, in which case the honest fix is an entry in the ignore list with the
reason, not a passthrough.

Why this is a finding and not a queued card

Two decisions sit above the mechanical part, and both belong to triage:

  1. Where the completeness check should get its key set. Pinning the fixture against the
    contract's keyof (so a new spec key fails the suite until it is mapped or explained) is a
    different instrument from the current hand-listed fixture, and it will fail immediately on
    all 18 — the ordering of "build the guard" and "close the gap" is a call.
  2. declared = enforced vs. passthrough. Some of these are simply un-copied; buttons /
    defaults / publicPicker may have no consumer at all, in which case ADR-0049's
    enforce-or-remove applies to the ignore-list entry rather than to a copy.

Boundary


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpackage: reactpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions