Filed unassigned by the domain:ui execution seat while implementing objectui#6111. Recording only — not graded, no domain:*. Measured on origin/main @ f66072d1b, by content, and re-confirmed against the #6111 branch.
Two renderers, one authored key, two different contracts
objectui#6111 delivers an authored FormSection.visibleWhen to the form renderer on the object-view chain, by copying it onto the virtual section-divider pseudo-field each layout synthesises. That makes the section's heading hide on a false predicate. It does not hide the section's fields.
packages/components/src/renderers/form/form.tsx:2014 treats section-divider as a purely presentational ROW:
if (type === 'section-divider') {
return <SectionDivider label={…} description={…} collapsible={…} … />;
}
grep finds no divider→field association anywhere in that file — no currentSection, no grouping pass, nothing that scopes the fields following a divider to it. The fields are siblings in one flat list. So the heading vanishes and the fields stay.
The other renderer does the opposite. apps/console/src/components/FormPage.tsx:1819:
if (!isSectionVisible(sec, values, loaded.record)) return null;
return (<section key={i} …>{/* heading AND fields */}</section>);
That is the whole <section> — heading and fields both — and it is the settled behaviour there (isSectionVisible's own docstring cites the triage ruling of 2026-08-22, following #5594).
So after #6111 one authored visibleWhen on one section means "hide the heading" on the object-view chain and "hide the section" in the console. That is the same class of asymmetry #6010's parity pin exists to refuse, one layer up.
Why #6111 shipped the heading-only half rather than fixing this
Not an oversight — a scope call, recorded here so the next seat does not re-derive it:
- The repo's own existing definition of this surface is heading-only.
packages/components/src/renderers/form/__tests__/predicate-scope-parity-6010.test.tsx's sectionSurface row hand-authors { name: 'pay', type: 'section-divider', visibleWhen } and asserts exactly and only that the heading text disappears.
- Closing the gap in a layout would mean stamping the section predicate onto each of the section's fields, and there is no free slot to stamp it into. A field's two predicate slots are both already spoken for:
visibleWhen carries the object-level field rule (form.tsx:1218, :1931) and visibleOn carries the authored per-field view predicate (ObjectForm.tsx merges d.visibleOn from the section's field defs). Composing two predicate sources into one CEL string inside a layout is the consumer-side tolerance that contract-first forbids, and it would put a third dialect-aware evaluator in the tree.
- So the fix belongs in the renderer, as a real section grouping that a predicate can gate — which is a public contract change in
@object-ui/components + @object-ui/types, not one line in a layout.
#6111 pins the current behaviour honestly instead of implying a guarantee it does not deliver (packages/plugin-form/src/__tests__/sectionVisibleWhen-6111.test.tsx, the measured scope: a hidden section still renders its FIELDS case). That assertion going red is the signal this card landed.
The open design question
A hidden section's fields are still registered with react-hook-form. Whoever takes this must decide, and the answer is not obvious in either direction:
- Does a hidden section's required field still block submit? If the fields merely stop rendering but keep their validation rules, a required field inside a hidden section blocks the submit with an error pointing at a control the user cannot see — the exact defect shape objectui#6110 describes for the wizard's submit-time re-check. If instead the rules are dropped, a section predicate becomes a way to bypass required-ness, which the server will then reject.
- Do the values still submit? The console renderer's answer is yes —
isSectionVisible "decides what is DRAWN and nothing else", and its header states visibility is not a submit-payload rule at either granularity. The plugin-form chain should almost certainly match, but it should say so rather than inherit it by accident.
- What carries the grouping? A real
FormSection node in the renderer, or a sectionKey back-reference on each field, or the divider gaining a fields: string[] claim like FormFieldTab already has.
Worth noting (3) has a natural convergence with objectui#6111's other unfixable arm — see the tabbed-arm card filed alongside this one: FormFieldTab already models "a group of fields claimed by name" and already needs a predicate slot. One grouping contract could serve both.
Generated by Claude Code
Filed unassigned by the
domain:uiexecution seat while implementing objectui#6111. Recording only — not graded, nodomain:*. Measured onorigin/main@f66072d1b, by content, and re-confirmed against the #6111 branch.Two renderers, one authored key, two different contracts
objectui#6111 delivers an authored
FormSection.visibleWhento the form renderer on the object-view chain, by copying it onto the virtualsection-dividerpseudo-field each layout synthesises. That makes the section's heading hide on a false predicate. It does not hide the section's fields.packages/components/src/renderers/form/form.tsx:2014treatssection-divideras a purely presentational ROW:grepfinds no divider→field association anywhere in that file — nocurrentSection, no grouping pass, nothing that scopes the fields following a divider to it. The fields are siblings in one flat list. So the heading vanishes and the fields stay.The other renderer does the opposite.
apps/console/src/components/FormPage.tsx:1819:That is the whole
<section>— heading and fields both — and it is the settled behaviour there (isSectionVisible's own docstring cites the triage ruling of 2026-08-22, following #5594).So after #6111 one authored
visibleWhenon one section means "hide the heading" on the object-view chain and "hide the section" in the console. That is the same class of asymmetry #6010's parity pin exists to refuse, one layer up.Why #6111 shipped the heading-only half rather than fixing this
Not an oversight — a scope call, recorded here so the next seat does not re-derive it:
packages/components/src/renderers/form/__tests__/predicate-scope-parity-6010.test.tsx'ssectionSurfacerow hand-authors{ name: 'pay', type: 'section-divider', visibleWhen }and asserts exactly and only that the heading text disappears.visibleWhencarries the object-level field rule (form.tsx:1218,:1931) andvisibleOncarries the authored per-field view predicate (ObjectForm.tsxmergesd.visibleOnfrom the section's field defs). Composing two predicate sources into one CEL string inside a layout is the consumer-side tolerance that contract-first forbids, and it would put a third dialect-aware evaluator in the tree.@object-ui/components+@object-ui/types, not one line in a layout.#6111 pins the current behaviour honestly instead of implying a guarantee it does not deliver (
packages/plugin-form/src/__tests__/sectionVisibleWhen-6111.test.tsx, themeasured scope: a hidden section still renders its FIELDScase). That assertion going red is the signal this card landed.The open design question
A hidden section's fields are still registered with react-hook-form. Whoever takes this must decide, and the answer is not obvious in either direction:
isSectionVisible"decides what is DRAWN and nothing else", and its header states visibility is not a submit-payload rule at either granularity. The plugin-form chain should almost certainly match, but it should say so rather than inherit it by accident.FormSectionnode in the renderer, or asectionKeyback-reference on each field, or the divider gaining afields: string[]claim likeFormFieldTabalready has.Worth noting (3) has a natural convergence with objectui#6111's other unfixable arm — see the tabbed-arm card filed alongside this one:
FormFieldTabalready models "a group of fields claimed by name" and already needs a predicate slot. One grouping contract could serve both.Generated by Claude Code