You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(react): a spec-bridge form field skips the normalizer's object-schema merge — it arrives with the raw spec (text), never the resolved widget id (field:text) #6366
Found while implementing #5898 (the form-view bridge's dropped spec keys). Filed unassigned as
an observation, for PM triage — #5898's fence was the DROPPED keys, and this is about a key that
arrives, so it is deliberately not folded into it.
The two routes to a runtime FormField disagree on type
normalizeSectionField (packages/plugin-form/src/sectionFields.ts) is the documented chokepoint
between the spec's authored form-field vocabulary and the runtime one. It branches on the identity
key:
// (3) already a runtime FormField (inline customFields): it carries its own `name`.// NOTE: in the spec shape `field` is a *string* (the field name); in a runtime// FormField `field` is the *metadata object*. So a string `field` is the// disambiguator for the spec shape below.if(typeoffd.field!=='string'){returnattachVisibility(fdasFormField,fd.visibleOn);}
The spec-bridge's mapField (packages/react/src/spec-bridge/bridges/form-view.ts) maps the spec's field onto the runtime name and emits no field key at all. So a bridged section field takes
branch (3) — "already a runtime FormField" — and is returned unchanged.
Branch (2), which an authored spec field takes when it reaches the normalizer directly, does five
things branch (3) does not:
resolves the widget id — base.type = mapFieldTypeToFormType(rawType, { multiple })
merges the object-schema base (fromObjectSchema): label i18n, validation (buildValidationRules), disabled, the field metadata stash widgets read precision / reference_to off
stamps both lookup spellings (reference / reference_to)
folds the object-level visibleWhen / readonlyWhen / requiredWhen rules onto the runtime field
The measurable half
mapFieldTypeToFormType('text') returns 'field:text' (packages/fields/src/field-type-alias.ts:110).
The bridge forwards the spec type verbatim, so a bridged field reaches the renderer carrying type: 'text' while the same authored field normalised directly carries type: 'field:text'. The
same asymmetry holds for every entry in that map (number → field:number, datetime → field:datetime, …), and for the (type, multiple) pair rule objectui#3986 added — a bridged select + multiple: true cannot land on the multi-value picker, because nothing re-decides the
widget on that route.
Pinned incidentally by the conformance suite in #5898's PR: its carries spec 'type' to its destination row asserts field.type === 'text' on the bridged node, i.e. the raw spec spelling.
That row is correct about the BRIDGE; this card is about whether the value is correct for the
RENDERER one layer on.
Why it is a finding and not a queued card
Two things need a decision above the mechanical part, and neither is mine to make:
Which layer owns the translation. Either the bridge emits fully runtime-shaped fields (and
must therefore do the mapFieldTypeToFormType fold and the object-schema merge itself — but it
has no object schema in hand, only BridgeContext), or it emits spec-shaped fields and lets the
normalizer's branch (2) do the work (which would mean keeping the spec field key rather than
renaming it to name, and that is the 表单字段簇:spec↔runtime 是双层词汇——枢纽补缺、覆盖闸门、边界响亮化(objectstack#4115) #3090 mixed-vocabulary hazard the normalizer warns about
by name). Both are coherent; they are different architectures.
Whether the bridged route is reached in production at all.SpecBridge is exported for
hosts; I did not find an in-repo caller that feeds its object-form node into ObjectForm with a
live object schema, so the blast radius is unmeasured. If the answer is "no host does this", the
honest outcome may be ADR-0049 enforce-or-remove on the route rather than a fix.
Found while implementing #5898 (the form-view bridge's dropped spec keys). Filed unassigned as
an observation, for PM triage — #5898's fence was the DROPPED keys, and this is about a key that
arrives, so it is deliberately not folded into it.
The two routes to a runtime
FormFielddisagree ontypenormalizeSectionField(packages/plugin-form/src/sectionFields.ts) is the documented chokepointbetween the spec's authored form-field vocabulary and the runtime one. It branches on the identity
key:
The spec-bridge's
mapField(packages/react/src/spec-bridge/bridges/form-view.ts) maps the spec'sfieldonto the runtimenameand emits nofieldkey at all. So a bridged section field takesbranch (3) — "already a runtime FormField" — and is returned unchanged.
Branch (2), which an authored spec field takes when it reaches the normalizer directly, does five
things branch (3) does not:
base.type = mapFieldTypeToFormType(rawType, { multiple })fromObjectSchema): label i18n,validation(buildValidationRules),disabled, thefieldmetadata stash widgets readprecision/reference_tooffrequiredtest against the object field'sdefaultValue(A required field whosedefaultValueis a runtime token (NOW()/current_user) cannot be submitted from a create form #4069)reference/reference_to)visibleWhen/readonlyWhen/requiredWhenrules onto the runtime fieldThe measurable half
mapFieldTypeToFormType('text')returns'field:text'(packages/fields/src/field-type-alias.ts:110).The bridge forwards the spec
typeverbatim, so a bridged field reaches the renderer carryingtype: 'text'while the same authored field normalised directly carriestype: 'field:text'. Thesame asymmetry holds for every entry in that map (
number→field:number,datetime→field:datetime, …), and for the(type, multiple)pair rule objectui#3986 added — a bridgedselect+multiple: truecannot land on the multi-value picker, because nothing re-decides thewidget on that route.
Pinned incidentally by the conformance suite in #5898's PR: its
carries spec 'type' to its destinationrow assertsfield.type === 'text'on the bridged node, i.e. the raw spec spelling.That row is correct about the BRIDGE; this card is about whether the value is correct for the
RENDERER one layer on.
Why it is a finding and not a queued card
Two things need a decision above the mechanical part, and neither is mine to make:
must therefore do the
mapFieldTypeToFormTypefold and the object-schema merge itself — but ithas no object schema in hand, only
BridgeContext), or it emits spec-shaped fields and lets thenormalizer's branch (2) do the work (which would mean keeping the spec
fieldkey rather thanrenaming it to
name, and that is the 表单字段簇:spec↔runtime 是双层词汇——枢纽补缺、覆盖闸门、边界响亮化(objectstack#4115) #3090 mixed-vocabulary hazard the normalizer warns aboutby name). Both are coherent; they are different architectures.
SpecBridgeis exported forhosts; I did not find an in-repo caller that feeds its
object-formnode intoObjectFormwith alive object schema, so the blast radius is unmeasured. If the answer is "no host does this", the
honest outcome may be ADR-0049 enforce-or-remove on the route rather than a fix.
Boundary
mapFieldTypeToFormType/field-type/field:/normalizeSectionField/spec-bridge/bridge/runtime FormField/form-view, plus a broadform|type|widgettitle sweep. Nothingopen covers it. finding:
metadata-admin/previews/object-fields-bridge.tshas zero importers, and itsserializeDesignerFieldwould destroy every key the designer does not model #6309 is the nearest neighbour by shape (a different bridge whose serializerwould destroy keys) and is unrelated in mechanism. [finding]
object-master-detail-formdeclaresformTypeas a barestringwhile the renderer switches on theobject-formvariant enum — an out-of-vocabulary value silently routes around the whole simple-form path #5939 and finding(types): three more type names are declared twice with disagreeing shapes —FormField,MarkdownSchema,KanbanSchema— and one doc assertsFormFieldis declared once #6172 touch the form-field/formTypevocabulary but from the types side, not this translation.
same-name, so it lands wherever branch (3) leaves it — the same place
colSpan,widgetandrequiredalready landed. This card is about the ROUTE, not about any one key.Generated by Claude Code