Repository navigation
fix(plugin-form,plugin-view,types): object-form resolves its seven I18nLabel members (objectui#10993, batch 1) - #11050
Conversation
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…ers (objectui#10993) ObjectForm resolves title, description, submitText, cancelText, nextText, prevText and successMessage with pickLocalized against the active UI language, once, above its formType fork. The object-form registration declares both arms for the seven keys, ObjectFormSchema types them as I18nLabel, and the zod mirror takes the spec's I18nLabelSchema by reference for the five it declares. Pins: the render through the real SchemaRenderer, the manifest public door, the type and mirror rows, and seven MEMBER_PINS rows. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
… and description (objectui#10993) ObjectViewSchema.form takes ObjectFormSchema's slot keys, so widening ObjectFormSchema.title and .description to I18nLabel widened the object-view form slot with them, and plugin-view's type-check refused the raw reads. ObjectView now resolves both with pickLocalized against the UI language, the language ObjectForm resolves the same form's other labels against. The object-form pin follows main's objectui#10999: the wizard's own defaults come from its catalogue, so the wizard nothing-authored control is left to that card's pin, and every wizard map's zh entry differs from the zh pack's default for the same button. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…claim-ledger (objectui#10993) ObjectFormSchema's zod mirror now takes the spec's I18nLabelSchema by reference, so its spec-alignment claim is backed and check:spec-symbols refuses the stale debt entry. The block is what --claim-ledger prints. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
One conflict, in ObjectForm.tsx's schema memo: main rewrote a bare card citation on the comment line under the memo declaration this branch retyped. Kept both. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
Brings objectui#11000 (PR 11047), which also edits ObjectForm.tsx and the form presentations; merged without conflict. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
|
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
|
…scriptions cite by content (objectui#10993) objectui AGENTS.md #11: the two descriptions in the object-form block cited other files by line, and both addresses had rotted. Re-addressed by content. Reading the cited code for the rewrite showed `ModalForm` declares `modalCloseButton` and never reads it (measured: `false` still renders the close button), so the description now says so instead of "honoured by ModalForm". `contentLayout` names the condition ModalForm tests. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
Brings objectui#10982 (PR 11049), which also edits ObjectView.tsx in other regions; merged without conflict. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
✅ 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: ① Derived judgmentsInputs: card #10993 (body; triage Check-runs on the head, read at 2026-09-29T06:42Z after waiting in the foreground for the eight test shards, Type Check, Lint and Spec Main Shape Gate to finish: 43 latest runs, 40 success, 3 skipped (the two coverage matrices and dependabot), 0 failure, 0 pending. Among the successes: Type Check, Lint, Build and E2E, Test (all eight shards, the aggregate, dist pins), Spec Main Shape Gate, Bundle Analysis (console eager closure within budget), Doc Snippet Type Check, Line Citation Gate, Governed Surface Queue Guard, Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Claim Re-read, Changeset Overwrite Report. These conclusions are the gate verdicts; nothing was run locally.
② Semver level
Pending changesets (bot comment ③ Boundary flagsDev flags, report
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #10993
Clause-②: yes
Batch 1 of the family card: member 2,
object-form, repaired end to end, and the census measured (table below).ObjectFormresolves the sevenI18nLabelmembers ofComponentPropsMap['object-form']withpickLocalizedagainstuseObjectTranslation().language, once, above itsformTypefork, so every presentation it routes to receives a string. The registration declares both arms for the seven keys,ObjectFormSchematypes them asI18nLabel, and the zod mirror takes the spec'sI18nLabelSchemaby reference for the five it declares. The other census members are not repaired here; the seat files or batches the raw reads from the table. Draft: a dispatched dev's PR (claim comment 5882993226, branchclaude/issue-10993-object-form-i18nlabel), so ready and landing belong to the seat. Measured at headaa6013903, which carries main atbf7ab35ce(objectui#11000's PR 11047 included).What changed
packages/plugin-form/src/ObjectForm.tsx:localizeFormLabelsresolvestitle,description,submitText,cancelText,nextText,prevTextandsuccessMessageinside the dispatcher'sschemamemo, right afterfoldFormButtons, for the reasonsections[].groupis resolved above the fork: every presentation reads them, so one site covers the simple, tabbed, split, wizard, drawer and modal forms and the master-detail route. Only the map arm is touched: a string, or any non-object value, is handed on as authored, and a map with no usable entry becomesundefined, so the presentation's own default shows. It returns its input untouched when no member is a map.LocalizedObjectFormSchema(a homomorphic mapped type;Omitcollapses onBaseSchema's index signature) types the memo andSimpleObjectForm'sschema.WizardForm.tsx,DrawerForm.tsxandModalForm.tsxare not edited: they receive strings.packages/plugin-form/src/index.tsx: the sevenobject-forminputs declaretype: ['string', 'object'], with descriptions that teach the per-locale map, in the same change that taught the render site to resolve it (theComponentInputrule, objectui#5590).packages/types/src/objectql.ts: the sevenObjectFormSchemamembers widen fromstringtoI18nLabel.packages/types/src/zod/objectql.zod.ts:title,description,submitText,cancelTextandsuccessMessagewiden fromz.string()tostripImportedDefaults(SpecI18nLabelSchema).optional(), the spec's schema BY REFERENCE (I18nLabelSchemais exported by@objectstack/spec/uiand was already imported here for the charttitle).nextTextandprevTextwere never mirrored and stay unmirrored (see Acceptance notes).packages/plugin-view/src/ObjectView.tsx(outside the claim's file surface):ObjectViewSchema.formtakesObjectFormSchema's slot keys, so the widening widened theobject-viewform.title/form.descriptionwith it, and plugin-view's type-check refusedObjectView's two raw reads (the drawer and modal header it draws around the form). Both now resolve withpickLocalizedagainst the same UI language. Measured below; the hunks stay clear of the regions objectui#10982 edits.scripts/check-spec-symbol-derivation.mjs:ObjectFormSchemaleavesCLAIM_DEBT.check:spec-symbolsrefused the stale entry once the mirror referenced a spec schema; the block is what--claim-ledgerprints.packages/plugin-form/src/ObjectForm.i18nLabels.test.tsx,packages/types/src/__tests__/object-form-i18n-label-members-10993.test.ts,apps/console/src/__tests__/objectFormI18nLabelManifest.test.tsandpackages/plugin-view/src/__tests__/ObjectView.formLabelsI18nLabel-10993.test.tsx(new); sevenMEMBER_PINSrows inregistry-inputs-spec-parity.test.ts; inzod-mirror-parity.test.ts,objectql.zod.ts#ObjectFormSchemajoinsSPEC_DERIVED_PAIRS, so itsUnmirroredDeclaredentry (21 keys, none touched) moves from the split's LOCAL half to the SPEC-DERIVED half, and the header figures and history sentences follow.content/docs/api/schema-reference.md: theObjectFormSchematable types the seven asstring \| I18nLabel..changeset/10993-object-form-i18nlabel.md:@object-ui/plugin-formpatch,@object-ui/plugin-viewpatch,@object-ui/typesminor (the widening), with theClause-②: yesline.Premise checks
@objectstack/spec17.4.0ComponentPropsMap['object-form']: exactly seven members are theI18nLabelunion (stringor a record of string to string), the seven the card names.sectionsandcustomFieldstype no nested label.SchemaRendererunder azhprovider:{ type: 'object-form', properties: { objectName, mode: 'create', submitText: { en, 'zh-CN' } } }renderedComponent "form" failed to renderwith "Objects are not valid as a React child (found: object with keys {en, zh-CN})". It is pinned (red on base, below).SimpleObjectForm:submitTextandcancelTextinto theformnode'ssubmitLabel/cancelLabel(both of its render branches),successMessageinto four success-toast sites.TabbedForm,SplitForm:submitText/cancelText.WizardForm:nextText,prevText,submitText,cancelText,successMessage.DrawerForm,ModalForm:title/descriptionas the header,submitText/cancelTextin the footer; neither readssuccessMessage.EmbeddableFormreads its ownconfig.title, a different node. The master-detail route forwardstitle,submitTextandcancelTexttoMasterDetailForm, which already resolves them (PR objectui#10986).safeValidateSchemarefused a map on the five mirrored members (invalid_typeat the member) and did not judgenextText/prevTextat all (a map and a number both parsed). Separately, it refuses the{ type, properties }form ofobject-formwholesale (objectNameinvalid_type,modeinvalid_value, even forproperties: { objectName: 'order' }), because the arm is the flat mirror. That is reported, not changed here.stripImportedDefaults(SpecI18nLabelSchema), which returns the spec object itself for a schema with no defaults.Evidence
All runs at
aa6013903unless a line says otherwise, through the shared verify lock on a shared box.pnpm exec vitest runoverpackages/plugin-form/(in two halves, for the foreground cap):Test Files 68 passed (68),Tests 550 passed (550), andTest Files 73 passed (73),Tests 1115 passed | 1 skipped (1116).packages/types/:Test Files 279 passed (279),Tests 6432 passed (6432).packages/plugin-view/with app-shell'sblock-config-schema-parity-8216.test.tsandblock-config.test.ts:Test Files 61 passed (61),Tests 630 passed (630).register-plugins, including the parity gate and both I18nLabel manifest pins; clispec-vocabulary-hint,validate-union-arm-selectionandregistered-types-validate-ratchet-10859; schema-catalogcatalog-gallery-renderandsafe-validate-corpus-6318):Test Files 20 passed (20),Tests 999 passed (999).check-spec-symbol-derivation(the gate script this PR edits):Test Files 31 passed (31),Tests 630 passed (630).turbo run build --filter='@object-ui/plugin-view^...'(15 tasks), thenpnpm --filtertype-checkfor@object-ui/types,@object-ui/plugin-formand@object-ui/plugin-view, joined by&&: exit 0.check-changeset-presence,changeset:check,check:changeset-claims,check:pending-changeset-literals,check:control-bytes,check:new-line-citations(0 new),check:spec-symbols,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:handler-key-reads,check:i18n-keys,check:registry-bare-names,check:prompt-keys,check:unreferenced-sources,check:phantom-deps,check:component-surface-parity,check:doc-types,check:doc-fences,check:element-data-source-declaration,check:designer-field-key-parity.check-governed-queue-guard --testover the 14 paths: NOT GOVERNED.files: ['**/*.{ts,tsx}']block overtseslint.configs.recommended;--format jsonover the 12 changed code files reports 12 files, 0 errors, 173 warnings, 0 of them on a line this branch adds. The config enables no type-aware linting (noproject,projectServiceorTypeCheckedineslint.config.js) and noeslint-rules/rule reads another file, so this diff cannot move a verdict on an untouched file.Red on base, then green. The three fix files were committed, then replaced on disk by their
b3c96d6bcblobs under a trap-restore, at5d58ae2d8(hashes equal to the base blobs;localizeFormLabels0, object arms 3, i.e. master-detail's, specI18nLabelSchemacrossings 1, i.e. the chart's).Tests 26 failed | 37 passed (63): the render pin's 7 map rows failed (simple, tabbed and split onComponent "form" failed to render, wizard, drawer and modal onComponent "object-form" failed to render, all "Objects are not valid as a React child (found: object with keys {en, zh-CN})"; thesuccessMessagerow onexpected [ '[object Object]' ] to deeply equal [ '订单已保存' ]) and its 5 controls passed; the types pin's 5 map rows failed oninvalid_typeand 17 passed; the manifest pin's 7 arm rows and 7 map rows failed and 15 passed. Restored: hashes equal the HEAD blobs,git diff HEADempty.ObjectView.tsxthe same way against main's blob:Tests 2 failed | 2 passed (4), the two map rows on "Objects are not valid as a React child".Type pin, reverse leg.
ObjectFormSchema.submitTextnarrowed back tostringon disk:tsc -p tsconfig.test.jsonfails withTS2344: Type 'false' does not satisfy the constraint 'true'on theassertionLabelMembersAreI18nLabelrow (andzod-mirror-parity's wider-than-declared ledger reds too). Restored by hash.Consumer probe of the built
.d.ts. A one-off file outside the repo, with@object-ui/typesmapped topackages/types/dist/index.d.ts: locale maps ontitleandsubmitTextcompile; with its@ts-expect-errorremoved,submitText: 42failsTS2322 … not assignable to type 'string | (Record … ) | undefined', so the rebuilt declaration is read and is notany.The render pin's harness. Each row mounts inside an async
act. With a plainrenderandfindBy…, three rows that mount after certain earlier rows stayed on "Loading form..." past the find window, deterministically, and the same three did so on the baseObjectForm.tsxtoo, so it is not this change; committing the mount-time schema read insideactremoves it. Every wizard map'szhentry differs from the zh pack's own default for that button (objectui#10999 moved the wizard's defaults to its catalogue), so a map dropped instead of resolved fails its row; the wizard's nothing-authored defaults are left to that card's pin.The census
Every installed (17.4.0)
ComponentPropsMaprow'sI18nLabelmembers, enumerated at runtime by walking each row's zod shape for the string-or-record-of-string union, and each objectui renderer's read. 45 rows, 24 with such a member, 56 members: 7 repaired here, 33 already resolving, 6 raw reads, 9 with no read site, 1 with no renderer. "Measured" means a render through the realSchemaRendererunder azhprovider with anen-first map; the rest are read at the named site.object-formObjectFormlocalizeFormLabelsobject-master-detail-formMasterDetailFormpickLocalized(…, language)object-metricObjectMetricWidget/MetricWidgetpickLocalized(…, language)'string'only, measuredtype-mismatchon a mapobject-gridObjectGridresolveInlineI18nLabel(schema.label, displayLocale)(caption, export label, detail title); measured'string'only, measuredtype-mismatchobject-gridObjectGridcaption: resolveInlineI18nLabel(schema.label, displayLocale) || schema.title, aTableCaptionchildComponent "data-table" failed to renderelement:textElementTextRendererpickLocalized(props.content, language)element:buttonElementButtonRendererpickLocalized(props.label, language)element:text,element:button,element:image,element:numberariaAttrsinelements.tsx:String(v)underaria-${k}aria-arialabel="[object Object]"; a plain string also lands onaria-arialabel, neveraria-labelelement:text_inputElementTextInputRendererpickLocalized(…, language)element:record_pickerElementRecordPickerRendererpickLocalized(…, language)element:text_inputelement:record_picker,element:metadata_viewerpage:headerPageHeaderRendererpickLocalized(titleSrc / subtitleSrc, language)page:cardPageCardRendererpickLocalized(schema?.title, language)page:tabs,page:accordionPageTabsRenderer/PageAccordionRenderertranslateLabel(pickLocalized(it.label, language), …)page:header,page:card,page:tabs,page:accordioncontainers.tsx; measured, no attributerecord:alertRecordAlertRendererpickLocalized(…, language)record:related_listRecordRelatedListRendererpickLocalized(schema.title / schema.add.label, language)titleinput'string'only, measuredtype-mismatchrecord:detailsRecordDetailsRendererpickLocalized(rawTitleSrc, language)record:pathRecordPathRendererrenders{o.stage.label}andstageAriaLabel(o.stage.label, …)Component "record:path" failed to renderrecord:activity,record:chatter,record:details,record:discussion,record:highlights,record:path,record:related_listuseRecordAriaProps→useRecordAriaName→resolveInlineI18nLabel(aria?.ariaLabel, locale), the display localerecord:chatter,record:discussionRecordChatterRendererreadsfeed's limit, types, filter mode and the rest, neverfeed.ariaai:chat_windowpalette-placeholder-blocks.test.tsxpins the exclusion)The table is a reading at
aa6013903on spec 17.4.0 and is not re-derived by anything in this PR; the instrument is the enumeration described above plus the named sites.Acceptance notes
check:eager-closure/ Bundle Analysis andcheck:sdui-registration-pins. Reason: both need a full console build. The seven input descriptions ship in the console's eager closure (register-pluginsimports@object-ui/plugin-formeagerly).check:doc-snippetsandcheck:doc-examples. Both answered PRECONDITION NOT MET (exit 2): they need 34 packages built. The doc edit adds table rows and one sentence, no fenced snippet.apps/consoleand@object-ui/app-shelltype-check. Reason: their dependency closures were not built here. The console change is two test files. Outside plugin-form and plugin-view, a grep for reads of the seven members off anObjectFormSchemaorObjectViewSchema['form']finds none that type-checks against them:FormPagereads aFormViewBody, andScreenViewbuilds its node throughas any. CI's type-check is the reading.nextTextandprevTextstay unmirrored. They were never declared on the zod mirror, sosafeValidateSchemadoes not judge them (a number parses). Mirroring them would REFUSE values accepted today, a narrowing this widening PR does not make; they stay in the pair'sUnmirroredDeclaredentry, and the types pin records it.DrawerFormandModalFormnever readsuccessMessage. A source reading, not measured at runtime; recorded, not filed.index.tsx'smodalCloseButtonandcontentLayoutdescriptions, in the registration block this PR edits. Not repaired here: they are a different defect class from this card's. Carrier: none.2eaf5be27(objectui#10999, the wizard's catalogue defaults, which this PR's wizard rows now account for),63ab76112(objectui#11016's citation rewrite: one textual conflict, on a comment line inObjectForm.tsx's memo, kept both sides) andbf7ab35ce(objectui#11000's PR 11047, clean).Generated by Claude Code