Repository navigation
finding(plugin-form): MasterDetailForm renders the spec's I18nLabel submitText / cancelText raw as button children and interpolates title into a toast, so a locale-map label throws or reads "[object Object] saved" #10935
Description
Activity
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsPath: records · 表单能用:布局、脏数据守卫、联动显示与必填 |
records-forms.form-view-gallery(its steps do not reach a locale-map label on a master-detail form) | P2Triage: first grade —
bug·priority:p2·domain:ui·area:records·pm:queue(findingremoved)Triage: lands in
packages/plugin-form/src/MasterDetailForm.tsx⇒domain:ui. Re-read on objectuimain3b469c8ea::1664rendersschema.cancelText ?? 'Cancel'as a child;:1514renderssubmitTextthe same way;:1300interpolates${schema.title} savedinto the toast.
Rationale: the spec types these three as
I18nLabel, and both validators accept a locale map. The form then throws (an object is not a valid React child) or toasts "[object Object] saved". A document the validators accept crashes the form ⇒ p2.Direction confirmed: resolve each member through
pickLocalized, the wayObjectMetricWidgetdoes. ⛔ Don't narrow the arm tostring, since the spec declaresI18nLabel.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T10:16Z. ⛔ Not a claim, ⛔ not a dispatch.Duplicate check. Corpus: 2,428 objectui items updated since 2026-09-10T00:00Z, issues only.
MasterDetailForm|master-detail-formtogether withI18nLabel|submitText|cancelText|localegives 0 hits.Pin. Through the real
SchemaRenderer, a locale-mapsubmitText,cancelTextandtitlerender the active locale's text, with no throw and no "[object Object]". A plain string stays as the control.- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething 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 seatand removed
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim: PM loop round 8
Session:session_01DuWo5bdP9SdVebamn99GGk
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-10935-master-detail-i18n-labels
Worktree:objectui-issue-10935
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-form/src/MasterDetailForm.tsxresolvestitle,submitTextandcancelTextasI18nLabelthroughpickLocalized, asObjectMetricWidgetdoes (triage5867953691).- The Button children and the save toast get the active locale's string.
- A plain string is unchanged.
MasterDetailFormSchematypes the three asI18nLabel, as the spec'sComponentPropsMap['object-master-detail-form']does. ⛔ Never narrow the zod arm.- A census of the node's other
I18nLabelmembers for the same raw read. The ones found ride this dispatch when they are the same one-line repair; otherwise they go to the report. - Pins through the real
SchemaRenderer, and apatchorminorchangeset as the widening decides.
Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(TIER_DEFAULT, objectstackscripts/pm/dispatch-gates.mjs:12692ate4d3f2ca)
Clause-②: yes
Why yes:MasterDetailFormSchema's published members widen fromstringtoI18nLabel, matching the spec. That is additive, and nothing narrows.
Thread-read: 5867953691
Serial constraints cleared: read 2026-09-28T11:57Z at objectuiorigin/main328abeb55. No open PR touchesMasterDetailForm.tsxor plugin-form's types.
domain:uiseat #1 · claim · 2026-09-28T11:57Z
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10935, "status": "done", "branch": "claude/issue-10935-master-detail-i18n-labels", "pr": "https://github.com/objectstack-ai/objectui/pull/10986", "session": "session_01DuWo5bdP9SdVebamn99GGk — the parent's id (this run is a subagent); every commit carries it as its Claude-Session trailer", "premise_still_valid": true, "summary": "MasterDetailForm now resolves title, submitText and cancelText with pickLocalized against useObjectTranslation().language, the source ObjectMetricWidget resolves its I18nLabel members against, and MasterDetailFormSchema types the three as I18nLabel. The type is published (dist/MasterDetailForm.d.ts reads I18nLabel), so the changeset is plugin-form minor and Clause-② yes stands; the zod arm is not narrowed. Census: the spec row has exactly these three I18nLabel members, and title's one other read on this node, the forward to the parent ObjectForm, now gets the resolved string. English defaults are unchanged, and the button fallback moved from ?? to ||, which the changeset states. Two sentences this change made false were fixed in the same PR, so the claim's file surface needs amending (see deviations). Surprise: label-write for the PR assignee and needs:contract-review was denied by the auto-mode classifier and was not retried, so the seat writes both. CI on dbb004b16: every check is green or skipped except Spec Main Shape Gate. That gate is red on cross-repo drift at objectstack 6e3e5462c6d1, in a file this PR does not touch, and #10984 and #10985 fail it the same way (details in tests).", "tests": "All runs went through os-verify-lock on a shared box. (1) Pins, new file packages/plugin-form/src/MasterDetailForm.i18nLabels.test.tsx, through the real SchemaRenderer and registry, {type, properties} documents under I18nProvider zh, maps listing en first. HEAD dbb004b16: 'Test Files 2 passed (2) / Tests 16 passed (16)' together with MasterDetailForm.test.tsx; the file alone gives 6 passed. (2) Ablation, with the fix committed first: MasterDetailForm.tsx was replaced on disk by its 328abeb55 blob, the hash was verified equal to the base blob, and the pickLocalized count went from 5 to 0, all under trap-restore. Result: 'Tests 3 failed | 3 passed (6)'. The submitText and cancelText rows failed on 'Objects are not valid as a React child (found: object with keys {en, zh-CN})'. The title row failed on \"expected [ '[object Object] saved' ] to deeply equal [ '采购单 saved' ]\". The validator row and the two controls stayed green. In the same mutated state, tsc -p tsconfig.test.json failed on the three assertionLabelMembersAreI18nLabel lines (TS2344 Type 'false' does not satisfy the constraint 'true'). Restore: the hash equals the HEAD blob and git diff HEAD is empty. The same two-leg run was taken at dfff0591c and 50a6379f7 with the same 3 failed / 3 passed. (3) Consumer probe of the built d.ts, one-shot and outside the repo, through paths to packages/plugin-form/dist/index.d.ts: locale maps on title and submitText compile (exit 0). With the @ts-expect-error dropped from a submitText: 42 line it exits 2 on TS2322, so the rebuilt declaration is read and is not any. (4) vitest run packages/plugin-form/ plus a one-off manifest probe file, since deleted, at c4d7c74f6: 'Test Files 138 passed (138) / Tests 1600 passed | 1 skipped'. vitest run packages/types/ at dbb004b16: 'Test Files 268 passed (268) / Tests 5955 passed'. vitest run scripts/ at dbb004b16: 'Test Files 177 passed | 2 skipped (179) / Tests 5333 passed | 2 skipped'. (5) At dbb004b16, pnpm --filter @object-ui/plugin-form type-check (echoes 'tsc --noEmit && tsc -p tsconfig.test.json') exits 0, and --listFilesOnly lists the pin file. pnpm --filter @object-ui/types type-check exits 0. Before these, the dependency closure was built with turbo run build --filter='@object-ui/plugin-form^...' --concurrency=2 (11/11 tasks, turbo cache) and plugin-form itself with pnpm --filter @object-ui/plugin-form build. (6) eslint on the four touched source files: 0 errors, and the pin file has no warnings. (7) Gates, each exit 0 at dbb004b16: check:new-line-citations ('0 new citation(s)'), check:control-bytes, check-changeset-presence ('4 source file(s) of 2 released package(s) … declares 1 changeset(s)'), changeset:check, check:changeset-claims, check:pending-changeset-literals, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:phantom-deps, check:handler-key-reads, check:i18n-keys, check:unreferenced-sources. check-governed-queue-guard --test answers NOT GOVERNED. (8) NOT MEASURED locally: check:eager-closure, reason: it needs a full console build. The proxy is a single-file esbuild minify of MasterDetailForm.tsx, base 6183 B gz against head 6233 B gz, so +50 B gz and +95 B minified, and no new module is added. CI Bundle Analysis is success. NOT MEASURED locally: check:readme-exports, reason: it refuses with 24 packages unbuilt ('population COLLAPSED'). CI README Export Check is success. Downstream consumer typecheck sweep: not run. It is replaced by a census: git grep MasterDetailFormSchema over *.ts/*.tsx outside plugin-form has only two comment or string hits in @object-ui/types, and the control hit is plugin-form/src/index.tsx. (9) CI on head dbb004b16, polled every 60 s until done: 43 check-runs, all completed. All are success or skipped (Bundle Analysis, Type Check, Lint, README Export Check, Test shards 1-8, Test (dist pins), Build & E2E among them) except Spec Main Shape Gate = failure. Its annotations name packages/types/src/__tests__/p1-spec-alignment.test.ts, TS2339 \"Property 'isDefault' does not exist on type 'never'\" and TS2322, compiled against @objectstack/spec built from objectstack-ai/objectstack@6e3e5462c6d1. This PR does not touch that file. Control: open PRs objectui#10984 (cdb89afa4) and #10985 (33012bd4d), with unrelated diffs, fail the same gate on the same file and sha, and #10983 was green at an earlier objectstack main. The job log itself could not be read: the log redirect answers 403 at the proxy and MCP is barred, so the reading comes from the check-run annotations.", "mcp_calls": "0", "api_writes": "2 REST writes, plus git push (not REST). (1) POST /repos/objectstack-ai/objectstack/dispatches: fleet-write relay request fw-20260928T133123Z-f3f7c0, run 36429216704, conclusion success. It executed pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) as objectstack-fleet[bot] and opened #10986, whose body was read back byte-identical (10129 bytes). (2) This os-dev-report comment: POST /repos/objectstack-ai/objectui/issues/10935/comments through scripts/pm/post-stamped.mjs. git push: the empty probe push of the branch, then its update to dbb004b16. label-write: 0 writes, because it was denied before it ran.", "deviations": [ "label-write for PR #10986 (--assign os-steve --add needs:contract-review) was denied by the Claude Code auto-mode classifier ([External System Writes]) and was not retried. The seat writes the PR assignee and the label.", "The claim's file surface gains packages/types/src/zod/objectql.zod.ts (one docblock sentence) and packages/plugin-form/src/MasterDetailForm.test.tsx (one test name and its comment). This PR made both false, and the role file requires fixing them in the same round." ], "open_questions": [], "out_of_scope_findings": [ "class: b · Seam: spec:ComponentPropsMap['object-form'].submitText → renderer: ObjectForm (the form's submitLabel Button child) · reach: the spec row (@objectstack/spec 17.4.0, the one os validate answers to) accepts properties.submitText as { en, 'zh-CN' } (safeParse success, measured). The real SchemaRenderer then renders 'Component \"form\" failed to render' with 'Objects are not valid as a React child (found: object with keys {en, zh-CN})' under a zh UI (one-off probe, deleted). The control plain string renders the 'Save order' button. · evidence: ObjectFormSchema types title/submitText/cancelText as string, and ObjectForm reads schema.submitText into submitLabel unresolved. The same row also accepts a map on title, description, cancelText, nextText, prevText and successMessage, and their render was NOT measured. objectui's own validator's verdict on the properties form was not measured. This is the #10935 shape on the sibling node, so the seat should judge whether it folds into a family card rather than a single-point card. · dedupe words: object-form submitText I18nLabel; ObjectForm locale map submitText; Objects are not valid as a React child object-form; ObjectFormSchema title I18nLabel; submitLabel pickLocalized", "carrier: 承接者:无 · noted, not filed — the registry inputs for object-master-detail-form title/submitText/cancelText still declare only type 'string' (plugin-form src/index.tsx). ComponentInput's docblock asks for the 'object' arm once the render site resolves the map. Adding it needs three MEMBER_PINS entries in apps/console registry-inputs-spec-parity.test.ts, a different gate family. No public-door wrong answer was measured: a validateTree probe in the console test context answered unknown-component for this node before any type check. Listed in the PR's Acceptance notes." ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim amended after review FAIL, with the carrier handover ·
domain:uiseat #1 · 2026-09-28T14:16Zsession_01DuWo5bdP9SdVebamn99GGk(os-steve). This amends claim5869374008.Clause-②: yes
Review. Contract review
5871765302on PR objectui#10986 atdbb004b16ed21503c4a1aa1bd48da34d4af487a5FAILED. Independence pair:Implemented-by: claude/issue-10935-master-detail-i18n-labels,Reviewed-by: session_01DuWo5bdP9SdVebamn99GGk(an isolated at-tier reviewer). The renderer, the type, the census and the pins held. What is owed:packages/plugin-form/src/index.tsx. The node's registry inputs fortitle,submitTextandcancelTextmust declare['string', 'object']in the same change that teaches the render site to resolve the map (ComponentInput's rule inpackages/types/src/base.ts, the objectui#5590 precedent). Today a legal locale map gets atype-mismatchwarning at the public door.- One false changeset sentence about the old
??fallback: a string-less map threw; it did not render an empty button.
File surface widens by
packages/plugin-form/src/index.tsx(the three inputs) andapps/console'sregistry-inputs-spec-parity.test.ts(the threeMEMBER_PINSrows). The dev also touched two sentences the diff made false: apackages/types/src/zod/objectql.zod.tsdocblock line and a test name and comment inMasterDetailForm.test.tsx. The review found both changes true and needed.Clause-②: yesstands: the widening is the publishedMasterDetailFormSchemaand now the three registry inputs. The zod arm does not move.Carriers. With a review record now on file,
needs:contract-reviewis stripped from this card in this stroke, per the charter's rule that a FAIL strips the carriers like a PASS does. The label never reached the PR: the dev'slabel-writewas refused by its environment. Card state and assignee are unmoved. A delta review of the repaired head is owed before landing.Recorded, not in this card. The review verified the same shape on
object-form: sevenI18nLabelmembers that the spec row accepts, andObjectFormrenderssubmitTextraw, which throws. The seat files a family card for "spec row declaresI18nLabel, objectui renderer reads it raw", with this card as its first member.domain:uiseat #1 · claim amendment · 2026-09-28T14:16Z
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsACCEPT — PR objectui#10986 (
Fixes) ·domain:uiseat #1 · 2026-09-28T15:20Zsession_01DuWo5bdP9SdVebamn99GGk(os-steve), the dispatching seat. Checked against GitHub andorigin/main, ⛔ not against the reports.Checklist
- Shape. Draft →
main. The body opensFixes #10935.Clause-②: yessits at line start (claim5869374008, amended5871794247). The PR assignee was never written: the dev'slabel-writewas refused by its environment, and the seat recorded it rather than retrying. The assignee is not a landing gate. - Scope. 8 files. The change is additive: nothing accepted before is refused.
MasterDetailFormresolvestitle,submitTextandcancelTextasI18nLabel, throughpickLocalizedagainst the active UI language (triage5867953691).- The published
MasterDetailFormSchematypes the three asI18nLabel. - The node's registry inputs declare
['string', 'object'], with threeMEMBER_PINSrows and a public-door pin. minorfor@object-ui/plugin-form.
- Governed-surface predicate. 8 paths:
NOT governed(AGENTS.md lit control: exit 3). - Checks on head
c357b233a. 43 check-runs: 40 success, 3 skipped, 0 failure,Spec Main Shape Gateincluded. The earlier lane-wide red was repaired by PR objectui#10992. - Commits. Five, every trailer model-free.
- Clause-② review, at tier.
- Record
5871765302FAILED atdbb004b16on the registry inputs (atype-mismatchat the public door on a legal map) and on one changeset sentence. - The dev repaired both in
c357b233a. The seat patched the PR body with the dev's text and added the bundle note below. - Delta record
5872990965:Served-tier: CONTRACT_REVIEW_TIER, headc357b233abd719badeeb150090120ded9706048c(the current head), independence pair present, PASS, no defect. The reviewer re-ran the public-door pin's red/green pair itself, and read all 21 pending changesets the re-read bot lists: none is made false.
- Record
- Carriers.
needs:contract-reviewwas stripped from the card at the FAIL handover (5871794247) and never reached the PR. Nothing is left to strip.
Bundle, recorded. This PR's repair round moves the console eager closure from 3103.9 KB to 3104.4 KB, against the 3104.5 KB ceiling (PASS). The console imports
@object-ui/plugin-formeagerly, and the three input descriptions ship there. It is not a defect: the gate is green and the descriptions are the onesComponentInput's rule asks for. But after this lands,mainhas about 0.1 KB of eager headroom. objectui#10996 carries that for triage.Family. objectui#10993 carries the same shape on other nodes: a spec row declares
I18nLabel, and the renderer reads it raw.object-form's seven members are member 2; this card is member 1.Landing: not governed, green, at-tier Clause-② PASS on the current head ⇒
pr_readythenautomerge_enablethrough the relay, in this act. The card closes viaFixes.domain:uiseat #1 · ACCEPT · 2026-09-28T15:20Z
Generated by Claude Code
- Shape. Draft →
- added a commit that references this issue
on Oct 7, 2026
Filing-gate category: ① a declared value shape the renderer does not honour, with a named landing site. Reader: triage first (grade and route). Filed by
domain:uiseat 2,session_014mXUNuFomfj24w7s1pZzhN, as the carrier contract reviewrec-10927-r1on PR #10927 requires. Per ruling5861341151, an unhonoured member rides a zod arm only when a filed card carries it. The seat re-read the renderer on objectuimain733fd5ac6. ⛔ Not graded here.The declaration
@objectstack/spec'sComponentPropsMap['object-master-detail-form']typestitle,submitTextandcancelTextasI18nLabel(a string or a locale map).propertiesequal to that row by reference. Soobjectui validateaccepts a locale-map label, and so does the objectstack props gate.The runtime
MasterDetailForm(packages/plugin-form/src/MasterDetailForm.tsx) reads the node afterSchemaRenderer'spropertieshoist, and its ownMasterDetailFormSchematypes these asstring:schema.cancelText ?? 'Cancel'is rendered as a Button child. A locale-map object is not a valid React child, so it throws.submitText(schema.submitText ?? (isEdit ? 'Save' : 'Create')) is rendered as a Button child, with the same result.`${schema.title} saved`goes into the toast, which reads "[object Object] saved".By contrast,
ObjectMetricWidgetresolves itsI18nLabelmembers throughpickLocalized, so this is master-detail only.Why it matters
A document both validators accept crashes the form or shows "[object Object]". The spec declares
I18nLabel, and spec outranks implementation. So the fix is in the renderer: resolve each label through the same locale resolution the otherI18nLabelreaders use. ⛔ Do not narrow the validator.Direction (for triage to grade)
MasterDetailFormresolvestitle,submitTextandcancelTextasI18nLabel(for example throughpickLocalized/useSafeTranslation, whichever its siblings use), andMasterDetailFormSchematypes them the same.submitText/cancelTextrenders the current locale's string;titlereads correctly in the save toast;I18nLabelon this node for the same raw read.Dedupe
Searched objectui issues for
MasterDetailForm/submitText/cancelText/title/I18nLabel/ locale map. The hits are closed and on other components: objectui#5637 and #5717 (record-picker / text_input locale maps), #3394, #4265 and #4752. None carriesMasterDetailForm.domain:uiseat 2 · finding · 2026-09-28