Repository navigation
finding(plugin-form): with explicit sections plus customFields, the drawer and modal arms ignore the authored members (the default arm honours them) #10254
Description
Activity
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsBlocked-by: #9849
分诊首次定级:
priority:p2·bug·domain:ui·pm:blocked—— 同时写了sections和customFields时,抽屉和弹窗表单忽略作者的自定义字段;要改的位置正被 #9849 第二步的 PR 修改,排在它后面Path:
packages/plugin-form/src/DrawerForm.tsx、packages/plugin-form/src/ModalForm.tsx(分区字段由buildSectionFields从对象 schema 重建,不经customFields)Triage: lands in
@object-ui/plugin-form⇒domain:ui,bug,priority:p2,pm:blockedBlocked-by #9849; rationale: the registeredcustomFieldsinput promises "field definitions merged over the set generated from object metadata" for everyformType, and with explicitsectionsthe drawer and modal arms rebuild section fields from the object schema alone, so an author's label / required override silently does not apply on two of three arms; the fix sits in the section-push region that PR #10284 (#9849 step two, in flight) is rewriting in the same two files, so it must follow that PR rather than race it.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T16:10Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),读了 #9849 的当前状态,并在 objectuimain(06b82b8)上核对。本席实测
DrawerForm的buildSectionFields只传入objectSchema等,不传customFields;两条分区路径都用它。ModalForm同形。PR fix(plugin-form): drawer and modal arms merge customFields like ObjectForm #10251 只修了"没有 sections"的路径。与卡面一致。- finding(plugin-form): ModalForm 的 derived-fieldGroups push 同样丢弃分组的
description—— 同文件里它的 stacked 兄弟带着这个键,两条 push 连门都不一致 #9849:pm:dispatched(os-project-manager),PR feat(plugin-form): one divider-row rule, group semantics on every arm, and the modal collapse pair (objectui#9849 step two) #10284(第二步:分隔行规则、各臂的分组语义、弹窗的折叠对)打开中,改的正是这两个文件的分区 push 区域。⇒ 同文件在飞兄弟。
定级说明
p2:违背一个已注册输入的描述,且是静默的;例如作者用
customFields把某字段设为必填,在抽屉和弹窗里不会生效。执行要点
- 等 PR feat(plugin-form): one divider-row rule, group semantics on every arm, and the modal collapse pair (objectui#9849 step two) #10284 合并后,在新的
main上重跑卡面的探针(三条臂、同一 schema)。 - 抽屉和弹窗的分区字段改为从"对象字段 +
customFields合并后的池"里取,与默认臂一致。 ⚠️ 卡面指出各臂对分区条目的覆盖语义本就不同(ObjectForm只复制visibleOn/colSpan/span,normalizeSectionField会应用label/required等)—— 先写清楚这次统一成哪一种,⛔ 不要顺手改变默认臂已有的行为。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 24, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsUnblock re-derivation:
pm:blocked→pm:queue· 2026-09-25T01:11ZActing on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「帮我排查一下 blocked 卡片,哪些需要更新状态帮我更新。」; where — the maintainer's chat with session
session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-24. ⛔ Not a claim and not a dispatch: the card returns to its lane's take order. Candidate surfaced bycheck-half-states.mjsH19 on this sweep.- Blocker: finding(plugin-form): ModalForm 的 derived-fieldGroups push 同样丢弃分组的
description—— 同文件里它的 stacked 兄弟带着这个键,两条 push 连门都不一致 #9849 closedcompletedon 2026-09-24; its last PR feat(plugin-form): one divider-row rule, group semantics on every arm, and the modal collapse pair (objectui#9849 step two) #10284 is merged. - Double-check ① — latest conversion comment
5817773218names exactly that condition (sameDrawerForm.tsx/ModalForm.tsx). - Double-check ② — no merged PR has referenced this card since that comment, and no commit on
mainnames finding(plugin-form): with explicitsectionspluscustomFields, the drawer and modal arms ignore the authored members (the default arm honours them) #10254. - Re-derived, still owed: on
origin/mainDrawerForm'sbuildSectionFieldsstill passesobjectSchema/objectName/readOnly/mode/recordId/fieldLabeland nocustomFields, and both section arms ofDrawerFormandModalFormstill build from it.
State:
pm:blocked→pm:queue;domain:ui,priority:p2unchanged; no assignee.
Generated by Claude Code
- Blocker: finding(plugin-form): ModalForm 的 derived-fieldGroups push 同样丢弃分组的
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 —
domain:uiexecution seat 4
Session:session_01BP8CMtACxTdLjqR6rhd33C
Branch:claude/issue-10254-sections-customfields-drawer-modal
Worktree:objectui-issue-10254
Domain:domain:ui
Seat:domain:ui#4
File surface:packages/plugin-form/src/DrawerForm.tsxandpackages/plugin-form/src/ModalForm.tsx: their section fields are taken from the pool of object fields merged withcustomFields, as the default arm already does. If the merge is best shared,buildSectionFieldsor a helper beside it inpackages/plugin-form/src/may change too. Tests beside them, and one.changeset/10254-…md. ⛔ The defaultObjectFormarm's existing behaviour does not change. Stop on breach; explain in the report.
Container & model:S,mode:subagent,model: opus— two form arms honour a registered input the third already honours
Clause-②: no
Thread-read: 5824994394
Serial constraints cleared: the blocker objectui#9849 is closed, and its PR objectui#10284 merged as8813335bd. Open-PR file lists read 2026-09-25T01:20Z ⇒ no open PR touchespackages/plugin-form/src/. Livepm:dispatchedclaims of the other seats read 2026-09-25T01:20Z ⇒ none namesplugin-form.Clause-②: no. Two arms stop dropping an author'scustomFieldsdefinition under explicitsections, which is what the registered input's description already promises for everyformType. No declared key, schema or export moves.
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10254, "status": "done", "branch": "claude/issue-10254-sections-customfields-drawer-modal", "pr": "https://github.com/objectstack-ai/objectui/pull/10451", "session": "session_01BP8CMtACxTdLjqR6rhd33C — subagent run, so this is the parent PM seat's harness-stamped id (the Claude-Session trailer on bbe2b1e4b)", "premise_still_valid": true, "summary": "The premise holds on main 0d39dc6e5. The card's probe through the real ObjectForm (one schema, sections [customer, note], customFields [{ name: note, label: INLINE NOTE }]) reads simple INLINE NOTE, drawer Note, modal Note. After the fix all three read INLINE NOTE. customFieldsMerge.ts gains findCustomFieldMember, the one precedence lookup, and mergeCustomFields (the default arm's merge) now resolves every name through it (A1: no second merge). SectionFieldsContext gains an optional customFields, and normalizeSectionField uses the member that names a field as the BASE: a bare name is drawn as the member verbatim; for a spec { field } entry the overrides apply to a copy of the member; over a member base only the entry's own type re-decides the widget. DrawerForm and ModalForm pass schema.customFields into their buildSectionFields context. Derived fieldGroups sections take the same route, which retires withCustomFieldMembers. Only the base-definition precedence is unified. Section-entry override rules (A3), the fields-intersection pool membership and section order are unchanged, and ObjectForm.tsx is untouched. A2, measured: under explicit sections the default arm does not draw a member no section lists. The drawer and modal already matched, and this is pinned. The relay opened draft PR #10451 as objectstack-fleet[bot]. The assignee field reads os-litant (the PM's), left alone. No labels were written (objectui, none named). Commit trailers are the model-free pair the dispatch sets, not the harness reminder's model-named line.", "tests": "Everything below ran at bbe2b1e4b, the final commit, and every run went through os-verify-lock.sh. New pins (packages/plugin-form/src/__tests__/drawerModalSectionsCustomFields-10254.test.tsx, 5 rows x simple/drawer/modal: LABEL, REQUIRED as aria-required plus an empty submit refused with \"INLINE NOTE is required\" and no create, then a filled submit reaching create, SPEC ENTRY, UNDECLARED NAME, UNNAMED MEMBER): 15/15. Reverse proof leg 1 (ablation-replace.mjs swaps the drawer's customFields wiring line for a marker; anchor 1 to 0, blob changed, marker on disk 1): \"Tests 5 failed | 25 passed (30)\". The five are exactly the drawer LABEL/REQUIRED/SPEC ENTRY/UNDECLARED NAME rows plus the #10073 drawer FIELD GROUPS row. All modal and simple rows stayed green. Restore: blob == HEAD and git diff HEAD empty. Leg 2 (the member-base rawType guard reverted): \"Tests 2 failed | 13 passed (15)\", the drawer and modal SPEC ENTRY rows, Expected \"TEXTAREA\" Received \"INPUT\". Restore proven. Pre-fix leg (the 4 sources at BASE, trap-restored, blob hashes and git diff HEAD proven): \"Tests 8 failed | 7 passed (15)\". The eight are the drawer and modal member rows; the simple and UNNAMED MEMBER rows stayed green. A first leg-1 attempt was refused by ablation-replace.mjs before running anything (the replacement was a substring of the anchor). It measured nothing and the file was restored. pnpm exec vitest run --maxWorkers=2 packages/plugin-form/: \"Test Files 117 passed (117)\", \"Tests 1181 passed | 1 skipped (1182)\", VERDICT command-exit 0. The 31 suites outside plugin-form naming DrawerForm/ModalForm/buildSectionFields/normalizeSectionField/customFields/sectionFields: \"Test Files 31 passed (31)\", \"Tests 1141 passed (1141)\", VERDICT command-exit 0. Closure build pnpm --workspace-concurrency=2 --filter @object-ui/plugin-form^... build: VERDICT command-exit 0. pnpm --filter @object-ui/plugin-form type-check (tsc --noEmit plus tsconfig.test.json; --listFiles counts the new test once): VERDICT command-exit 0. Gates (all exit 0): check-changeset-presence (\"5 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)\"), check-changeset-no-major, check:new-line-citations (\"0 new citation(s)\"), check:control-bytes (\"OK\"), check:changeset-claims. The last flagged the #8738, #9884 and #5594 changesets naming sectionFields.ts; each was read and is still true, and the #10073/#9778/#9849 changesets were read by symbol with none falsified. ESLint on the 5 touched files: exit 0, --format json counts 5 files and 0 errors. The config is not type-aware, so untouched files cannot move. No readFileSync-style pin quotes a changed line. Governed-surface test: NOT GOVERNED. Clause-2 measured on the built dist/index.d.ts: 0 hits for findCustomFieldMember and SectionFieldsContext, positive control DrawerForm 2. CI at report time on bbe2b1e4b: 42 check runs, 20 success, 3 skipped, 19 in_progress, none red.", "mcp_calls": "0", "api_writes": "2 relay writes, each a repository_dispatch to objectstack-ai/objectstack that the fleet-write run executes as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls with draft forced, run 36084418774 success, which opened #10451; (2) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10254/comments via post-stamped.mjs. Also 2 git pushes (the empty-branch probe and bbe2b1e4b), which are not REST. No label writes, no PATCH.", "open_questions": [], "out_of_scope_findings": [ "class: b · The tabbed, wizard and split arms ignore customFields under sections, the same defect as this card on the three sections-only arms. Contract: the registered customFields input (packages/plugin-form/src/index.tsx) reads 「Field definitions merged over the set generated from object metadata」, one description for every formType. Seam: spec:object-form.customFields (registered input) → renderer:TabbedForm / WizardForm / SplitForm (their buildSectionFields context passes no customFields). Evidence: probe through the real ObjectForm on main 0d39dc6e5 and again on bbe2b1e4b with the card's schema: simple renders INLINE NOTE, and tabbed/wizard/split render Note; a member's required: true yields no aria-required on those three. After #10451 the fix is one line per file (customFields: schema.customFields in each buildSectionFields context). This is an independent card in objectui, not a sub-issue of this one (whose surface is drawer/modal), with a Blocked-by: #10254 line because the fix reuses SectionFieldsContext.customFields · dedupe words: `customFields sections tabbed wizard split ignored` · `TabbedForm WizardForm SplitForm customFields` · `buildSectionFields customFields tabbed`", "class: a · The default (simple) arm draws a section's members in POOL order, not in the order the section lists them. Probe through the real ObjectForm on main 0d39dc6e5 and again on bbe2b1e4b: an object declaring customer before note, with sections [{ fields: [note, customer] }], renders customer, note on simple, and note, customer on drawer/modal/tabbed/wizard/split. Mechanism: the SimpleObjectForm section loop in ObjectForm.tsx filters the pool by section membership instead of walking the section's names. Lands in objectui · dedupe words: `ObjectForm section field order` · `sections fields order pool order simple arm` · `SimpleObjectForm section member order`", "class: b · The default arm drops the spec FormFieldSchema overrides authored on a section entry, except visibleOn/colSpan/span. Contract (@objectstack/spec 17.4.0 FormFieldSchema): label 「Display label override」, required 「Required override」, readonly 「Read-only override」, helpText 「Help/hint text」. Seam: spec:FormFieldSchema.label/required/readonly/helpText → renderer:ObjectForm SimpleObjectForm section loop (its sectionDefByName merge copies three keys). Evidence on main 0d39dc6e5 with no customFields: the entry { field: note, label: SECTION LABEL, required: true } renders Note without aria-required on simple, and SECTION LABEL* with aria-required=true on the other five arms. This is the override-rule split the card body and triage note 3 name, and it was kept unchanged here (A3). Lands in objectui · dedupe words: `section entry label required override ignored default arm` · `FormFieldSchema override simple sections` · `sectionDefByName visibleOn colSpan span`", "carrier: 承接者:无 · Two object-form registration descriptions in packages/plugin-form/src/index.tsx cite cross-file line addresses (ObjectForm.tsx:361, ModalForm.tsx:638). The ModalForm one was already false at BASE 0d39dc6e5. This is drift, not a class; it is in #10451 Acceptance notes and was not filed." ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsSeat decision on the
os-dev-report(5825475081) and a claim file-surface amendmentdomain:uiseat #4,session_01BP8CMtACxTdLjqR6rhd33C. This amends claim5825076379for PR objectui#10451.The dev's class-b finding folds into this PR in a patch round (⛔ no separate card). The
tabbed,wizardandsplitarms ignorecustomFieldsunder explicitsections. That is the defect this card fixes on drawer and modal, against the same registeredcustomFieldscontract ("Field definitions merged over the set generated from object metadata", one description for everyformType).- After this PR, each fix is one line:
customFields: schema.customFieldsin that arm'sbuildSectionFieldscontext, reusing the newSectionFieldsContext.customFields. - Landing drawer and modal alone would leave three of the six arms dropping an author's definition. A follow-up card would cost a second claim, dispatch and review for three lines.
Added to the claim's file surface:
packages/plugin-form/src/TabbedForm.tsx,WizardForm.tsxandSplitForm.tsx, each at itsbuildSectionFieldscontext, plus their rows in the existing pin.Serial: open-PR file lists read at 2026-09-25T02:05Z show no other open PR touching
packages/plugin-form/src/. Livepm:dispatchedclaims of the other seats name noplugin-formfile.Not folded, filed at ACCEPT: the dev's other two findings are separate defects on the default arm, and this PR deliberately leaves that arm unchanged. They are section member order (pool order, not section order) and dropped
FormFieldSchemasection-entry overrides.
Generated by Claude Code
- After this PR, each fix is one line:
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10254, "status": "done", "branch": "claude/issue-10254-sections-customfields-drawer-modal", "pr": "https://github.com/objectstack-ai/objectui/pull/10451", "head": "49dc4dd3c242c978239868d321b2df9125a7f22d", "round": "patch round 1 (seat decision 5825490852)", "session": "session_01BP8CMtACxTdLjqR6rhd33C — subagent run, so this is the parent PM seat's harness-stamped id (the Claude-Session trailer on 49dc4dd3c)", "premise_still_valid": true, "summary": "Patch round 1 folds the class-b finding in as the seat ruled. TabbedForm, WizardForm and SplitForm each pass customFields: schema.customFields into their buildSectionFields context. That callback is the only route by which each arm builds section fields. The wizard also calls it from its final-submit required gate (missingRequiredByStep). The mobile auto-stepper hands WizardForm already-merged runtime FormFields that normalizeSectionField draws verbatim, so nothing is applied twice. Mechanism assumption falsified: the fix is not one line per arm. TabbedFormSchema, WizardFormSchema and SplitFormSchema declared no customFields, so schema.customFields did not type-check. Each now declares customFields?: FormField[], as DrawerFormSchema and ModalFormSchema do. That widens three exported types, which is why Clause-② now measures yes (open question 1). submitTarget.ts had three comment passages saying these arms neither declare nor render customFields, and giving that as the reason not to widen their interfaces. This change falsifies them, so they are corrected (comment-only, no code). That file is outside the amended surface (open question 2). The pin file is renamed to sectionsCustomFields-10254.test.tsx and covers all six arms (30 rows). Every schema has one section, so the wizard's rendered step is the section under test. The changeset body names all five arms, and its frontmatter is byte-identical (SHA-256 of the three frontmatter lines unchanged). The default arm is untouched. One new commit, 49dc4dd3c, pushed fast-forward from bbe2b1e4b: no force, amend or rebase, model-free trailers. The worktree is removed.", "tests": "Everything below ran at 49dc4dd3c through os-verify-lock.sh. Pins (sectionsCustomFields-10254.test.tsx, 5 rows x simple/drawer/modal/tabbed/wizard/split): \"Tests 30 passed (30)\". Reverse proof, wizard leg: ablation-replace.mjs replaced the wizard's customFields wiring line with a marker; on-disk wiring 1 to 0, marker 1; blob b9dfeef7dab8 to 6588dafdd06f. Result \"Tests 4 failed | 26 passed (30)\": exactly the wizard LABEL/REQUIRED/SPEC ENTRY/UNDECLARED NAME rows. Restore: blob == HEAD (b9dfeef7dab8) and git diff HEAD empty. Three-arm leg: TabbedForm/WizardForm/SplitForm checked out at bbe2b1e4b (wiring count 0 each), trap-restored. Result \"Tests 12 failed | 18 passed (30)\": tabbed, wizard and split x the four member rows. Restore: blobs == HEAD and git diff HEAD 0 bytes. pnpm exec vitest run --maxWorkers=2 packages/plugin-form/: \"Test Files 117 passed (117)\", \"Tests 1196 passed | 1 skipped (1197)\", VERDICT command-exit 0. The 38 suites outside plugin-form naming DrawerForm/ModalForm/TabbedForm/WizardForm/SplitForm/buildSectionFields/normalizeSectionField/customFields/sectionFields/submitTarget/hasInlineFieldSource: \"Test Files 38 passed (38)\", \"Tests 1733 passed (1733)\", VERDICT command-exit 0. Closure build pnpm --workspace-concurrency=2 --filter @object-ui/plugin-form^... build: VERDICT command-exit 0. pnpm --filter @object-ui/plugin-form type-check (tsc --noEmit plus tsconfig.test.json; --listFiles counts the renamed test once): VERDICT command-exit 0. Gates (all exit 0): check-changeset-presence (\"9 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)\"), check-changeset-no-major, check:new-line-citations (\"0 new citation(s)\"), check:control-bytes (\"OK\"), check:changeset-claims. The last flagged five pending changesets that name touched files (#6237 wizard-step split on WizardForm.tsx; #8738, #9884 and #5594 on sectionFields.ts; the simple-form submit-handler one on submitTarget.ts). Each was read and is still true. By symbol, 14 other pending changesets name TabbedForm/WizardForm/SplitForm, and none says anything about customFields on those arms. ESLint on the 5 round-1 files: 0 errors, per-file warnings unchanged against bbe2b1e4b. Governed-surface test: NOT GOVERNED. Built dist: customFields?: FormField[] appears once in each of TabbedForm.d.ts, WizardForm.d.ts, SplitForm.d.ts, DrawerForm.d.ts and ModalForm.d.ts. CI at report time on 49dc4dd3c: 43 check runs, 40 success, 3 skipped, none red.", "mcp_calls": "0", "api_writes": "Round 1: 1 relay write, this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10254/comments via post-stamped.mjs (a repository_dispatch to objectstack-ai/objectstack executed as objectstack-fleet[bot]), plus 1 git push (49dc4dd3c, fast-forward), which is not REST. No PATCH of the PR body: pr_body_replacement is for the seat to write. No label writes. Round 0 (already reported): the pr_create and the first os-dev-report comment.", "open_questions": [ { "question": "Clause-② on claim 5825076379 reads no. After this round it measures yes, because the three exported schema interfaces (TabbedFormSchema / WizardFormSchema / SplitFormSchema) gain an optional customFields?: FormField[]. Which should the card carry?", "options": [ "A — keep the declarations and amend the claim line to yes. The pr_body_replacement already reads yes. Cost: none beyond the claim edit; the types become honest (declared = read), and this matches DrawerFormSchema / ModalFormSchema.", "B — drop the three interface members and read the key through a narrow cast. Clause-② goes back to no. Cost: a renderer reading a key its own schema type does not declare (enforced but not declared), a small follow-up commit, and the Clause-② line in pr_body_replacement changed back to no." ], "recommendation": "A. submitTarget.ts withheld this exact widening only because these arms did not render the key. Now that they do, leaving it undeclared would be the mirror of the declared-not-enforced shape that file warns about, and the drawer and modal already set the precedent." }, { "question": "submitTarget.ts (comment-only, three passages) is outside the amended file surface, but this change falsified those passages, so they were corrected in 49dc4dd3c. Should it be added to the claim?", "options": [ "A — add packages/plugin-form/src/submitTarget.ts (comment-only) to the claim's file surface.", "B — revert the comment edits and file the stale passages separately." ], "recommendation": "A, because a change that makes a published source comment false owes the correction in the same PR, and no code in the file moved." } ], "out_of_scope_findings": [ "carrier: 承接者:无 · No new findings this round. The two default-arm findings from round 0 (section order, dropped section-entry overrides) are with the seat to file at ACCEPT, per 5825490852. The index.tsx stale cross-file line citation stays an Acceptance note, not filed." ], "pr_body_replacement": "Fixes #10254\nClause-②: yes — measured on the built declarations at `49dc4dd3c`. `TabbedFormSchema`, `WizardFormSchema` and `SplitFormSchema` (re-exported from `dist/index.d.ts`) each gain an optional `customFields?: FormField[]` member, the one `DrawerFormSchema` and `ModalFormSchema` already declare. Nothing else on the package face moves: `findCustomFieldMember` and `SectionFieldsContext` are absent from `dist/index.d.ts`. The claim line reads `no`; the seat is asked to amend it on the card.\n\n## What changes\n\nWith explicit `sections` plus `customFields`, five of the six `object-form` arms built every section member from the object schema alone, so the authored definition was dropped: `drawer`, `modal`, `tabbed`, `wizard` and `split`. The default arm resolves section names against its merged pool and drew the member. Now all six agree.\n\n- `customFieldsMerge.ts`: the override direction's lookup is extracted as `findCustomFieldMember(customFields, name)`, and `mergeCustomFields` (the default arm's merge) now resolves every name through it. Same precedence, one spelling: first member naming the field wins, an empty `customFields` supplies nothing. `withCustomFieldMembers` is retired.\n- `sectionFields.ts`: `SectionFieldsContext` gains an optional `customFields`. In `normalizeSectionField`, 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 of `fromObjectSchema`. Over a member base, the widget is re-decided only from the entry's own `type`.\n- `DrawerForm.tsx` / `ModalForm.tsx`: the `buildSectionFields` callback passes `customFields: schema.customFields`. Derived (`fieldGroups`) sections go through the same callback, which retires the `withCustomFieldMembers` pre-map. Derived output is unchanged, and #10073's FIELD GROUPS rows pin it.\n- `TabbedForm.tsx` / `WizardForm.tsx` / `SplitForm.tsx` (patch round 1): each `buildSectionFields` callback passes `customFields: 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 hands `WizardForm` runtime `FormField` objects already merged, which `normalizeSectionField` draws verbatim, so nothing is applied twice. The three schema interfaces declare `customFields?: FormField[]`, as the drawer and modal schemas do, because `schema.customFields` does not compile without it.\n- `submitTarget.ts` (comment-only): three passages said these three arms neither declare nor render `customFields`, 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.\n\n## Surface\n\nClaim `5825076379`, amended by seat comment `5825490852`, adds `TabbedForm.tsx`, `WizardForm.tsx` and `SplitForm.tsx` at their `buildSectionFields` context. Two things beyond that one line per arm, both forced by the wiring:\n- the three optional interface members, since the wiring does not compile without them;\n- the `submitTarget.ts` comment corrections, since the change falsifies those sentences.\n\nThe seat is asked to add `submitTarget.ts` (comment-only) to the claim.\n\n## Which semantics this unifies (triage note 3)\n\n**Unified:** the base definition a section member starts from. A `customFields` member 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.\n\n**Deliberately not unified (each arm keeps what it had):**\n- Section-entry override rules. The default arm copies only `visibleOn` / `colSpan` / `span` from a spec entry. The other five apply `normalizeSectionField`'s full set on top of the base.\n- Pool membership. The default arm intersects sections with top-level `fields` and drops a name nothing declares; the other five do not.\n- Field order inside a section.\n\n⛔ No default-arm behaviour change: `ObjectForm.tsx` is untouched. The default arm's section order and its dropped section-entry overrides are separate findings, which the seat files at ACCEPT.\n\n## Mechanism assumptions, measured\n\n- **Probe on `main` (`0d39dc6e5`), card schema:** simple `INLINE NOTE`; drawer, modal, tabbed, wizard and split `Note`. After this PR all six read `INLINE NOTE`.\n- **A1 (precedence):** one lookup, `findCustomFieldMember`, is shared by `mergeCustomFields` and `normalizeSectionField`. No second merge.\n- **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.\n- **A3 (override rules):** kept. With `{ field: 'note', label: 'SECTION LABEL' }` over a required member labelled `INLINE NOTE`, the default arm renders `INLINE NOTE*` and the other five render `SECTION LABEL*`. That is the pre-existing override split: with no member, it is `Note` against `SECTION LABEL*`. No pin authors an override the two rule sets answer differently.\n- **Round-1 assumption, falsified:** \"each fix is one line\". The three schema interfaces did not declare `customFields`, so `schema.customFields` did not type-check. See Surface.\n- **A4 (pins):** `__tests__/sectionsCustomFields-10254.test.tsx`, renamed from `drawerModalSectionsCustomFields-10254.test.tsx` now that it covers every arm. It mounts the real `ObjectForm` per `formType`, with five rows on each of the six arms: `simple` is the default-arm control, then `drawer`, `modal`, `tabbed`, `wizard` and `split`. The rows are:\n - LABEL;\n - REQUIRED: `aria-required`, then an empty submit refused with `INLINE NOTE is required` and no `create`, then a filled submit reaching `create`;\n - SPEC ENTRY: a `{ field, colSpan }` entry keeps the member's label, required marker and `textarea` type;\n - UNDECLARED NAME;\n - UNNAMED MEMBER.\n\n 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.\n\n## Tests (at `49dc4dd3c`)\n\nEvery run went through `os-verify-lock.sh`. Verdict lines are quoted.\n\n- Pins: `Tests 30 passed (30)`.\n- Reverse proof, wizard leg (round 1): `ablation-replace.mjs` replaced the wizard's wiring line with a marker. On disk the wiring count went 1 → 0 and the marker count to 1; the blob went `b9dfeef7dab8` → `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`) and `git diff HEAD` is empty.\n- Reverse proof, three-arm leg (round 1): `TabbedForm.tsx`, `WizardForm.tsx` and `SplitForm.tsx` were checked out at `bbe2b1e4b` (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 and `git diff HEAD` is 0 bytes.\n- Round-0 legs, run at `bbe2b1e4b` under the file's old name:\n - drawer wiring removed: `Tests 5 failed | 25 passed (30)`, the drawer's four member rows plus #10073's drawer FIELD GROUPS row;\n - member-base type guard reverted: `Tests 2 failed | 13 passed (15)`, the drawer and modal SPEC ENTRY rows (`TEXTAREA` vs `INPUT`);\n - the four round-0 sources at `BASE`: `Tests 8 failed | 7 passed (15)`.\n- `pnpm exec vitest run --maxWorkers=2 packages/plugin-form/`: `Test Files 117 passed (117)`, `Tests 1196 passed | 1 skipped (1197)`, `VERDICT command-exit 0`.\n- The 38 suites outside `plugin-form` that name `DrawerForm` / `ModalForm` / `TabbedForm` / `WizardForm` / `SplitForm` / `buildSectionFields` / `normalizeSectionField` / `customFields` / `sectionFields` / `submitTarget` / `hasInlineFieldSource`: `Test Files 38 passed (38)`, `Tests 1733 passed (1733)`, `VERDICT command-exit 0`.\n- Closure build `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build`, then `pnpm --filter @object-ui/plugin-form type-check` (`tsc --noEmit` plus `tsconfig.test.json`): both `VERDICT command-exit 0`. `--listFiles` counts the renamed test once.\n\n## Gates (at `49dc4dd3c`)\n\n- `check-changeset-presence`: `9 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)`.\n- `check-changeset-no-major`: passed.\n- `check:new-line-citations`: `0 new citation(s)`.\n- `check:control-bytes`: `OK`.\n- `check:changeset-claims`: exit 0. It flags five pending changesets that name a touched file: #6237's wizard-step split (`WizardForm.tsx`), #8738, #9884 and #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 name `TabbedForm` / `WizardForm` / `SplitForm`, and none says anything about `customFields` on those arms. The changeset body now names all five arms; its frontmatter is byte-identical (the SHA-256 of the three frontmatter lines is unchanged).\n- ESLint on every touched file: 0 errors. The config is not type-aware. Per-file warnings are unchanged or fewer.\n- The governed-surface test answers `NOT GOVERNED`.\n\n## Acceptance notes\n\n- **Default-arm section order and dropped section-entry overrides:** these are separate defects on the default arm, left unchanged here. The seat files them at ACCEPT.\n- Two registration descriptions in `index.tsx` cite line addresses in other files (`ObjectForm.tsx:361`, `ModalForm.tsx:638`). The `ModalForm` one was already false at `BASE`. `index.tsx` is untouched here.\n\n---\n_Generated by [Claude Code](https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C)_\n" }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsSeat answers to the patch-round report (
5825763039) and claim amendment 2domain:uiseat #4,session_01BP8CMtACxTdLjqR6rhd33C. This amends claim5825076379, which was first amended in5825490852.open_questions[0]: A. The claim'sClause-②line becomesyes, and the three declarations stay.- The charter's test is "does this card widen the accepted set or the public surface?"
TabbedFormSchema,WizardFormSchemaandSplitFormSchemaare re-exported from the package entry (dist/index.d.ts), and each gains an optionalcustomFields?: FormField[]. That is a public-surface widening, so the answer isyes. - It is the honest shape, because the renderers now read the key: declared means read. It also matches
DrawerFormSchema/ModalFormSchema. - Option B would read an undeclared key through a cast. That is the enforced-not-declared shape, the mirror of the one
submitTarget.tswarns about. - Per the
Clause-②gate, a contract-review PASS on the actual diff must be on record before this PR is enqueued. The seat's review covers it.
open_questions[1]: A.packages/plugin-form/src/submitTarget.tsjoins the file surface, comment-only. This change made three published source comments false, and a change that falsifies a comment owes the correction in the same PR. No code in the file moves.The claim line now reads:
Clause-②: yes, amended in this comment.Serial: open-PR file lists read 2026-09-25T02:39Z ⇒ only this PR (objectui#10451) touches
submitTarget.ts.
Generated by Claude Code
- The charter's test is "does this card widen the accepted set or the public surface?"
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actions🔁 REWORK (patch round 2): PR objectui#10451 at
49dc4dd, one frontmatter line and the PR textdomain:uiseat #4,session_01BP8CMtACxTdLjqR6rhd33C. The contract review of PR objectui#10451 at49dc4dd3c242c978239868d321b2df9125a7f22dpassed every code item.- The code:
findCustomFieldMemberis the one precedence lookup.normalizeSectionFieldbuilds on a shallow copy of the member and never writes to the author's object. The wizard's final-submit gate sees the member. The default arm is byte-identical. The 30 pins are real and can fail.submitTarget.tsis comment-only and true. - CI: 40 success, 3 skipped, 0 red.
clean. - The surface: exactly the claim plus its two amendments.
It failed on the semver level and on the PR text. No source change is asked for.
Item 1: the changeset level is
minor, notpatch- What changed: since patch round 1,
TabbedFormSchema,WizardFormSchemaandSplitFormSchema(all re-exported from the package entry) declarecustomFields?: FormField[]. That is a public-surface widening. This seat ruled itClause-②: yesin5825785976. - The rule: objectstack
AGENTS.md, Post-Task Checklist item 3, says "yestakes at leastminor". objectuiAGENTS.md「版本号策略」 caps the level atminor(⛔ nevermajor) and otherwise advancesminor/patchas usual. - Precedent: every recent merged objectui PR declaring
Clause-②: yesfor a widening declaredminor. - The frontmatter: the frontmatter of
.changeset/10254-sections-customfields-drawer-modal.mdstayed byte-identical from round 0, whenpatchwas right. It becomes'@object-ui/plugin-form': minor. The fixed group already has pendingminorentries, so the release level does not move.
Item 2: the PR title and body
- Title: it still says "on the drawer and modal arms"; the head fixes five arms.
- Two stale sentences: the body says the seat "is asked to" amend the claim line to
yesand to addsubmitTarget.ts. Both were done in5825785976. - The A3 paragraph: it shows
SECTION LABEL*for an entry that does not restaterequired: true. State that it does, or drop the*. - The line-citation note: the Acceptance note on the two
index.tsxcitations says theModalFormone was already false atBASE. TheObjectForm.tsxone is false at base and head too. Say both. - The level: name
minorwherever the body states the level.
The seat writes the new title and body at ACCEPT, from the dev's replacement.
Unchanged
The file surface: no amendment. The two default-arm observations (pool order; dropped section-entry overrides) are still filed by the seat at ACCEPT, not in this PR.
Generated by Claude Code
- The code:
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10254, "status": "done", "branch": "claude/issue-10254-sections-customfields-drawer-modal", "pr": "https://github.com/objectstack-ai/objectui/pull/10451", "head": "2c6e298efcb9f93a2b77cfbd9f942e570933863b", "round": "patch round 2 (REWORK 5826395815, plus the seat addendum on the index.tsx quotes)", "session": "session_01BP8CMtACxTdLjqR6rhd33C — subagent run, so this is the parent PM seat's harness-stamped id (the Claude-Session trailer on 2c6e298ef)", "premise_still_valid": true, "summary": "Item 1: one new commit, 2c6e298ef, pushed fast-forward from 49dc4dd3c (no force, amend or rebase; model-free trailers). It changes .changeset/10254-sections-customfields-drawer-modal.md frontmatter line 2 from '@object-ui/plugin-form': patch to '@object-ui/plugin-form': minor. That is the only change: numstat 1/1, frontmatter lines 1 and 3 and the body bytes identical by SHA-256 before and after. The body was re-read: it names no level, it has no line-address citation, and every sentence is still true at the new head (no source change since 49dc4dd3c). So it is unchanged. Item 2: pr_title_replacement names the five arms (120 bytes). pr_body_replacement makes these edits:\n(a) the Clause-② line now says the claim was amended to yes in 5825785976 and the changeset declares minor;\n(b) submitTarget.ts is stated as joined in 5825785976;\n(c) the A3 paragraph now says which entry restates required. The member case's * comes from the member; the no-member entry restates required: true. Both cases were re-measured on all six arms at 2c6e298ef with the scratch probe (not a pin);\n(d) per the seat addendum, the index.tsx note quotes `ObjectForm.tsx:361` and `ModalForm.tsx:638` as index.tsx carries them, bound to base 0d39dc6e5, where neither line holds what its description says. The numbers are not corrected, and index.tsx is untouched;\n(e) a Changeset section names minor and carries a short line on this round. Gate lines now carry exit codes at 2c6e298ef.\nI wrote no PR title or body; the worktree is removed.", "tests": "No source change this round, so the suite results from 49dc4dd3c stand (plugin-form 117 files / 1196 passed | 1 skipped; pins 30/30; the 38 outside suites 1733 passed). 2c6e298ef differs from 49dc4dd3c only in the changeset level line. Gates at 2c6e298ef, exit codes: check-changeset-no-major 0 (\"No changeset declares a major bump\"); check:changeset-claims 0 (the same five flagged pending changesets as round 1, all still true); check-changeset-overwrite 0 (\"1 changeset(s) added, 0 modified, 0 deleted\" and \"No pre-existing changeset was modified or deleted\"); check:control-bytes 0 (\"OK, scanned 8497 tracked text file(s)\"); plus check-changeset-presence 0 and check-changeset-fixed 0 (\"All workspace packages are in the changeset fixed group\"). A3 re-measure for edit (c): the scratch probe through the real ObjectForm at 2c6e298ef, under os-verify-lock.sh: \"Tests 49 passed (49)\", VERDICT command-exit 0. Card schema: INLINE NOTE on all six arms. Member case (entry without required, member required): simple INLINE NOTE*, the other five SECTION LABEL* with aria-required=true. No-member case (entry restating required: true): simple Note with aria-required absent, the other five SECTION LABEL* with aria-required=true. Edit (d) was read from git show: at 0d39dc6e5 and at 49dc4dd3c, ObjectForm.tsx line 361 is a comment line and modalCloseButton is read further down, and ObjectForm.tsx has a 0-line diff between them.", "mcp_calls": "0", "api_writes": "Round 2: 1 relay write, this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10254/comments via post-stamped.mjs (a repository_dispatch to objectstack-ai/objectstack executed as objectstack-fleet[bot]), plus 1 git push (2c6e298ef, fast-forward), which is not REST. No PR title/body write, no label write, no MCP write.", "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · No new findings this round. The two default-arm findings (section order, dropped section-entry overrides) stay with the seat to file at ACCEPT. The index.tsx quoted citations stay an Acceptance note, not filed and not corrected." ], "pr_title_replacement": "fix(plugin-form): draw customFields members inside explicit sections on the drawer, modal, tabbed, wizard and split arms", "pr_body_replacement": "Fixes #10254\nClause-②: yes — measured on the built declarations at `49dc4dd3c`. `TabbedFormSchema`, `WizardFormSchema` and `SplitFormSchema` (re-exported from `dist/index.d.ts`) each gain an optional `customFields?: FormField[]` member, the one `DrawerFormSchema` and `ModalFormSchema` already declare. Nothing else on the package face moves: `findCustomFieldMember` and `SectionFieldsContext` are absent from `dist/index.d.ts`. The claim line was amended to `yes` in `5825785976`, and the changeset therefore declares `minor`.\n\n## What changes\n\nWith explicit `sections` plus `customFields`, five of the six `object-form` arms built every section member from the object schema alone, so the authored definition was dropped: `drawer`, `modal`, `tabbed`, `wizard` and `split`. The default arm resolves section names against its merged pool and drew the member. Now all six agree.\n\n- `customFieldsMerge.ts`: the override direction's lookup is extracted as `findCustomFieldMember(customFields, name)`, and `mergeCustomFields` (the default arm's merge) now resolves every name through it. Same precedence, one spelling: first member naming the field wins, an empty `customFields` supplies nothing. `withCustomFieldMembers` is retired.\n- `sectionFields.ts`: `SectionFieldsContext` gains an optional `customFields`. In `normalizeSectionField`, 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 of `fromObjectSchema`. Over a member base, the widget is re-decided only from the entry's own `type`.\n- `DrawerForm.tsx` / `ModalForm.tsx`: the `buildSectionFields` callback passes `customFields: schema.customFields`. Derived (`fieldGroups`) sections go through the same callback, which retires the `withCustomFieldMembers` pre-map. Derived output is unchanged, and #10073's FIELD GROUPS rows pin it.\n- `TabbedForm.tsx` / `WizardForm.tsx` / `SplitForm.tsx` (patch round 1): each `buildSectionFields` callback passes `customFields: 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 hands `WizardForm` runtime `FormField` objects already merged, which `normalizeSectionField` draws verbatim, so nothing is applied twice. The three schema interfaces declare `customFields?: FormField[]`, as the drawer and modal schemas do, because `schema.customFields` does not compile without it.\n- `submitTarget.ts` (comment-only): three passages said these three arms neither declare nor render `customFields`, 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.\n\n## Surface\n\nClaim `5825076379`, amended by seat comment `5825490852`, adds `TabbedForm.tsx`, `WizardForm.tsx` and `SplitForm.tsx` at their `buildSectionFields` context. Two things beyond that one line per arm, both forced by the wiring:\n- the three optional interface members, since the wiring does not compile without them;\n- the `submitTarget.ts` comment corrections, since the change falsifies those sentences.\n\n`submitTarget.ts` (comment-only) joined the claim in `5825785976`.\n\n## Which semantics this unifies (triage note 3)\n\n**Unified:** the base definition a section member starts from. A `customFields` member 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.\n\n**Deliberately not unified (each arm keeps what it had):**\n- Section-entry override rules. The default arm copies only `visibleOn` / `colSpan` / `span` from a spec entry. The other five apply `normalizeSectionField`'s full set on top of the base.\n- Pool membership. The default arm intersects sections with top-level `fields` and drops a name nothing declares; the other five do not.\n- Field order inside a section.\n\n⛔ No default-arm behaviour change: `ObjectForm.tsx` is untouched. The default arm's section order and its dropped section-entry overrides are separate findings, which the seat files at ACCEPT.\n\n## Mechanism assumptions, measured\n\n- **Probe on `main` (`0d39dc6e5`), card schema:** simple `INLINE NOTE`; drawer, modal, tabbed, wizard and split `Note`. After this PR all six read `INLINE NOTE`.\n- **A1 (precedence):** one lookup, `findCustomFieldMember`, is shared by `mergeCustomFields` and `normalizeSectionField`. No second merge.\n- **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.\n- **A3 (override rules):** kept. A scratch probe through the real `ObjectForm`, re-run on all six arms at `2c6e298ef` (not a pin), reads:\n - Member case. The entry `{ field: 'note', label: 'SECTION LABEL' }` does not restate `required`, and the member (labelled `INLINE NOTE`) declares `required: true`. The default arm renders `INLINE NOTE*`; the other five render `SECTION LABEL*`, whose marker comes from the member.\n - No-member case. The entry `{ field: 'note', label: 'SECTION LABEL', required: true }` does restate `required`. The default arm renders `Note` with no required marker; the other five render `SECTION LABEL*`.\n\n 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.\n- **Round-1 assumption, falsified:** \"each fix is one line\". The three schema interfaces did not declare `customFields`, so `schema.customFields` did not type-check. See Surface.\n- **A4 (pins):** `__tests__/sectionsCustomFields-10254.test.tsx`, renamed from `drawerModalSectionsCustomFields-10254.test.tsx` now that it covers every arm. It mounts the real `ObjectForm` per `formType`, with five rows on each of the six arms: `simple` is the default-arm control, then `drawer`, `modal`, `tabbed`, `wizard` and `split`. The rows are:\n - LABEL;\n - REQUIRED: `aria-required`, then an empty submit refused with `INLINE NOTE is required` and no `create`, then a filled submit reaching `create`;\n - SPEC ENTRY: a `{ field, colSpan }` entry keeps the member's label, required marker and `textarea` type;\n - UNDECLARED NAME;\n - UNNAMED MEMBER.\n\n 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.\n\n## Changeset\n\n`.changeset/10254-sections-customfields-drawer-modal.md` declares `'@object-ui/plugin-form': minor`: a public-surface widening (`Clause-②: yes`) takes at least `minor`, and ⛔ never `major`.\n\n**Patch round 2 (`2c6e298ef`):** no source change. The seat's contract review at `49dc4dd3c` passed every code item, and this round moves only the changeset level from `patch` to `minor`. 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 at `49dc4dd3c`, which differs from `2c6e298ef` only in that one line. The gates were re-run at `2c6e298ef`.\n\n## Tests (at `49dc4dd3c`)\n\nEvery run went through `os-verify-lock.sh`. Verdict lines are quoted.\n\n- Pins: `Tests 30 passed (30)`.\n- Reverse proof, wizard leg (round 1): `ablation-replace.mjs` replaced the wizard's wiring line with a marker. On disk the wiring count went 1 → 0 and the marker count to 1; the blob went `b9dfeef7dab8` → `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`) and `git diff HEAD` is empty.\n- Reverse proof, three-arm leg (round 1): `TabbedForm.tsx`, `WizardForm.tsx` and `SplitForm.tsx` were checked out at `bbe2b1e4b` (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 and `git diff HEAD` is 0 bytes.\n- Round-0 legs, run at `bbe2b1e4b` under the file's old name:\n - drawer wiring removed: `Tests 5 failed | 25 passed (30)`, the drawer's four member rows plus #10073's drawer FIELD GROUPS row;\n - member-base type guard reverted: `Tests 2 failed | 13 passed (15)`, the drawer and modal SPEC ENTRY rows (`TEXTAREA` vs `INPUT`);\n - the four round-0 sources at `BASE`: `Tests 8 failed | 7 passed (15)`.\n- `pnpm exec vitest run --maxWorkers=2 packages/plugin-form/`: `Test Files 117 passed (117)`, `Tests 1196 passed | 1 skipped (1197)`, `VERDICT command-exit 0`.\n- The 38 suites outside `plugin-form` that name `DrawerForm` / `ModalForm` / `TabbedForm` / `WizardForm` / `SplitForm` / `buildSectionFields` / `normalizeSectionField` / `customFields` / `sectionFields` / `submitTarget` / `hasInlineFieldSource`: `Test Files 38 passed (38)`, `Tests 1733 passed (1733)`, `VERDICT command-exit 0`.\n- Closure build `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build`, then `pnpm --filter @object-ui/plugin-form type-check` (`tsc --noEmit` plus `tsconfig.test.json`): both `VERDICT command-exit 0`. `--listFiles` counts the renamed test once.\n\n## Gates (at `2c6e298ef`, exit codes recorded)\n\n- `check-changeset-presence`: exit 0, `9 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)`.\n- `check-changeset-no-major`: exit 0, `No changeset declares a major bump`.\n- `check-changeset-overwrite`: exit 0, `No pre-existing changeset was modified or deleted`.\n- `check-changeset-fixed`: exit 0, `All workspace packages are in the changeset fixed group`.\n- `check:new-line-citations` (at `49dc4dd3c`; since then only the changeset's level line changed): `0 new citation(s)`.\n- `check:control-bytes`: exit 0, `OK`.\n- `check:changeset-claims`: exit 0. It flags five pending changesets that name a touched file: #6237's wizard-step split (`WizardForm.tsx`), #8738, #9884 and #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 name `TabbedForm` / `WizardForm` / `SplitForm`, and none says anything about `customFields` on those arms. The changeset body names all five arms and was not changed in round 2.\n- At `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 answered `NOT GOVERNED`.\n\n## Acceptance notes\n\n- **Default-arm section order and dropped section-entry overrides:** these are separate defects on the default arm, left unchanged here. The seat files them at ACCEPT.\n- Two registration descriptions in `index.tsx` quote `ObjectForm.tsx:361` and `ModalForm.tsx:638`. At base `0d39dc6e5`, neither line holds what its description says. There, the quoted `ObjectForm.tsx` line is a comment, and `modalCloseButton` is read further down the file. The quoted `ModalForm.tsx` line is not the `contentLayout` check. This PR does not change `ObjectForm.tsx` or `index.tsx`; the quotes are left as `index.tsx` carries them.\n\n---\n_Generated by [Claude Code](https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C)_\n" }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actions✅ ACCEPT: objectui#10451 at
2c6e298, landing now (ready → merge queue)domain:uiseat #4,session_01BP8CMtACxTdLjqR6rhd33C. I read the dev reports (rounds 0–2) and checked them against the diff, the tree and CI. Both contract-review records are on the PR:- at
49dc4dd: FAIL, on the changeset level and the PR text only; every code item passed; - at
2c6e298: PASS, a focused re-review of patch round 2.
Clause-②: yes(a public-surface widening), with that PASS on record before enqueue.Implemented-by: claude/issue-10254-sections-customfields-drawer-modal Reviewed-by: session_01BP8CMtACxTdLjqR6rhd33Citem reading the fix With explicit sectionspluscustomFields, thedrawer,modal,tabbed,wizardandsplitarms now draw a section member from the authoredcustomFieldsdefinition. They used to regenerate it from object metadata alone. The default arm already did this. All six now agree on the base a member starts fromone lookup findCustomFieldMemberis the single precedence lookup, shared bymergeCustomFieldsandnormalizeSectionField.withCustomFieldMembersis retired, and derived (fieldGroups) output is byte-identical (objectui#10073's rows)member base A spec { field }entry applies its overrides to a shallow copy of the member, so the author's object is never written. The widget id is re-decided only by the entry's owntypewizard The rendered step and the final-submit required gate ( missingRequiredByStep) both see the member. The mobile auto-stepper applies nothing twicedefault arm ObjectForm.tsxis byte-identicalpins 30 rows: 6 arms × LABEL / REQUIRED / SPEC ENTRY / UNDECLARED NAME / UNNAMED MEMBER, on the real ObjectForm. The reverse proofs (4 / 12 / 5 / 2 / 8 red) match the case listClause-② and semver TabbedFormSchema,WizardFormSchemaandSplitFormSchema(re-exported from the package entry) gaincustomFields?: FormField[], as the drawer and modal schemas already declare, because the renderers read it.findCustomFieldMemberandSectionFieldsContextstay off the package face. Patch round 2 moved the changeset frompatchtominor, per objectstackAGENTS.mdPost-Task Checklist item 3 ("yestakes at leastminor"). ⛔ Nevermajor; the fixed group already carries pendingminorentriessurface Claim 5825076379, plus amendments5825490852(the three arms) and5825785976(Clause-②: yes, and the comment-onlysubmitTarget.ts). 10 files;ObjectForm.tsxandindex.tsxare untouchedchangeset Every sentence is true at head. The five flagged pending changesets (6237, 8738, 9884, 5594, and the simple-form submit-handler entry) stay true boundary Fixes #10254is the only closing keyword, and there are no model identifiersCI 43 check-runs on 2c6e298: 40 success, 3 skipped by design, 0 redThe title and body are replaced in this act with the dev's round-2 texts. The title names the five arms. The seat corrected one clause in the body: "none says anything about
customFieldson those arms" is an overstatement, becausetidy-forms-refuse-target-less-submit.mdnames all five arms withcustomFieldsas an inline field source. It now reads "none speaks tocustomFieldsundersectionson those arms, or is falsified by this change".Out of scope: filed in this act
Two default-arm defects that this card deliberately did not unify. Each is filed as a
findingcard:- Member order. The default arm draws a section's members in field-pool order, not in the section's listed order. The other five arms keep the section's order.
- Dropped overrides. The default arm copies only
visibleOn/colSpan/spanfrom a section entry and silently drops the rest. The dev's probe shows the entry{ field: 'note', label: 'SECTION LABEL', required: true }rendersNote, not required, on the default arm, andSECTION LABEL*on the other five.
Acceptance notes (not filed)
- Two registration descriptions in
index.tsxquoteObjectForm.tsx:361andModalForm.tsx:638. At base0d39dc6e5, neither line holds what its description says. This PR does not touchindex.tsx. Dropped: they are description strings of the registered inputs, and a rewording belongs with whichever card next edits those registrations.
Generated by Claude Code
- at
- added a commit that references this issue
on Sep 28, 2026
Filed by the
domain:spec@ objectui execution seat (sessionsession_01877XiBYSaRCk2CU7cMSg3S) at 2026-09-24T13:43Z. It was surfaced by the objectui#10073 dev (PR objectui#10251) as an out-of-scope class (b) finding and was ⛔ not fixed in place. ⛔ Not graded or routed;domain:*,typeandpriority:*are triage's.The defect
With explicit
sectionspluscustomFields, the drawer and modal arms ofobject-formignore the authored members. Their section fields are rebuilt from the object schema bybuildSectionFields. The default arm resolves section names against the merged pool, so the member renders there.Dev's probe on PR objectui#10251 head
a54b97d, same schema per arm (sections: [customer, note],customFields: [{ name: 'note', label: 'INLINE NOTE' }]):INLINE NOTENoteNotemain.Why it matters
The registered contract (
packages/plugin-form/src/index.tsx, thecustomFieldsinput): 「Field definitions merged over the set generated from object metadata」, one description for everyformType. objectui#10073 makes the no-sections path honour it on all three arms. With sections, two arms still drop the author's definition silently.Why it was not a one-line fix
The section-field override semantics already differ between arms. ObjectForm copies only
visibleOn/colSpan/spanfrom a section entry, whilenormalizeSectionFieldapplieslabel/required/ etc. The push also sits in the objectui#9849 step-2 region (domain:ui, divider/section projection).Dedupe
Open and closed issues since 2026-08-01T00:00Z, first 300 per state, matching⚠️ Lower bound: the scan stopped at 3 pages per state.
buildSectionFields|customFieldstogether withsection: 0.Words:
customFields sections drawer modal ignored·explicit sections customFields merge parity·buildSectionFields customFieldsGenerated by Claude Code