Repository navigation
fix(plugin-form): draw customFields members inside explicit sections on the drawer, modal, tabbed, wizard and split arms - #10451
Conversation
…on drawer and modal With explicit `sections`, DrawerForm and ModalForm built every section member from the object schema alone, so a `customFields` member naming that field was dropped; the default ObjectForm arm drew the member. The section normalizer now takes the member naming a field as the base definition, through `findCustomFieldMember`, the lookup the default arm's `mergeCustomFields` now resolves every name through. Each arm's section-entry override rules are unchanged. Derived (fieldGroups) sections reach the member by the same route, which retires `withCustomFieldMembers`. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
|
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
|
…on tabbed, wizard and split TabbedForm, WizardForm and SplitForm built every section member from the object schema alone, so a `customFields` member naming that field was dropped, as the drawer and modal did before the previous commit. Each arm now passes `customFields` into its `buildSectionFields` context, the one route it builds section fields by (the wizard's final-submit required gate reads the same callback). Their schema interfaces declare the key, as DrawerFormSchema and ModalFormSchema do, and the `submitTarget.ts` comments that said these three neither declare nor render it are corrected. The pins cover all six arms. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
✅ 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
|
Since the previous commit, TabbedFormSchema, WizardFormSchema and SplitFormSchema declare `customFields`, which widens the package's public type surface. A widening takes a minor level, so the changeset's level moves from patch to minor. The body is unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
✅ 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 the ① Derived judgments
② Semver level
objectui Precedent agrees. Among merged objectui PRs declaring ③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10254
Clause-②: yes — measured on the built declarations at
49dc4dd3c.TabbedFormSchema,WizardFormSchemaandSplitFormSchema(re-exported fromdist/index.d.ts) each gain an optionalcustomFields?: FormField[]member, the oneDrawerFormSchemaandModalFormSchemaalready declare. Nothing else on the package face moves:findCustomFieldMemberandSectionFieldsContextare absent fromdist/index.d.ts. The claim line was amended toyesin5825785976, and the changeset therefore declaresminor.What changes
With explicit
sectionspluscustomFields, five of the sixobject-formarms built every section member from the object schema alone, so the authored definition was dropped:drawer,modal,tabbed,wizardandsplit. The default arm resolves section names against its merged pool and drew the member. Now all six agree.customFieldsMerge.ts: the override direction's lookup is extracted asfindCustomFieldMember(customFields, name), andmergeCustomFields(the default arm's merge) now resolves every name through it. Same precedence, one spelling: first member naming the field wins, an emptycustomFieldssupplies nothing.withCustomFieldMembersis retired.sectionFields.ts:SectionFieldsContextgains an optionalcustomFields. InnormalizeSectionField, a bare name that a member names is drawn as that member (shape 3). For a spec{ field }entry, the member (a copy) is the BASE the entry's overrides are applied to, in place offromObjectSchema. Over a member base, the widget is re-decided only from the entry's owntype.DrawerForm.tsx/ModalForm.tsx: thebuildSectionFieldscallback passescustomFields: schema.customFields. Derived (fieldGroups) sections go through the same callback, which retires thewithCustomFieldMemberspre-map. Derived output is unchanged, and finding(plugin-form):DrawerForm/ModalFormstill REPLACEcustomFieldswhereObjectFormnow MERGES #10073's FIELD GROUPS rows pin it.TabbedForm.tsx/WizardForm.tsx/SplitForm.tsx(patch round 1): eachbuildSectionFieldscallback passescustomFields: schema.customFields. That callback is the only route by which each arm builds section fields. The wizard calls it for the rendered step and again in its final-submit required gate (missingRequiredByStep), so both see the member. The mobile auto-stepper handsWizardFormruntimeFormFieldobjects already merged, whichnormalizeSectionFielddraws verbatim, so nothing is applied twice. The three schema interfaces declarecustomFields?: FormField[], as the drawer and modal schemas do, becauseschema.customFieldsdoes not compile without it.submitTarget.ts(comment-only): three passages said these three arms neither declare nor rendercustomFields, and gave that as the reason not to widen their interfaces. This change makes both false, so the passages are corrected. No code in the file moves.Surface
Claim
5825076379, amended by seat comment5825490852, addsTabbedForm.tsx,WizardForm.tsxandSplitForm.tsxat theirbuildSectionFieldscontext. Two things beyond that one line per arm, both forced by the wiring:submitTarget.tscomment corrections, since the change falsifies those sentences.submitTarget.ts(comment-only) joined the claim in5825785976.Which semantics this unifies (triage note 3)
Unified: the base definition a section member starts from. A
customFieldsmember naming the field supplies the whole definition, in place of the one generated from object metadata. It uses the same lookup and the same precedence as the default arm's merge. A field no member names is generated exactly as before.Deliberately not unified (each arm keeps what it had):
visibleOn/colSpan/spanfrom a spec entry. The other five applynormalizeSectionField's full set on top of the base.fieldsand drops a name nothing declares; the other five do not.⛔ No default-arm behaviour change:
ObjectForm.tsxis untouched. The default arm's section order and its dropped section-entry overrides are separate findings, which the seat files at ACCEPT.Mechanism assumptions, measured
Probe on
main(0d39dc6e5), card schema: simpleINLINE NOTE; drawer, modal, tabbed, wizard and splitNote. After this PR all six readINLINE NOTE.A1 (precedence): one lookup,
findCustomFieldMember, is shared bymergeCustomFieldsandnormalizeSectionField. No second merge.A2 (member no section lists): the default arm does not draw it under explicit sections, and the other five already matched. Pinned as UNNAMED MEMBER on all six arms.
A3 (override rules): kept. A scratch probe through the real
ObjectForm, re-run on all six arms at2c6e298ef(not a pin), reads:{ field: 'note', label: 'SECTION LABEL' }does not restaterequired, and the member (labelledINLINE NOTE) declaresrequired: true. The default arm rendersINLINE NOTE*; the other five renderSECTION LABEL*, whose marker comes from the member.{ field: 'note', label: 'SECTION LABEL', required: true }does restaterequired. The default arm rendersNotewith no required marker; the other five renderSECTION LABEL*.The label split, and the no-member case's dropped
required, are the pre-existing override split. No pin authors an override the two rule sets answer differently.Round-1 assumption, falsified: "each fix is one line". The three schema interfaces did not declare
customFields, soschema.customFieldsdid not type-check. See Surface.A4 (pins):
__tests__/sectionsCustomFields-10254.test.tsx, renamed fromdrawerModalSectionsCustomFields-10254.test.tsxnow that it covers every arm. It mounts the realObjectFormperformType, with five rows on each of the six arms:simpleis the default-arm control, thendrawer,modal,tabbed,wizardandsplit. The rows are:aria-required, then an empty submit refused withINLINE NOTE is requiredand nocreate, then a filled submit reachingcreate;{ field, colSpan }entry keeps the member's label, required marker andtextareatype;Every schema has ONE section, so the wizard's rendered step is the section under test, and the
drawnFields()equalities pin that only that step's fields are drawn.Changeset
.changeset/10254-sections-customfields-drawer-modal.mddeclares'@object-ui/plugin-form': minor: a public-surface widening (Clause-②: yes) takes at leastminor, and ⛔ nevermajor.Patch round 2 (
2c6e298ef): no source change. The seat's contract review at49dc4dd3cpassed every code item, and this round moves only the changeset level frompatchtominor. That is frontmatter line 2 only: lines 1 and 3 and the body bytes are unchanged (SHA-256 compared before and after), and the body names no level. Every test below ran at49dc4dd3c, which differs from2c6e298efonly in that one line. The gates were re-run at2c6e298ef.Tests (at
49dc4dd3c)Every run went through
os-verify-lock.sh. Verdict lines are quoted.Tests 30 passed (30).ablation-replace.mjsreplaced the wizard's wiring line with a marker. On disk the wiring count went 1 → 0 and the marker count to 1; the blob wentb9dfeef7dab8→6588dafdd06f. Result:Tests 4 failed | 26 passed (30). The four are exactly the wizard's LABEL, REQUIRED, SPEC ENTRY and UNDECLARED NAME rows. Restore: blob equals HEAD (b9dfeef7dab8) andgit diff HEADis empty.TabbedForm.tsx,WizardForm.tsxandSplitForm.tsxwere checked out atbbe2b1e4b(wiring count 0 in each), trap-restored. Result:Tests 12 failed | 18 passed (30). The twelve are tabbed, wizard and split × LABEL, REQUIRED, SPEC ENTRY and UNDECLARED NAME. Restore: blobs equal HEAD andgit diff HEADis 0 bytes.bbe2b1e4bunder the file's old name:Tests 5 failed | 25 passed (30), the drawer's four member rows plus finding(plugin-form):DrawerForm/ModalFormstill REPLACEcustomFieldswhereObjectFormnow MERGES #10073's drawer FIELD GROUPS row;Tests 2 failed | 13 passed (15), the drawer and modal SPEC ENTRY rows (TEXTAREAvsINPUT);BASE:Tests 8 failed | 7 passed (15).pnpm exec vitest run --maxWorkers=2 packages/plugin-form/:Test Files 117 passed (117),Tests 1196 passed | 1 skipped (1197),VERDICT command-exit 0.plugin-formthat nameDrawerForm/ModalForm/TabbedForm/WizardForm/SplitForm/buildSectionFields/normalizeSectionField/customFields/sectionFields/submitTarget/hasInlineFieldSource:Test Files 38 passed (38),Tests 1733 passed (1733),VERDICT command-exit 0.pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build, thenpnpm --filter @object-ui/plugin-form type-check(tsc --noEmitplustsconfig.test.json): bothVERDICT command-exit 0.--listFilescounts the renamed test once.Gates (at
2c6e298ef, exit codes recorded)check-changeset-presence: exit 0,9 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).check-changeset-no-major: exit 0,No changeset declares a major bump.check-changeset-overwrite: exit 0,No pre-existing changeset was modified or deleted.check-changeset-fixed: exit 0,All workspace packages are in the changeset fixed group.check:new-line-citations(at49dc4dd3c; since then only the changeset's level line changed):0 new citation(s).check:control-bytes: exit 0,OK.check:changeset-claims: exit 0. It flags five pending changesets that name a touched file: The tabbed modal arm (ModalFormcontentLayout: 'tabbed') cannot carry a sectionvisibleWhenat all —FormFieldTabdeclares no predicate slot #6237's wizard-step split (WizardForm.tsx), finding(plugin-form):object-formcarries TWO surfaces spelledfieldswith different member vocabularies — the specFormFieldSchemaobject is silently dropped by the top-level one #8738, finding(plugin-form):object-master-detail-form.fields声明「Ignored whensectionsis given」,而运行时把两者取交集 —— 落在fields之外的 section 成员静默消失,是最后一个时整段一起消失 #9884 and Console FormPage (the standalone /forms and internal FormView renderer) never evaluates visibleWhen/visibleOn — objectui#2212 was fixed in the OTHER form renderer #5594 (sectionFields.ts), and the simple-form submit-handler one (submitTarget.ts). Each paragraph was read and is still true. By symbol, 14 other pending changesets nameTabbedForm/WizardForm/SplitForm, and none speaks tocustomFieldsundersectionson those arms, or is falsified by this change. The changeset body names all five arms and was not changed in round 2.49dc4dd3c: ESLint on every touched file gave 0 errors (the config is not type-aware; per-file warnings are unchanged or fewer), and the governed-surface test answeredNOT GOVERNED.Acceptance notes
index.tsxquoteObjectForm.tsx:361andModalForm.tsx:638. At base0d39dc6e5, neither line holds what its description says. There, the quotedObjectForm.tsxline is a comment, andmodalCloseButtonis read further down the file. The quotedModalForm.tsxline is not thecontentLayoutcheck. This PR does not changeObjectForm.tsxorindex.tsx; the quotes are left asindex.tsxcarries them.Generated by Claude Code