Repository navigation
fix(plugin-form): one collapsed / collapsible resolution for the default and drawer arms (objectui#9849 step 1) - #10236
Conversation
…default and drawer arms Step one of objectui#9849 (director ruling letter E, item 4): the three collapse resolutions converge onto the declaration-based one objectui#9780 established. `resolveSectionCollapse` in fieldGroups.ts (module-internal, not re-exported from the package barrel) now answers the pair for ObjectForm's grouped layout and both DrawerForm pushes. The drawer's explicit push read `collapsible` alone for the control while reading `collapsed` unconditionally for the state, so `collapsed: true` written alone still drew a section with its fields out of the DOM and no control on the page. It now installs the control, and a section is only ever collapsed while its row is on the page to carry that control. Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
changeset-claim-re-read
|
✅ 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
|
…e moves The objectui#9849 step-one changeset said the only sections rendering differently were ones whose fields could not be reached. Two edge cases also move: a section sharing its key with a collapsible sibling is no longer hidden by that sibling's toggle, and a drawer section declared collapsed at mount now follows a later change to that declaration until the user toggles it. Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ 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
|
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by this seat. It was fed only the card, the objectui#9780 ruling, the PR and the two trees. Its served tier was checked by this seat against the stamp in its transcript. Adopted by this seat. ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Contract review — prose-only re-reviewServed-tier: ② Changeset proseThe rewritten clause is TRUE as a completeness claim. Every before/after divergence was enumerated per arm, and the sections that render differently are the unreachable class plus exactly the two named edges:
The reverse direction (false → true after mount) did not move on any arm. It was verified on the drawer's explicit push, the drawer's derived push and ObjectForm. No sentence implies step (2) is done or that the modal collapses. Nothing to quote. ③ Boundary flags
Rendered by an isolated review subagent spawned by this seat; its served tier was checked against its transcript stamp. Adopted by this seat. Implemented-by: VERDICT: PASS |
…lapse-resolution-converge
✅ 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
|
Part of #9849
Clause-②: no
Step (1) of the two the director ruling orders on this card (comment
5749155880, letter E, maintainer 「同意」): 「the threecollapsed/collapsibleresolutions converge to the declaration-based one objectui#9780 established, before the gate rule lands on it.」 Step (2) (E inprojectSectionDivider, deletingSectionDividerGate, re-pinningdrawerFormSectionDescription-9834row 5, the diagnostic pin) is ⛔ not in this PR and stays on the card. That is why the first line saysPart of.What changed
fieldGroups.tsgainsresolveSectionCollapse, besideprojectSectionDivider. It is module-internal: the package barrelsrc/index.tsxnamesfieldGroupszero times, andpackage.jsonexportsdeclares only".". It is the ONE resolution of the authored pair, and it follows objectui#9780 letter A:collapsedimpliescollapsible, read off the declaration and never off the live state.collapsible: falsewithcollapsed: trueresolves to collapsed, with the control present.collapsiblealone is open, with the control.hostsControl). This states the default arm's 「an untitled bucket is never collapsible」 once, for every arm.ObjectForm.tsx(grouped layout, explicit and derived sections share one loop): the inline objectui#9780 expression moves into the helper. Behaviour is unchanged (see the table).DrawerForm.tsx, both pushes, now call the same helper:collapsiblealone for the control andcollapsedunconditionally for the state.collapsible, which is a third spelling.useStatereplaces a seed that readcollapseda second time. An untoggled section falls back to its declaration inside the helper, as in ObjectForm..changeset/9849-collapse-resolution-converge.md:patchfor@object-ui/plugin-form.⛔ No new accepted key or value, no new package export, no new prop.
DrawerFormSectionConfigandModalFormSectionConfigare unchanged, and no section map inObjectForm.tsxcopies a new key.Premise, re-measured on
origin/main62597c5by printing everycollapsline in the four filesisCollapsible = Boolean(section.collapsible) || Boolean(section.collapsed), the statecollapsedSections[key] ?? section.collapsed, and fields hidden only onlabel && isCollapsed.collapsible: section.collapsible,onToggleonly forsection.collapsible, and the statecollapsedSections[key] ?? section.collapsedread unconditionally, which hides the fields. AuseStateseed also reads.collapsed.section.collapsible, the control only forsection.collapsible.collapslines. It has no resolution of its own. Neither route reads either member,ModalFormSectionConfigdeclares neither, andObjectForm's modal map copies neither. So the modal draws every field and no control, whatever the section declares.fieldGroups.tsat base only maps the@objectstack/speccollapseenum onto the pair (deriveFieldGroupSections) and copies a resolved pair onto the row (projectSectionDivider). Neither step is a resolution.⇒ The code has three resolutions, as the ruling says. objectui#10172, #10161 and #10141 did not move them. But there are only two in behaviour:
deriveFieldGroupLayoutnever emitscollapsedwithoutcollapsible(the deprecatedcollapsed: truealone maps to'collapsed', which maps to both), and every keyed group gets a heading (label defaults to the key).Before / after, per route
Read by
__tests__/collapseResolution-9849.test.tsxthrough a real renderer. "Before" means the same pin file run against the base source:Tests 9 failed | 21 passed (30). Every failure was on a drawer explicit route or a drawer trap row, and each failed for the right reason (0 controls while the fields were hidden).collapsedalonecollapsible: false+collapsedcollapsiblealonecollapsedcollapsedalonesectionsobjectFormSectionMembers-8071row 8 owns it)fieldGroupscollapse: 'collapsed'and deprecatedcollapsed: true)formType: 'drawer'(drawer map)fieldGroupsformType: 'modal', ModalForm derivedSectionDividerGate, step 2), ⛔ not the resolution. The default arm's blurb-only row carries no collapse pair under objectui#9835 letter B. The drawer's explicit push draws a row for every member, andSectionDividerrenders that row when it has a heading or a blurb. Both answers keep the fields reachable, and that is all the new file asserts there.ModalFormSectionConfig, which this step does not add.Evidence, all at head
8cb4574bPins. New
packages/plugin-form/src/__tests__/collapseResolution-9849.test.tsx, 30 rows:Every zero is lit: each absent-fields row also reads the heading drawn and the same reader non-empty after a click, and each no-control row is paired on its route with a row where the same reader finds one.
Tests 9 failed | 21 passed (30).Ablation. Run through
ablation-replace.mjs: each anchor hit exactly once, each write was verified on disk by blob hash and marker count, and each restore was proven by state (blob equals HEAD,git diff HEADempty) with a wrapper trap on EXIT/INT/TERM. The run covered the new file pluscollapsedImpliesCollapsible-9780,objectFormSectionMembers-8071andfieldGroupSections.test.tsx(53 tests). Directions were written down before the run, and all three matched:d0dc3ee90d5b→c0de0a1cc600, marker 0 → 3 → 0)Tests 9 failed | 44 passed (53): exactly the 9 drawer explicit and drawer trap rows. Every ObjectForm row stays green, and so do the #9780 and 8071 filesd0dc3ee90d5b→4bda9e2214c5)Tests 53 passed (53): predicted, because the old derived resolution is behaviourally identical on every author-reachable inputc24e82e8e24c→8a713103f65a)Tests 10 failed | 43 passed (53): thecollapsed-alone and contradiction rows on all three explicit routes at once, the drawer blurb row,collapsedImpliesCollapsible-9780rows 1 and 3, andobjectFormSectionMembers-8071row 4. One mutation reddens the default arm and the drawer together, which shows that one path serves bothNo
dist/is involved: the suite importsplugin-formfrom source through the root vitest config, so no dist preflight applies.Suites and gates (heavy runs under the shared verify lock,
VERDICT command-exit 0):pnpm exec vitest run packages/plugin-form/from the repo root:Test Files 109 passed (109),Tests 1074 passed | 1 skipped (1075).pnpm --filter @object-ui/plugin-form run type-checkechoedtsc --noEmit && tsc -p tsconfig.test.jsonand exited 0, after building the dependency closure (--filter '@object-ui/plugin-form^...' build, exit 0).tsc -p tsconfig.test.json --listFilesOnlylists the new pin file, so its types are checked..ts/.tsxfiles:eslint.config.jsapplies to**/*.{ts,tsx};--format jsonreports 4 files, 0 errors;projectService, noparserOptions.project), so this diff cannot move the verdict of a file it does not touch;no-explicit-anyschema casts, the sibling pins' pattern;pnpm lintis CI's.pnpm check:control-bytes,check:new-line-citations(0 new),node scripts/check-changeset-presence.mjs(names the changeset),check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:vi-mock-specifiersandcheck:vi-mock-override-shapeall exited 0.check-governed-queue-guard --testover the 5 paths reported NOT GOVERNED. A manual control-byte grep over the 5 files found no match.Spec Main Shape Gate, which is red on every PR today on untouched test files (objectui#10229). ⛔ Not addressed here.Acceptance notes
collapsedchanges after mount follows its declaration until the user toggles it, as ObjectForm always has.FormSectionContainer(FormSection.tsx, a published React component) has the same pair shape at the prop level. The objectui#9780 report already recorded it as not class (c): it is a component prop, and its only caller (the wizard) passes neither. It is outside this file surface and not touched..changeset/9849-single-divider-projection.mdsays of that change that 「every arm's gate, its collapse resolution and its predicate handling are unchanged」. That was a description of that change's diff, and it is still true of it. This changeset follows it in the same CHANGELOG and states the convergence.check:changeset-claimsasks for a read of.changeset/8738-object-form-fields-description.md, which namesObjectForm.tsx. It was read: it concerns the top-levelfieldsvocabulary, which this diff does not touch, and it is still true.Dispatched by the
domain:uiseat, sessionhttps://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC, claim comment5812052116.Generated by Claude Code