Repository navigation
fix(plugin-form): MasterDetailForm resolves its I18nLabel title, submitText and cancelText (objectui#10935) - #10986
Conversation
…itText and cancelText (objectui#10935) `@objectstack/spec` types the three as `I18nLabel` in `ComponentPropsMap['object-master-detail-form']`, and the validator arms the node with that row, so a per-locale map is an accepted document. The form read them raw: a map as a Button child threw, and a map `title` toasted "[object Object] saved". Each now goes through `pickLocalized` against `useObjectTranslation().language`, as `ObjectMetricWidget` does, and `MasterDetailFormSchema` types them `I18nLabel`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
… members (objectui#10935) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…g to any (objectui#10935) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
… two sentences the widening made false (objectui#10935) - `MasterDetailForm.i18nLabels.test.tsx` gains a compile-time pin (`assertionLabelMembersAreI18nLabel`) that the three members equal the row's `I18nLabel`. - `ObjectMasterDetailFormBlockSchema`'s docblock said `MasterDetailFormSchema` types the three as a plain string; it now says `I18nLabel` and names the pin. - The existing host-string `cancelText` test no longer calls the plugin locale-agnostic: it resolves per-locale maps now. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
|
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
|
Contract reviewServed-tier: Card objectui#10935 · PR objectui#10986 · branch fetched as ① Derived judgmentsRenderer. Type. Census. I enumerated the installed row at runtime ( Extra files. Pins. At head, CI. 43 check-runs on the head, all completed: 40 success, 3 skipped (dependabot and the two coverage rows), 1 failure, Commits. Four, ② Semver level
Changeset sentences: all true but one. "On the two buttons, an authored empty string, or a map with no string entry, now shows the English default. The old ③ Boundary flagsClause-②: yes is honest and complete: the widening is the published Registry inputs (defect 1). The dev's note is true: the Out-of-scope finding, class b: verified. The installed PR body. No line addresses, no angle brackets. Two false sentences: the "instead of an empty button" claim for the string-less map (as in the changeset), and the "no public-door reading of a wrong answer" conclusion (above). Defects, each with its fix.
Implemented-by: VERDICT: FAIL Generated by Claude Code |
…l inputs, and correct the changeset's empty-button sentence (objectui#10935) Contract review 5871765302 on PR objectui#10986 found two defects. - The `object-master-detail-form` registration still declared `title`, `submitText` and `cancelText` as `'string'` while this PR teaches the render site to resolve the `I18nLabel` map. Through the public door (`getPublicConfigs()` -> `manifestFromConfigs` -> `validateTree`) a legal map drew `type-mismatch`. The three now declare `['string', 'object']`, with descriptions teaching the map, as `ComponentInput.type` prescribes. The console parity gate gets the three `MEMBER_PINS` rows it requires, and `masterDetailFormI18nLabelManifest.test.ts` pins the public door: a map is not reported, a string is not reported, a number still is. - The changeset said the old `??` fallback rendered an empty button for an empty string AND a string-less map. The map half is false: it threw as a React child. The sentence now says so, and the changeset records the arm widening. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
✅ 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: Delta over record 5871765302 (FAIL at ① Derived judgmentsDefect 1 (registry inputs): repaired. Defect 2 (changeset sentence): repaired. The replaced sentence, "An authored empty string used to render an empty button; a map with no string entry used to throw as a React child", is true: under Commit and CI. The one new commit carries no model name (its trailers are the co-author line and the session link). CI on Bundle, a fact the PR body does not carry. The console budget comment on this head reads eager closure 3104.4 KB against the 3104.5 KB budget, PASS; on ② Semver level
③ Boundary flagsPR body amendments, each checked against the head. (a) The opening now names head Clause-②: yes remains honest and complete: the interface widening and the inputs widening are both named in the changeset. The 21 pending changesets the re-read bot lists (comment 5870894969). This PR's diff edits
Beyond the bot's list, the pending entries that discuss Out-of-scope items from 5871765302 (the Implemented-by: VERDICT: PASS Generated by Claude Code |
…osure Brings in PR #10986 (objectui#10935), which touched zod/objectql.zod.ts away from the `exportOptions` note this branch edits. Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm Co-authored-by: Claude <noreply@anthropic.com>
…e parser tier answers (objectui#10981) (objectstack-ai#11006) Fixes objectstack-ai#10981 Clause-②: no — message, `.describe()` and docblock text only; every accept or refuse verdict stays byte-identical. Implemented by the `os-dev` agent dispatched from the `domain:spec @ objectui` seat, session `https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm` (`mode:subagent`). Base `4b742f41d`. PR objectstack-ai#10986 landed on `zod/objectql.zod.ts` meanwhile, so `main` `665025908` was merged in before the final push. Head `ebd156ef5`. ## What changed Only the false clause changes, on four sites. Each clause names the code the parser tier answers for that key and value shape, as measured on the live registry (next section). | site | face | before | after | |---|---|---|---| | `zod/form.zod.ts`, the `FormSchema.mode` refusal (objectui#10286) | zod message and `.describe()`, one string through `retirementTombstone` | "rendered the same form — no error, no warning" | "rendered the same form — no render-time error or warning; only the parser tier's `unknown-prop` warning noticed it" | | `data-display.ts`, the `TimelineSchema.events` docblock (objectui#6170) | TS face, emitted `.d.ts` | "drew an EMPTY rail, with no error and no warning" | "drew an EMPTY rail, with no render-time error or warning; only the parser tier's `unknown-prop` warning noticed it" | | `base.ts`, the `data-table` sentence in the `BaseSchema.bind` docblock | TS face, emitted `.d.ts` | "renders its header over an empty body, with no error and no warning" | "renders its header over an empty body, with no render-time error; nothing on the page says why, but a render-time console warning (`[ObjectUI] DataTable bind:`, objectui#6575) and the parser tier's `unknown-prop` warning both name it" | | `zod/objectql.zod.ts`, the note on `OBJECT_GRID_EXPORT_OPTIONS_GUIDANCE` (objectui#7762) | source note on a non-exported constant; it ships in the emitted JS, in no `.d.ts` | "no error, no warning, no console line" | "no render-time error, warning or console line (only the parser tier's `type-mismatch` warning noticed it)" | - **Why `base.ts` has its own wording.** That site was not silent at render either. `data-table` has logged `[ObjectUI] DataTable bind:` for an authored `bind` since objectui#6575 (`aff3d7aef`, 2026-08-28). The docblock was written a day later (`99a3c2db6`, 2026-08-29). `merge-base --is-ancestor aff3d7a 99a3c2d` answers 0, with the control leg `94d4876df` also answering 0. So "only the parser tier noticed it" would be false there too. - **Why `exportOptions` names `type-mismatch`.** The `object-grid` registration declares `exportOptions` as `type: 'object'`. So `checkType` answers an array, not the undeclared-key branch. - **New pin:** `packages/types/src/__tests__/silence-clause-parser-tier-closure-10981.test.ts`. - **Optional item, taken:** `packages/components/src/renderers/__tests__/header-bar-unknown-prop-parser-tier-10981.test.tsx`, a new file beside `container-declaration-ratchet`. - **Changeset:** `.changeset/10981-silence-clause-parser-tier-closure.md`, `@object-ui/types: patch`. ## The parser tier, measured before each clause was written **Instrument.** The same as PR objectstack-ai#10980: a one-shot probe, deleted and never committed, under `packages/components/src/renderers/__tests__/`. - It imported the components renderers, `fields` and 18 of the 19 `plugin-*` packages (all but `plugin-designer`). `plugin-tree` has no vitest alias, so it came in by relative source path. - The manifest was built from every KNOWN registry key through `manifestFromConfigs`, the way `container-declaration-ratchet.test.tsx` builds it. - Each leg ran `validateTree` on a control and on the control plus the one key, and reports only the diagnostics that key added. - Each leg also rendered through the real `SchemaRenderer`, inside `PredicateScopeProvider` and `SchemaRendererProvider` with a stub data source, all inside `act`. `console.warn` and `console.error` were spied, and the HTML was compared with the control's. | site | keys | value shapes | the parser tier adds | render leg | |---|---|---|---|---| | `FormSchema.mode` | `form`, `ui:form`. Bare `form` resolves to `ui:form`; `view:form` is `skipFallback` | `edit`, `read`, `disabled`, `create`, `view` (the objectui#10286 pin's set) | exactly one `unknown-prop` on `mode`, 10 of 10 legs | 0 warnings, 0 errors on every variant. HTML equals the control's except for one `mode` attribute on the form element | | `TimelineSchema.events` | `timeline` (resolves to `view:timeline`), `view:timeline`, `plugin-timeline:timeline` (the `TimelineRenderer` registration `TimelineSchema` describes) | 2 events; `[]`; `items` plus `events` | exactly one `unknown-prop` on `events`, 7 of 7 | byte-identical HTML, 0 warnings, 0 errors | | `BaseSchema.bind` on `data-table` | `data-table`, `ui:data-table` | `bind: 'users'` and `bind: 'app.settings.users'`, each with `data` absent and with `data: []` | exactly one `unknown-prop` on `bind`, 8 of 8 | byte-identical HTML (the "No results" body in both), 0 errors, and exactly 1 console warning on every variant: `[ObjectUI] DataTable bind: … is ignored` | | `exportOptions` | `object-grid`, `plugin-grid:object-grid` | `['csv', 'xlsx']`, `['csv']`, `[]` | exactly one `type-mismatch` ("expected an object"), 6 of 6 | the control is `{ formats: ['csv', 'json'] }`, the default an array loses to. Byte-identical HTML, the export control and the rows present in both, 0 warnings, 0 errors | **Controls.** - `form` with an invented key draws `unknown-prop`. `view:form` with `mode: 'edit'` draws nothing, because that registration declares `mode`. - On `object-grid`: an `exportOptions` object draws nothing, a string draws `type-mismatch`, and an invented key draws `unknown-prop`. So the probe tells the two codes apart on one node. - `accordion` with a `body` list draws `not-a-container`, the third code. - With `data` absent, the `data-table` control already carries `missing-required-prop` on `data`; the table above counts only what `bind` adds. - The first control render in the file logged one `react-i18next` initialisation warning. No variant did. **The same answer before each retirement (source reading, not executed).** The `mode`, `events` and `exportOptions` sentences are past tense. - `FormSchema.mode`, at `a05c35063^`, the parent of the objectui#10286 commit: the `ui:form` registration declared the same 13 inputs as today, none named `mode`. `plugin-form`'s `form` was `namespace: 'view', skipFallback: true`. `validate.ts` had the same base-prop set, without `mode`, and one `unknown-prop` branch. - `TimelineSchema.events`, at `195052fff^`, the parent of the objectui#6170 tombstone commit: neither `plugin-timeline:timeline` nor `view:timeline` declared `events`. A `git log -G` for an `events` input name over `packages` finds no registration that ever declared one, back to the shallow boundary (2026-07-20). - `exportOptions`, at `f08d1a86a^`, the parent of the objectui#7762 refusal: `GRID_QUERY_INPUTS` declared `exportOptions` as `type: 'object'`. It had done so since `375efb402` (2026-08-17), and objectui#7762 was filed on 2026-09-05. `checkType` gave a single non-enum arm `type-mismatch`, as today. - `BaseSchema.bind` is present tense, measured today. - The history reads ran on a deepened checkout (`git fetch --deepen=4000`, shallow boundary 2026-07-20). ## Census over joined text (the closure nail) **Instrument.** A one-shot script. It enumerates the non-test `.ts` files of `packages/types/src` from the ref with `git ls-tree` and reads each one from the ref. It joins JSDoc and line-comment continuations and `'…' + '…'` concatenations, and unescapes quotes. Then it counts: - A: "no (adjective) error" and "no (adjective) warning" within 60 characters, with no sentence break; - B: the same, in reverse order; - C: "no (adjective) error or / nor / and (adjective) warning", the family's corrected shape; - D: "without (an) error or / and warning"; - E: "neither error nor warning"; - F: "silent…" within 80 characters of a negated warning, in either order; - G, the wide net: any negated warning ("no warning", "without a warning", "never warns", …); - H, the wider net over other nouns: "no diagnostic", "no console line", "without complaint", "not a warning". | reading | base `4b742f41d` | main `665025908` | head `ebd156ef5` | |---|---|---|---| | files enumerated | 67 | 67 | 67 | | A | 4 | 4 | 0 | | B, D, E | 0 | 0 | 0 | | C, and how many are followed by "the parser tier's" | 302, 300 | 302, 300 | 302, 302 | | F | 1 | 1 | 0 | | G | 6 | 6 | 2 | | H | 25 | 25 | 24 | | control: the objectui#10928 clause | 282 | 282 | 282 | | control: the objectui#10959 tail | 18 | 18 | 20 | - **Positive control.** C fires on every one of the 300 known objectui#10928 / objectui#10959 control strings. A fires on exactly the four sites the card names, which were found independently. - **Every non-control hit at base, read.** - A is the four sites and nothing else. The two C hits without the parser tier's clause are two of the four (`base.ts`, `data-display.ts`). F is the fourth site again. - G adds two hits that say "no warning window" (`objectql.ts`, `zod/objectql.zod.ts`, objectui#6475). That is a deprecation window, not a silence claim, so they stay. - H is outside the card's phrasing family. It is listed under Acceptance notes and not changed. ## Every other byte, and only comment and string lines - Substitute the old clause back for the new one on joined text: each head file equals its base file, 4 of 4, against `4b742f41d` and again against `665025908`. - The first four ablation legs put the old clause back with the anchor tool. Each mutated blob equals the base blob byte for byte: `eba1c495f`, `4dfce4ac6`, `b1abfe909` and `e2c561734` are the base blobs of the four files. - `git diff -U0` over the four files: every changed line is a ` * ` JSDoc line or a `+ '…'` string-literal line. - The objectui#10286, objectui#6170, objectui#7762 and `bind` pins pass unchanged; they are in the full `types` run. - **Emitted `dist`**, after `pnpm --filter @object-ui/types build` at head: - the old clauses, both spellings: 0 files; - the `mode` clause: `zod/form.zod.js`, `zod/index.zod.js`; - the `events` clause: `data-display.d.ts`; the `bind` clause: `base.d.ts`; - the `exportOptions` note: `zod/objectql.zod.js`, `zod/index.zod.js` (the emitted JS keeps comments). ## Reverse verification (ablation), after the fix was committed **Instrument.** objectstack's `scripts/ablation-replace.mjs` in WRAP mode, run from this worktree. - The anchor must hit exactly once and the on-disk blob must move. The tool proves each restore: blob equals HEAD and `git diff HEAD` is empty. - The driver re-proved each restore independently, and ran inside an EXIT / INT / TERM trap. - The types pin imports `../zod/*` source and reads the files from disk. The components pin resolves the registry from source through the vitest aliases. So no build sits between a mutation and a run. | leg | red | red tests | |---|---|---| | the old clause back into `FormSchema.mode` | 3 / 9 | the `mode` arm, the spelling walk, the pairs walk | | the old clause back into `TimelineSchema.events` | 3 / 9 | the `events` docblock, the pairs walk, the parser-tier walk | | the old clause back into `BaseSchema.bind` | 3 / 9 | the `bind` docblock, both walks | | the old clause back into the `exportOptions` note | 2 / 9 | the note, the pairs walk | | the note names `unknown-prop` in place of `type-mismatch` | 1 / 9 | the note | | `zod/ai.zod.ts`, which the pin does not name: an objectui#10928 clause loses its parser-tier half | 1 / 9 | the parser-tier walk | | `header-bar.tsx` starts declaring `title` (components pin) | 4 / 6 | both population legs, both lit controls | - Every leg restored to its HEAD blob (`3a595a747`, `33e3a4770`, `a12a28cf1`, `4e75a80cf` twice, `a40e67ff6`, `ce3850854`) with an empty `git diff HEAD`. After the run, `git status --porcelain` was empty. - **The first `header-bar` attempt was a no-op.** Its replacement contained the anchor, so the tool refused (anchor count 1 to 1) and ran no test. It was re-run with a replacement that does not contain the anchor. The table shows the second run. ## The pins **`silence-clause-parser-tier-closure-10981.test.ts`.** It follows the house style of PR objectstack-ai#10956 and PR objectstack-ai#10980. `@object-ui/types` imports no workspace package, so it holds the prose. - **Zod face.** `FormSchema.mode` is read through its refusal: code `invalid_type` and path `['mode']` first, then the clause. The walk covers every spelling of the objectui#10286 pin, and the `.describe()` text. - **`exportOptions`.** The bare array is refused with `invalid_type` at `['exportOptions']`, with exactly the `.describe()` string. Then the note above `OBJECT_GRID_EXPORT_OPTIONS_GUIDANCE` must carry the clause and name no `unknown-prop`. - **TS face.** The `TimelineSchema.events` and `BaseSchema.bind` docblocks. - **The closure walk.** It reads every non-test `.ts` file of `packages/types/src` from disk, with the root taken from the pin's own `import.meta.url`. - No joined text may pair "no error" with "no warning" (patterns A, B, D, E). - Every C phrasing must have "the parser tier's" within 80 characters after it. - Non-vacuity, by name: the four site files are reached, and both earlier waves' clauses are found. **`header-bar-unknown-prop-parser-tier-10981.test.tsx`.** It answers the open question from PR objectstack-ai#10956 and PR objectstack-ai#10980, on the live components registry. - On `header-bar` and `ui:header-bar`, each of the 9 refused keys draws exactly one `unknown-prop` naming the key. This holds in every value shape the objectui#10387 and objectui#10286 pins refuse. - The control draws nothing. - The lit control shows `crumbs` declared, and no refused key declared. ## Dispatch assumptions, checked 1. **Holds.** There are exactly four sites, one per file, on `4b742f41d` and on `665025908`. The census was widened to patterns A–H above, with controls. 2. **Holds, and made more exact.** - `form` with `mode` draws `unknown-prop`: for all five spellings, on `form` and `ui:form`. - A bare `timeline` with `events` draws `unknown-prop`. The bare key resolves to `view:timeline`. The docblock describes `TimelineRenderer`, which is registered as `plugin-timeline:timeline` (`skipFallback`). That registration also answers `unknown-prop`, so the clause is true for the node the docblock names and for the bare key. - `data-table` with `bind` draws `unknown-prop`. But "only the parser tier noticed it" would be false there, because a render-time console warning fires too (objectui#6575). The site names both. - `object-grid` with an `exportOptions` array draws `type-mismatch`: for three array shapes, on both keys. 3. **Holds**, by source reading at the parent of each retirement. See above. 4. **Changed during the run.** PR objectstack-ai#10986 landed (`8aa68b159`) on `zod/objectql.zod.ts`. `main` `665025908` was merged in before the final push, with no conflict. The note edited here is away from that hunk. PR objectstack-ai#10997 had not landed at `665025908`. ## Gates, head `ebd156ef5` All ran after the merge commit. Exit codes were captured before any pipe. - `pnpm --filter @object-ui/types build` → 0. Its `check-dist-completeness` verified 134 emitted files. - `pnpm --filter @object-ui/types type-check` → 0. `tsc -p tsconfig.test.json --listFiles` includes the new pin. - `pnpm --filter @object-ui/types lint` → 0, with 0 errors. The 295 warnings are existing `no-explicit-any` ones; none is in the new file. - `pnpm exec vitest run packages/types/` → 0: 272 files and 5993 tests passed. - Outside `types` → 0: 27 files and 931 tests passed. - The script tests that read `packages/types/src`: `body-dialect-producer-scan`, `changeset-polarity-census`, `check-action-forward-parity`, `check-changeset-claims`, `check-component-surface-parity`, `check-doc-component-types`, `check-doc-example-types`, `check-handler-key-read-sites`, `check-readme-exports`, `check-spec-symbol-derivation`, `component-node-vocabulary-7434`, `one-authority-per-exported-name-6273`, `check-pending-changeset-literals`, `spec-symbol-ledger-types-7265`, `unconsumed-widget-option-claim-6186`, `vite-types-zod-lazy`; - `component-fixture-declared-keys` in `examples/schema-catalog`; - the new components pin, `container-declaration-ratchet`, the four `header-bar` component tests, `skill-guide-data-table-binding` and `data-table-bind-diagnostic`; - the parser pins `body-dialect-6771` and `containment-declared-slot-9910`. - `pnpm --filter '@object-ui/components^...' build`, then `pnpm --filter @object-ui/components type-check` → 0. `tsconfig.test.json --listFiles` includes the new components pin. `eslint` over the two new test files → 0, with no findings. - `check-changeset-presence`, `check-changeset-no-major`, `check-changeset-fixed`, `check-changeset-overwrite` → 0. `check-changeset-claims` → 0; it is report-only. - `pnpm check:control-bytes`, `check:new-line-citations` (0 new), `check:pending-changeset-literals`, `check:test-path-roots`, `check:phantom-deps` and `check:self-import` → 0. - `check-governed-queue-guard --test` over the 7 paths → NOT GOVERNED. - Declared to CI: repo-wide `pnpm lint`, full `pnpm test` and the other workflow gates. ## Acceptance notes Not changed here, and left for the seat to route. - **Same false claim, outside this package.** `skills/objectui/rules/protocol.md`, the "Readers only." paragraph, says a `bind` on `data-table` renders its header over an empty body "no error, no warning". The render leg and the parser leg above both contradict it. It is on the governed `skills/**` surface, outside this card. - **Same claim in unpublished test prose.** The headers of `base-bind-declared.test.ts` and `object-grid-export-options-refusal-7762.test.ts` repeat it. `mirror-partial-record-narrowing-8516.test.ts` has a different "no error and no warning" claim, about grid columns. All three are test files, outside the census. - **Pending changesets.** `.changeset/7762-object-grid-export-options-bare-array-refusal.md` and `.changeset/6575-data-table-bind-diagnostic.md` describe the pre-fix behaviour in the old words. Pending changesets are out of scope here. - **Adjacent wording, left in place.** - The `base.ts` heading "the one documented silent failure" stays. The corrected sentence now says in which sense it is silent: nothing on the page. - The `exportOptions` guidance string says an array "is silently dropped for the csv/json default". That is present tense about the renderer, and "silently" alone is outside the phrasing family. - **H, the "no diagnostic" nouns (24 at head).** Most are scoped to a named tier or instrument: "on either face" (`objectql.ts`), "zero unresolved-module diagnostics" from `tsc`, `checkType` by name, the TS index signature. A few are unscoped claims about an authored value: `ai.ts` (`maxResults`), `crud.ts`, `data-display.ts` (three) and `zod/data-display.zod.ts`. They were not measured here. - **Observation.** A `mode` on a `form` reaches the DOM as an inert `mode` attribute on the form element. The visible form is identical. - **Boundary.** `view:form` declares `mode` (enum `create`, `edit`, `view`). It is a different registration and node type, which the `FormSchema` docblock does not describe. - **A parser-tier finding, reported to the seat.** `validateTree` answers `bind` with `unknown-prop` on `list` and `tree-view` too, although both read `bind` through `useDataScope`. No registration declares a `bind` input, and the parser's base-prop set has no `bind`. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #10935
Clause-②: yes
MasterDetailFormresolves its threeI18nLabelmembers,title,submitTextandcancelText, withpickLocalizedagainst the active UI language, the wayObjectMetricWidgetdoes (triage comment 5867953691).MasterDetailFormSchematypes the three asI18nLabel, as@objectstack/spec'sComponentPropsMap['object-master-detail-form']does; the zod arm is not narrowed. Draft, headc357b233a(the repair round; the first round wasdbb004b16): a dispatched dev's PR (claim comment 5869374008), so ready and landing belong to the seat.What changed
packages/plugin-form/src/MasterDetailForm.tsxMasterDetailFormSchema.title,.submitTextand.cancelTextgo fromstringtoI18nLabel(imported as a type from@object-ui/types).MasterDetailFormreadsuseObjectTranslation().languageonce, the sourceObjectMetricWidget(and theMetricWidgetit forwards to) resolves itsI18nLabelmembers against.titleresolves once and feeds the built-in edit-save toast and the parentObjectForm'stitle(thestringthatObjectFormSchema.titledeclares).submitTextandcancelTextresolve where the two Buttons read them.||where it was??, so an authored empty string, or a map with no string entry, shows the English default. An authored empty string used to render an empty button; a map with no string entry used to throw as a React child.packages/plugin-form/src/MasterDetailForm.i18nLabels.test.tsx(new): the pins, below.packages/plugin-form/src/MasterDetailForm.test.tsx: the existing host-stringcancelTexttest was named "i18n is the host's job" and commented "the plugin is locale-agnostic". This change makes both false, so the name and comment are reworded. The assertion is unchanged.packages/types/src/zod/objectql.zod.ts: comment only. TheObjectMasterDetailFormBlockSchemadocblock saidMasterDetailFormSchematypes the three labels "as a plain string", which this change makes false. It now saysI18nLabeland names the type pin.packages/plugin-form/src/index.tsx: theobject-master-detail-forminputstitle,submitTextandcancelTextdeclaretype: ['string', 'object'], with descriptions that teach the per-locale map (repair round,c357b233a).apps/console/src/__tests__/: threeMEMBER_PINSrows inregistry-inputs-spec-parity.test.ts, and the new public-door pinmasterDetailFormI18nLabelManifest.test.ts(repair round,c357b233a)..changeset/10935-master-detail-i18n-labels.md:'@object-ui/plugin-form': minor, with theClause-②: yesline.Premise checks, on
origin/main328abeb55schema.cancelText ?? 'Cancel'andsubmitText(schema.submitText ?? …) as Button children, and the${schema.title} savedtoast inhandleSaved.titlehas one more read on this node: theparentSchemamemo forwards it to the parentObjectForm. That forward now gets the resolved string, which is the same one-line repair.ObjectMetricWidgetresolveslabel/titlewithpickLocalized(…, language), wherelanguagecomes fromuseObjectTranslation(), the UI language. This file already importeduseDisplayLocale(), but that is the NUMBER locale: it puts the tenant's localization before the UI language. So it is not the source for label text, and it is not used for it.I18nLabel:title,submitTextandcancelText.details,sectionsandfieldsarez.array(z.unknown()), so no nested label is typed there. The census came from enumerating the installed spec row (17.4.0) at runtime, not from reading the source. Nothing else rides this PR.src/index.tsxexportsMasterDetailFormSchemafrom the package entry (exports['.']isdist/index.d.ts). After the build,dist/MasterDetailForm.d.tsreadsI18nLabelfor all three. So the type widening is published:minor, andClause-②: yesstands.git grep MasterDetailFormSchemain*.ts/*.tsxoutsideplugin-formfinds only two comment or string mentions in@object-ui/types, and no code that reads the type (control: the same grep hitsplugin-form/src/index.tsx).ObjectFormwrites the three members from its ownstringfields, which a widening keeps compiling.Evidence
All at
dbb004b16unless a line says otherwise. The repair round's readings atc357b233aare in the next bullet. The runs went through the shared verify lock, on a shared box.c357b233a(contract review5871765302): the public-door pin withindex.tsxatdbb004b16gave6 failed | 7 passed (13)(the three arm rows and three map rows); at the head, 13 passed. Registry-reading suites:Test Files 10 passed (10) / Tests 309 passed (309). The cli ratchet and the schema-catalog gallery:2 passed / 601 passed.plugin-formandapps/consoletype-check exit 0. Gates exit 0, includingcheck-changeset-presenceandcheck:new-line-citations. CI: 43 check-runs, 40 success, 3 skipped, 0 failed,Spec Main Shape Gateincluded.Pins, through the real
SchemaRendererand the registry. Each node is authored in the{ type, properties }form under anI18nProviderset tozh. Every map listsenfirst, so falling back toenor to the first entry fails the row.safeValidateSchema.submitTextrenders保存订单on the Save button.cancelTextrenders返回on the Cancel button.onCancelsits beside the document as a host key: the arm refuses it as a runtime slot, and the button renders only when it is present.titletoasts采购单 savedon an edit save.Save PO,Discard,PO saved).Create,Cancel,Save,Saved).assertionLabelMembersAreI18nLabel, anExpect/Equaltuple that holds all three members equal toI18nLabel | undefined.Red on base, then green (ablation).
MasterDetailForm.tsxwas committed first, then replaced on disk by its328abeb55blob, under a trap-restore. The hash matched the base blob and thepickLocalizedcount went from 5 to 0.vitest run packages/plugin-form/src/MasterDetailForm.i18nLabels.test.tsxgaveTests 3 failed | 3 passed (6). ThesubmitTextandcancelTextrows failed on "Objects are not valid as a React child (found: object with keys {en, zh-CN})". Thetitlerow failed onexpected [ '[object Object] saved' ] to deeply equal [ '采购单 saved' ]. The validator row and the two controls passed.tsc -p tsconfig.test.jsonfailed on the threeExpectlines (TS2344: Type 'false' does not satisfy the constraint 'true').git diff HEADis empty. At HEAD the same file givesTests 6 passed (6), and withMasterDetailForm.test.tsxTests 16 passed (16).Consumer probe of the built
.d.ts. A one-shot file, outside the repo, importsMasterDetailFormSchemathrough apathsentry pointing atpackages/plugin-form/dist/index.d.ts. Assigning locale maps totitleandsubmitTextcompiles. With the@ts-expect-errordropped from asubmitText: 42line, it fails withTS2322 … not assignable to type 'string | (Record … ) | undefined', which proves the rebuilt declaration is read and is notany.Suites, type-check and lint.
vitest run packages/plugin-form/, run together with a one-off manifest probe file (since deleted), gaveTest Files 138 passed (138),Tests 1600 passed | 1 skipped, atc4d7c74f6. That is 137 plugin-form files plus the probe. The two later commits change test files, a type-only pin and one comment in@object-ui/types. The two changed plugin-form test files were re-run atdbb004b16, as above.vitest run packages/types/gaveTest Files 268 passed (268),Tests 5955 passed.vitest run scripts/gaveTest Files 177 passed | 2 skipped (179),Tests 5333 passed | 2 skipped.pnpm --filter @object-ui/plugin-form type-check(tsc --noEmit && tsc -p tsconfig.test.json) exits 0. The test project lists the new pin file (--listFilesOnly).pnpm --filter @object-ui/types type-checkexits 0.turbo run build --filter='@object-ui/plugin-form^...', 11 of 11 tasks, restored from the turbo cache), and plugin-form itself was built.eslintover the four touched source files: 0 errors. The new test file has no warnings.Gates (each exit 0):
check:new-line-citations(0 new citations),check:control-bytes,check-changeset-presence,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 --testover the five paths answers NOT GOVERNED.Bundle. No new module enters the graph:
@object-ui/i18nwas already imported by this file, and onlypickLocalizedanduseObjectTranslationjoin the named imports. A single-file esbuild minify ofMasterDetailForm.tsx, base against head, gives +95 bytes minified and +50 bytes gzip. That is a proxy for the plugin-form chunk, not the console's eager-closure gate: see NOT MEASURED.Bundle, repair round (from delta review
5872990965).apps/console'sregister-plugins.tsimports@object-ui/plugin-formeagerly, so the three new input descriptions ship in the eager closure. The budget comment onc357b233areads 3104.4 KB against the 3104.5 KB ceiling (PASS), against 3103.9 KB ondbb004b16and on every other PR built after objectui#10992. So this round costs about +0.5 KB, and landing it leaves about 0.1 KB of eager headroom onmain. The exhausted headroom is carried by objectui#10996.Acceptance notes
check:eager-closure/ Bundle Analysis. Reason: it needs a full console build. The proxy delta is above.check:readme-exports. Reason: it refuses to judge while 24 packages are unbuilt ("population collapsed"). This PR touches no README and no export name.inputsfor these three keys now declaretype: ['string', 'object'](object-master-detail-forminplugin-form/src/index.tsx), with descriptions that teach the{ en, 'zh-CN' }map. This repairs defect 1 of contract review5871765302. Through the production public door (ComponentRegistry.getPublicConfigs()→manifestFromConfigs→validateTree), the'string'-only declaration reportedtype-mismatchon a legal locale map, with the messageprop "submitText" expected a string, and the same fortitleandcancelText. My first probe missed it because it readgetAllConfigs(), which keys the block by its namespace, so the bare type answeredunknown-component.apps/console/src/__tests__/masterDetailFormI18nLabelManifest.test.tspins the door. Withindex.tsxatdbb004b16it gave6 failed | 7 passed (13); atc357b233aall 13 pass. A string, and a number that is still reported, are the controls. The console parity gate's threeMEMBER_PINSrows point atMasterDetailForm.i18nLabels.test.tsx.title. This PR only resolves authored labels.object-formnode is in the report as a finding for the seat to judge, and is not touched here. The spec row takes a locale map forsubmitText, and a probe through the realSchemaRendererrendersComponent "form" failed to renderwith "Objects are not valid as a React child".ObjectFormSchematypes itstring.Generated by Claude Code
Body amended by the
domain:uiseat #1 with the dev's repair-round text, after contract review5871765302; no code claim moved beyond that round's commit.