Skip to content

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

Description

@claude

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:

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 (numberfield: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:

  1. 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.
  2. 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.

Boundary


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions