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>
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.