Section grouping contract: a claiming section-divider gates its whole group - #6608
Merged
Merged
Conversation
…r gates its whole group A `section-divider` row may now claim its member fields (`FormField.fields: string[]`, the FormFieldTab/FormFieldPane membership shape) and the form renderer gates the WHOLE group on the divider's visibility verdict: heading and claimed fields hide together, the hidden members skip client-side validation (ruled: a user must never be blocked by an error pointing at a control they cannot see), and their values still submit (visibility decides what is DRAWN and nothing else — the console precedent). A divider without a claim keeps the old heading-only contract. Both ruled semantics ride the mechanism the field-level predicate already uses (return null; react-hook-form keeps the value and skips the unmounted control at submit validation), so field- and section-level visibility cannot drift. Zod mirror + coverage pin updated; new pin file section-grouping-6236.test.tsx; the 6010 parity sectionSurface row now asserts heading and claimed member move together. Maintainer ruling 2026-08-27 (option A on the card's facet block). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…t-6236 state The renderer-side grouping contract now exists (FormField.fields claim + whole-group gate, pinned in section-grouping-6236.test.tsx); what this chain still lacks is the synthesis-site stamp of the membership claim, so the pinned behaviour itself is unchanged and the assertion stays green. The comment now says which half is missing and keeps the red-flip as the landing signal for that remaining wiring. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…der synthesis sites Patch round on the same claim (fence widened by the PM after the fork was ruled option A). Every section-divider synthesis site — ObjectForm's stacked simple path, ModalForm's sectioned groups map and derived-fieldGroup path, DrawerForm's sectioned and derived-fieldGroup paths, SplitForm's paneFields — now copies the RESOLVED member names onto the divider it emits (fields: names), which is the exact string list the renderer's hiddenSectionFieldNames gate looks up. An authored FormSection.visibleWhen therefore hides the whole section on the object-view chain, matching the console renderer. The #6111 measured-scope pin FLIPPED as designed: it now asserts heading and claimed field hide together, and every per-layout DENIED/ALLOWED/FAULTED row also asserts the member field. The derived-fieldGroup sites carry the claim for uniformity but stay fail-open — the spec fieldGroups vocabulary has no section-predicate slot to author. Changeset gains @object-ui/plugin-form minor; the types-side scope notes updated to the landed truth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Aug 27, 2026
os-sales
marked this pull request as ready for review
August 27, 2026 13:50
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6236 — the ruled section grouping contract (maintainer, 2026-08-27, verbatim 「同意」 on triage's option A), landed in full across two rounds on this branch: round 1 shipped the public contract + renderer enforcement; round 2 (patch round, fence widened by the PM after the fork was ruled option A) shipped the producer wiring, and the #6111 landing-signal pin flipped. Out of scope here: #6237 (the tabbed arm) remains open and stays Blocked-by #6236; it is designed to reuse this same grouping contract. Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
What lands
@object-ui/types— section/tab shapes only.BaseSchemaand its zod mirror are untouched (open PR #6574's surface, honored):FormField.fields(optional string array) — the section grouping claim, declared fortype: 'section-divider'rows: names of the fields the section claims, the same membership shapeFormFieldTab.fields/FormFieldPane.fieldsalready model, so tabs, panes and sections share ONE grouping contract.FormFieldSchemagains the key; the 表单字段簇:spec↔runtime 是双层词汇——枢纽补缺、覆盖闸门、边界响亮化(objectstack#4115) #3090 coverage pin list updated in the same PR (that test's own header requires the two to move together).ObjectFormSection.visibleWhenscope note rewritten to the landed truth.@object-ui/components— the renderer gate inrenderers/form/form.tsx:hidden, legacycondition,visibleWhenviaresolveFieldRuleState, andvisibleOn— with the same record assembly, fail-open fallbacks and locator strings, so heading and group cannot reach different verdicts.renderFormFieldreturns null for a field claimed by a hidden section — the same mechanism a field's own false predicate uses. That inheritance is the design: values still submit (ruled: visibility decides what is DRAWN and nothing else, the console precedent of 2026-08-22 after Console FormPage (the standalone /forms and internal FormView renderer) never evaluates visibleWhen/visibleOn — objectui#2212 was fixed in the OTHER form renderer #5594), client-side validation is skipped (ruled: a user must never be blocked by an error pointing at a control they cannot see — the Two authored-predicate evaluators still passundefinedscope after #6010 — console FormPage (both routes) and the wizard's submit-time required re-check #6110 defect shape — while the server contract stays the loud floor), and the stale-error effect clears a member's error when its section hides mid-session.FormSection.visibleWhenstill renders its FIELDS on the plugin-form chain — the console renderer drops the whole section #6236 contract (predicate gates only the heading) — deliberate compatibility, pinned.@object-ui/plugin-form— the producer wiring (round 2): all sixsection-dividersynthesis sites now stamp the membership claim onto the divider they emit — ObjectForm's stackedsimplepath, ModalForm's sectioned groups map and derived-fieldGroup path, DrawerForm's sectioned and derived-fieldGroup paths, SplitForm'spaneFields. The claim is the RESOLVED member names (X.map(f => f.name)at each site) — exactly the strings the renderer'shiddenSectionFieldNamesgate looks up in the flat list, and resolved rather than authored on purpose: authoredsection.fieldsentries can be spec field-defs, and a perms-filtered field is not in the form at all. In DrawerForm's sectioned path the member resolution moved above the divider push so the claim can name what the group actually contributes.Changeset:
@object-ui/types+@object-ui/components+@object-ui/plugin-form, all minor.The landing signal — measured in both directions
sectionVisibleWhen-6111.test.tsx'smeasured scopepin flipped, deliberately, exactly once: it now asserts a hidden section hides its FIELDS too (heading gone AND claimed member gone, same chain, same mount, same predicate). The flip is real in both colours: with the wiring in place the whole 16-case file is green; ablating ONLY the ObjectForm stamp turns the flipped pin red in the SHOWN direction (the field comes back) — that ablation run IS the pre-wiring tree for the chain the pin mounts. The pin's comment records why it flipped and what it now pins.Every per-layout DENIED row now asserts the claimed member as well as the heading (ALLOWED and FAULTED rows assert the member stays, keeping the controls honest). Per-site discrimination, measured by a six-way single-site ablation matrix (each mutation and each restore proven on disk by line-exact anchor counts; vitest resolves workspace imports to src, so no rebuild is involved):
measured scopepin (2)The two derived-fieldGroup sites are stamped for contract uniformity but are not dischargeable by any DENIED row: the spec
fieldGroupsvocabulary has no section-predicate slot, so no authoring path can put avisibleWhenon those chains and their gate is structurally fail-open today. This is stated in the 6111 file header and the changeset rather than papered over with a row that could not discriminate.Pin movements
predicate-scope-parity-6010.test.tsx,sectionSurfacerow — moved (round 1): the divider carries a claim and the row asserts heading and claimed member move together, returning their shared verdict.sectionVisibleWhen-6111.test.tsx— per-layout rows extended and themeasured scopepin flipped (round 2, above).section-grouping-6236.test.tsx(round 1, renderer-level, 8 cases): DENIED hides heading and every claimed field (un-gated control stays); ALLOWED shows all; FAULTED unbound root fails open; hidden section's values still submit; required field in a hidden section does not block submit; the same required field in a visible section still blocks; a section hiding mid-session clears its members' stale errors and unblocks the submit; claim-less divider keeps the heading-only contract.Verification (round 2 union at final commit
f9c01fc2, tree clean)pnpm exec vitest run --maxWorkers=2 packages/components/src/renderers/form/ packages/plugin-form/ packages/types/→ Test Files 192 passed (192), Tests 1867 passed (1867) — including the flipped 16-case 6111 matrix.type-checkgreen after building its dependency closure; plugin-formlint0 errors (718 pre-existing warnings); types/components type-check + lint were green in round 1 and their sources are unchanged this round exceptobjectql.ts(re-covered by the types package runs in the union).check-control-bytesOK (5454 tracked files), changeset presence OK (12 source files, 3 released packages, 1 changeset),changeset:check(fixed group + no major) OK. Round 1 additionally rancheck:spec-symbols,check:designer-field-key-parity,check:action-forward-parity,check:i18n-keys,check:phantom-deps,check:self-import— all OK.renderFormFieldturned 5 rows red, all in the predicted directions; restore proven by emptygit diff HEAD+ anchor counts.Overlap notes
bindon BaseSchema and its zod mirror #6574 (bindonBaseSchema+ zod mirror): no file-level or symbol-level overlap — this PR does not touchBaseSchemaorbase.zod.ts.fieldskey is additive and optional; the zod change only tightens (an authored non-string-arrayfieldsvalue now rejects where it was silently stripped — the loud direction, intended).apps/console/src/components/FormPage.tsxis the semantic REFERENCE for the ruling and is deliberately not touched.