Repository navigation
fix(plugin-detail,plugin-dashboard,plugin-grid): the I18nLabel family's batch 2 — record:path stage labels and four registrations accept a locale map (objectui#10993) - #11099
Conversation
…(objectui#10993, batch 2) `stages[].label` is an I18nLabel in the spec row; a locale map was read raw as a React child and the block rendered `Component "record:path" failed to render`. The renderer now resolves each map with pickLocalized against the UI language before the stages are translated, classified or rendered. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…ccept an I18nLabel locale map (objectui#10993, batch 2) object-metric label/description/title, object-grid label (and view:grid) and record:related_list title declared 'string' only while their renderers already resolve a per-locale map, so validateTree reported type-mismatch on a spec-legal map. Each declares ['string', 'object'], with a render pin per block and one manifest pin, the MEMBER_PINS rows, the schema-reference docs face and a changeset. The related_list and other plugin-detail registration comments cite by content, not line. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…w:grid, which the public manifest does not publish (objectui#10993) Also types record:path's local stage shape's value as unknown rather than any. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
… loop, so the narrowing of its copy is visible to the compiler (objectui#10993) 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
|
Brings main's modalCloseButton row in schema-reference.md (objectui#11091), which merged cleanly beside this branch's Spec alignment hunk. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…spec row without a version literal (objectui#10993) doc-version-claims refused `@objectstack/spec` 17.4.0 on a scanned doc surface. The sentence now points at the installed `ComponentPropsMap ['object-form']` row, so nothing in it can go stale. 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
|
Contract reviewServed-tier: PR objectui#11099, ① Derived judgments
② Semver level
Clause-②: yes — five published registry inputs widen from ③ Boundary flagsBoth batch-2 reports carry
Check-runs on the head, Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #10993
Clause-②: yes
Batch 2 of the family card: the
record:pathraw read is repaired, the registry half is declared, and the docs face is corrected.RecordPathRendererresolves eachstages[].labellocale map withpickLocalizedagainstuseObjectTranslation().languagebefore the stages are translated, classified or drawn, so a spec-valid map no longer crashes the block.object-metric'slabel/description/title,object-grid'slabelandrecord:related_list'stitledeclaretype: ['string', 'object'], because their renderers already resolve the map, sovalidateTreeno longer reportstype-mismatchon a legal map.schema-reference.mdstops calling fourteen ofobject-form's spec members, the fiveI18nLabelones batch 1 repaired among them, "ObjectUI extensions with no spec backing". The census table below records every row's standing after this batch.Held back, per the claim:
object-grid's deprecatedtitle(the caption fallback inObjectGrid.tsx, serial behind objectui#11026, whose branch edits that file). The 9 never-read members andai:chat_windoware the seat's to route. Draft: a dispatched dev's PR (claim comment 5888090052, branchclaude/issue-10993-batch2-i18nlabel-reads), so ready and landing belong to the seat. Measured at heada840a1094on basee119f120c.What changed
packages/plugin-detail/src/renderers/record-path.tsx:localizeStageLabelsresolves each stage whoselabelis an object withpickLocalizedagainst the UI language, inside a memo keyed onschema.stagesand the language, before the picklist translation,classify()and the two rails read it. That covers three reads, not two:classify()probesvalue + labelfor its won/lost tokens, so a map left raw there probed[object Object]and a lost stage authored as a map stopped reading as lost. Only the map arm is touched; a string or any other value is handed on as authored, and the input array is returned untouched when no stage carries a map. The picklist translation of the status field still outranks the authored label, as it did for a string.packages/plugin-dashboard/src/index.tsx:object-metric'slabel,descriptionandtitledeclare both arms, with descriptions that teach the map.MetricWidgetresolveslabel(the tile heading) anddescription(the sub-caption);ObjectMetricWidgetresolvestitle(the drill-down heading, falling back tolabel); all against the UI language.packages/plugin-grid/src/index.tsx:GRID_QUERY_INPUTS'labeldeclares both arms. Every read of it inObjectGrid(the caption, the export title, the record-detail overlay heading) resolves with the spec'sresolveI18nLabelagainstuseDisplayLocale(), and the description says so.view:gridregisters the same inputs, so its declaration carries the same arms; it is not in the public manifest, so no pin row reaches it through that door.ObjectGrid.tsxis not touched.packages/plugin-detail/src/index.tsx:record:related_list'stitledeclares both arms;RecordRelatedListRendererresolves it withpickLocalizedagainst the UI language before its object-label fallback. The same file's cross-file line addresses are re-addressed by content (objectuiAGENTS.mdcommandment 11, "When you touch a file anyway, fix the cross-file line addresses inside it"; the seat applied the same rule to batch 1): therelationshipValueField/addreads inrecord-related-list.tsx, thepicker.valueField/picker.labelFielddefaults inRelatedList.tsx(both addresses had drifted), thehideFieldsread inrecord-details.tsx, theDetailSection.tsxempty-section return, the spec'scomponent.zod.tsbindings, and theapplyFeedConfigcall shape inrecord-activity.tsx. Comment text only.content/docs/api/schema-reference.md: theObjectFormSchema"Spec alignment & extension keys" bullet that calledsubmitText,cancelText,nextText/prevText,successMessageand nine more keys "ObjectUI extensions ... with no spec backing yet" is replaced by what the installed spec says (measured below): a "Component-contract keys" class for the fourteen keys that are members ofComponentPropsMap['object-form']but not ofFormViewSchema, and an "Envelope key" class forclassName, which is onPageComponentSchema. TheObjectGridSchematable gains alabelrow typedstring \| I18nLabel(the table had nolabelrow at all).record-path.stageLabelI18nLabel-10993.test.tsxandrecord-related-list.titleI18nLabel-10993.test.tsx(plugin-detail),ObjectMetric.i18nLabel-10993.test.tsx(plugin-dashboard),ObjectGrid.labelI18nLabel-10993.test.tsx(plugin-grid),i18nLabelInputsManifest-10993.test.ts(console). Every render pin mounts the{ type, properties }document through the realSchemaRendererand registry underzhanden, with every map listingenfirst.registry-inputs-spec-parity.test.tsgains fiveMEMBER_PINSrows (object-grid.label,object-metric.description/.label/.title,record:related_list.title), and therecord:path.stagesrow names the new stage-label pin..changeset/10993-batch2-i18nlabel-reads.md:@object-ui/plugin-detail,@object-ui/plugin-dashboardand@object-ui/plugin-gridminor (each package's registration widens; plugin-detail also carries the renderer repair), with theClause-②: yesline.Premise checks
@objectstack/spec17.4.0,ComponentPropsMapaccepts a locale map and refuses a number (invalid_union) atrecord:pathstages[].label,object-metriclabel/title/description,object-gridlabelandrecord:related_listtitle.view:gridhas no row.SchemaRendererat base:{ type: 'record:path', properties: { statusField, stages: [ { value, label: { en, 'zh-CN' } } ] } }renderedComponent "record:path" failed to renderwith "Objects are not valid as a React child (found: object with keys {en, zh-CN})".ComponentInput.typerule: teach the render site, then declare the arm), read at the sites named above and measured by the render pins, which are green on the base renderers too (they are the render half the arms rest on, not repairs).ObjectGrid's fourthlabelread,columnDiagnosticLabel, feeds a console diagnostic that keeps the label only when it is a string, so a map is dropped from the message rather than printed.ComponentPropsMaprow for top-level members that accept a locale map and a string and refuse a number and an array, and read each one's registered input: 29 members, 28 declare both arms at this head, and the 29th isobject-grid.title, which is deliberately not declared (the ruled carve-out for the deprecated spelling).stages[].labelthrough the resolver the block'sarianame uses (resolveI18nLabelagainstuseDisplayLocale()). The stage label is resolved withpickLocalizedagainstuseObjectTranslation().languageinstead: it is the source every other visiblerecord:*label in this package uses (record:related_list.title,record:alert.title,record:detailssections), the card's direction, and the languaget()composes the stage's accessible name in, so the name never mixes a regional-locale label with UI-language state words. The two resolvers agree limb for limb; only the locale source differs.claude/issue-11026-number-use-grouping(fetched into a private ref) edits neitherplugin-grid/src/index.tsxnorplugin-detail/src/index.tsx; no serial conflict.Evidence
All runs at
a840a1094unless a line says otherwise, through the shared verify lock on a shared box.pnpm exec vitest run packages/plugin-detail/:Test Files 220 passed | 1 skipped (221),Tests 2159 passed | 8 skipped (2167).packages/plugin-dashboard/:Test Files 152 passed (152),Tests 1385 passed | 6 skipped (1391).packages/plugin-grid/:Test Files 166 passed (166),Tests 1543 passed (1543).register-plugins(the parity gate and the three I18nLabel manifest pins among them), cliregistered-types-validate-ratchet-10859,spec-vocabulary-hintandvalidate-union-arm-selection, and schema-catalogcatalog-gallery-renderandsafe-validate-corpus-6318:Test Files 21 passed (21),Tests 1022 passed (1022).turbo run buildover the three packages' dependency closure (14 tasks), thenpnpm --filtertype-check(tsc --noEmit && tsc -p tsconfig.test.json) for@object-ui/plugin-detail,@object-ui/plugin-dashboardand@object-ui/plugin-grid, joined by&&: exit 0.tsc -p tsconfig.test.json --listFilesin each package lists the new pins (2, 1 and 1), so the type-check covers them; each part exited 0.check-changeset-presence,changeset:check,check:changeset-claims,check:pending-changeset-literals,check:control-bytes,check:new-line-citations(0 new),check:doc-types,check:doc-fences,check:registry-bare-names,check:prompt-keys,check:component-surface-parity,check:designer-field-key-parity,check:element-data-source-declaration,check:i18n-keys,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:handler-key-reads,check:unreferenced-sources,check:phantom-deps.check-governed-queue-guard --testover the 12 paths: NOT GOVERNED.files: ['**/*.{ts,tsx}']block overtseslint.configs.recommended;--format jsonover the 10 changed code files reports 10 files, 0 errors, 67 warnings, 1 of them on a line this branch adds:react-hooks/preserve-manual-memoizationon thestagesmemo inrecord-path.tsx, whose type annotation this PR edits. Linting the base blob reports the same rule on the same memo. The config enables no type-aware linting (noparserOptions,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 fix files were committed, then the four source files (
plugin-dashboard,plugin-gridandplugin-detailsrc/index.tsx, andrecord-path.tsx) were replaced on disk by theire119f120cblobs inside the verify lock, under a trap-restore, at2ef598299(hashes equal to the base blobs;objectui#10993mentions 0 in each index file,localizeStageLabels0).Tests 16 failed | 225 passed (241)over the six pin files: the manifest pin's 5 arm rows (expected [ 'string' ] to deeply equal [ 'object', 'string' ]) and 5 map rows (atype-mismatchwarning where none is expected) failed while its 3 reachability, 5 string and 5 number controls passed; therecord:pathpin's 5 map rows failed onComponent "record:path" failed to renderwhile its string control passed; the parity gate failed exactlyevery member pin names a key that is still array/object-armed on a covered block, naming the five new rows; and theobject-metric,object-gridandrecord:related_listrender pins stayed green, as predicted, because those renderers resolved the map already. Restored: hashes equal the HEAD blobs,git diff HEADempty. The one later commit,a840a1094, rewriteslocalizeStageLabels' loop only, and the mutated tree is the base blobs either way.The census, after batch 2
Every installed (17.4.0)
ComponentPropsMaprow'sI18nLabelmembers, as batch 1 enumerated them in PR objectui#11050, with each row's standing now.object-formobject-master-detail-formobject-metricobject-gridobject-gridObjectGrid.tsx, serial behind objectui#11026element:textelement:buttonelement:text,element:button,element:image,element:numberelement:text_inputelement:record_pickerelement:text_inputelement:record_picker,element:metadata_viewerpage:headerpage:cardpage:tabs,page:accordionpage:header,page:card,page:tabs,page:accordionrecord:alertrecord:related_listtitleinput declares both arms hererecord:detailsrecord:pathrecord:activity,record:chatter,record:details,record:discussion,record:highlights,record:path,record:related_listrecord:chatter,record:discussionai:chat_windowThe rows' standings are readings at this head on spec 17.4.0; nothing in this PR re-derives the table.
Acceptance notes
check:eager-closure/ Bundle Analysis andcheck:sdui-registration-pins. Reason: both need a full console build. The five input descriptions add text to registrations the console loads eagerly.check:doc-snippetsandcheck:doc-examples. Reason: not run; they need the full package set built. The doc edit adds a table row and bullet text, no fenced snippet.apps/consoletype-check. Reason: its dependency closure was not built here. The console change is two test files; the new one copies the shape of batch 1'sobjectFormI18nLabelManifest.test.ts. CI's type-check is the reading.@object-ui/types(record-components.ts),RecordPathComponentProps.stages[].label,RecordRelatedListComponentProps.titleand.add.label, andRecordDetailsComponentProps.sections[].labelare typedstringwhere the spec row saysI18nLabel. The renderers resolve the map at runtime, so nothing crashes, but a TypeScript author who writes the spec's map gets a compile error.packages/typesis outside this claim; named in the report for the seat as this card's TS face.record:path'sstagesdescription promises what the renderer does not do. The registration says "Explicit stage definitions [{ value, label }] (else derived from field metadata)", echoing the spec's "(if not using field metadata)". A one-off probe (not committed):{ type: 'record:path', properties: { statusField: 'stage' } }throughSchemaRenderer, in a record context whose adapter declares thestagepicklist, renders "record:path — no stages configured", with no list items and nogetObjectSchemacall. Only the synthesized default page derives stages. This PR does not edit that description (a different defect). Reported to the seat.plugin-detail/src/index.tsx, the registration file this PR edits (see What changed).registry-inputs-spec-parity.test.ts, where this PR adds five rows as batch 1 added seven, carries 13 lines with such addresses in its prose and exemption reasons. They are left as batch 1 left them, because repairing them is outside this card; carrier: none.view:gridsharesGRID_QUERY_INPUTS, so itslabeldeclares both arms too. It is not ingetPublicConfigs()'s manifest, so the manifest pin has no row for it.mainmoved during the run by four commits (to3e71a9825), none touching this PR's files. It is not merged in, because the queue rebuilds onmain. objectui#11051's PR 11078 landed asa4b017eda, which is why the census marks theelement:*aria.ariaLabelrow done.Generated by Claude Code