Repository navigation
fix(types,layout,app-shell): a nav entry's inline locale-map label passes the door and renders in the viewer's locale; three inert nav resolver props retire (objectui#11299) - #11325
Conversation
… (objectui#11299)
`NavigationItemObject.label` was `z.string()`, so `safeValidateSchema` (what
`objectui validate` runs) refused an inline locale map label such as
`{ en: 'Accounts', 'zh-CN': '客户' }` that `@objectstack/spec`'s
`NavigationItemSchema` accepts. The mirror now takes the spec's
`I18nLabelSchema` by reference, and the TS twin `NavigationEntryItem.label`
is the spec's `I18nLabel`. The empty-string refusal is unchanged.
Pinned as a parity table against the spec's own schema, with a string control.
Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…locale; three inert resolver props retire (objectui#11299) `NavigationRenderer` gains an optional `locale` prop and `resolveNavItemLabel` a trailing `locale` argument, handed to the spec's own `resolveI18nLabel`, so a present label written as an inline locale map renders the viewer's entry (falling back by that resolver's order) instead of the `en` floor for everyone. The console passes its active `language` from `UnifiedSidebar` (both mounts), `nav:menu`, the command palette and the search results page. `resolveObjectLabel` / `resolveDashboardLabel` / `resolveViewLabel`, inert since objectui#11201, leave `NavigationRendererProps`, and the matching 2nd, 4th and 5th arguments leave `resolveNavItemLabel`: the signature is now `(item, t, targetLabel, locale)`. In-tree callers updated, including `plugin-designer`'s `AppCreationWizard`. `nav-menu-renderer`'s header comment is made true. Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
…ed name (objectui#11299) Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
…e widened nav label type (objectui#11299) `NavigationEntryItem.label` is the spec's `I18nLabel` now, so the inline rename's draft can no longer hand it to the keyed-label reader as is. A string and a keyed reference read as before; an inline locale map starts the draft empty, which is what the keyed read answered for a map at runtime. No behaviour change. Also adds the four changesets. Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
…set names both call sites (objectui#11299) Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ 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
|
…in the same release (objectui#11299) `.changeset/11201-nav-label-verbatim.md` said the sidebar is not told the viewer's locale and that the three retired resolver props are still accepted; `.changeset/10867-app-wizard-saves-spec-app.md` said a nav entry's `label` is `string | undefined`. Each sentence now says what its own card did and that objectui#11299, in the same release, changes it, pointing to that changeset. Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ 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: ① Derived judgmentsInputs: card objectui#11299 (body; triage Q3 — the door (
Q1 A — the locale (
Q2 — the retirement: right, and declared truthfully.
Surface growth past the claim: forced, and behaviour-preserving where claimed.
Pins.
Check-runs on the head: 43, all ② Semver level
③ Boundary flagsBoth reports carry no
Implemented-by: VERDICT: PASS |
Ejected from the merge queue:
|
Fixes #11299
Clause-②: yes
A navigation entry's
labelwritten as an inline locale map (the spec'sI18nLabel, for example{ en: 'Accounts', 'zh-CN': '客户' }) is now accepted byobjectui validateas the spec accepts it, and it renders the viewer's entry. The three nav resolver props that objectui#11201 left inert are removed.This follows triage's answers on the card (comment
5922552691): Q3 first, then Q1 A, with Q2 on the same call sites.What changed
Q3: the authoring door (
@object-ui/types).NavigationItemObject.labeltakes the spec'sI18nLabelSchemaby reference (stripImportedDefaults(SpecI18nLabelSchema), the same crossingbase.zod.tsuses). It wasz.string().NavigationEntryItem.labelis now the spec'sI18nLabeltype.docarm reads its base keys off the flat object, so it takes the same label.refuseEmptyNavLabel) is unchanged. It is still the one stated divergence from the spec.Q1 A: the locale (
@object-ui/layoutplus five console mounts).NavigationRenderergains an optionallocaleprop.resolveNavItemLabelgains a trailinglocaleargument.presentNavItemLabelhands that locale to the spec's ownresolveI18nLabel. There is no copy of its fallback order.useObjectTranslation().languagefrom five mounts:UnifiedSidebar(both the app and home mounts),nav-menu-renderer,CommandPaletteandSearchResultsPage.Q2: the retirement.
resolveObjectLabel,resolveDashboardLabelandresolveViewLabelleaveNavigationRendererProps.resolveNavItemLabelloses its 2nd, 4th and 5th arguments. Its signature is now(item, t, targetLabel, locale).UnifiedSidebarkeeps onlyappLabelfromuseObjectLabel, andnav-menu-rendererdropsuseObjectLabelentirely.nav-menu-renderer.tsx's header comment no longer promises "convention-based object / view / dashboard i18n resolution". It now describes the rule the sidebar actually applies.Zone 2 measurements
packages/types/src/__tests__/nav-label-locale-map-11299.test.tsran against the unfixed mirror. It reportedTests 7 failed | 8 passed (15), withinvalid_typeatlabel, and the spec'sNavigationItemSchema.safeParseaccepted the same entry. The 8 rows that passed are the string control and the refusal rows. After the fix the file reportsTests 15 passed (15). The spec (17.5.0) exportsI18nLabelSchemaandI18nLabelfrom@objectstack/spec/ui.UnifiedSidebarresolves area labels asresolveInlineI18nLabel(area.label, language), withlanguagefromuseObjectTranslation(). It does not callresolveAreaLabel, which belongs toAppSchemaRendererand has no locale.useNavTargetLabelreads the sameuseObjectTranslation().language. That value is thelocalepassed at all five mounts.git grep). The card counted "three in-tree mounts and two tests". The measurement found more:resolveNavItemLabelcalls:CommandPalette,SearchResultsPage,nav-menu-renderer, app-shellviews/metadata-admin/previews/navItemLabel.ts(not named on the card), andpackages/plugin-designer/src/AppCreationWizard.tsx(not named on the card, and a fourth released package). Tests: three layout files and app-shellnavItemLabel.inherit-11196.test.ts.UnifiedSidebarmounts. Tests: app-shelluseNavigationSync.standardEntryLocalizes-11201,StudioDesignSurface.standardNavEntries-11201, and layoutlabelVerbatim-11201andnavLabelOwnership.packages/plugin-list/src/ListView.tsx'sresolveObjectLabelis unrelated. It is a local alias ofuseListFieldLabel().objectLabel, used for export naming. It is not the nav prop and was not touched.packages/plugin-designer/src/NavigationDesigner.tsx: four reads passeditem.labelto the keyed-label reader (TS2345). It now goes through a smalllabelDraftOfhelper that keeps the runtime behaviour: a string and a keyed reference read as before, and a map starts the draft empty, which is what the keyed read answered for a map.AppSchemaRenderer: not taken. Taking a locale would add a public prop toAppSchemaRendererProps. Nothing in this repository mounts that component with a locale to give it, so it fails the "no extra surface" condition. Only itsresolveAreaLabeldocblock is edited, so that it stays true: it no longer says that a nav entry label is the keyed form, and it states that the component's entries read the sameenfloor.Tests
The final union ran at
81579bca12(git rev-parse --short HEAD):pnpm exec vitest run --maxWorkers=2 packages/types/ packages/layout/ packages/plugin-designer/ packages/app-shell/src/layout/plus the 74 app-shell test files thatgit grep -lfinds forNavigationRenderer,resolveNavItemLabel,resolveObjectLabel,CommandPalette,SearchResultsPageornav-menu-renderer. Result:Test Files 443 passed (443)andTests 8875 passed (8875),VERDICT command-exit 0.New pins:
nav-label-locale-map-11299.test.ts(types). The map passessafeValidateSchemaand the strict face exactly as the spec'ssafeParsedoes, with a string control. A parity table checks that the mirror's verdict equals the spec's for nine label shapes (keyed reference, non-string entry and non-locale key refused by both).NavigationRenderer.labelLocaleMap-11299.test.tsx(layout).客户, and no locale reads theendefault.resolveNavItemLabelequals the spec'sresolveI18nLabelfor twelve (map, locale) pairs, which pins "no copy of the fallback order".@ts-expect-errorlines. The layouttype-checkcompiles them, so a re-added prop or argument fails that check.nav:menu, palette, search). Each renders a map-labelled entry under zh-CN and under en, through a realI18nProvider; the sidebar harness uses its mockedlanguageinstead.Ablation:
presentNavItemLabelwas changed to drop the locale (thelocaleargument of itsresolveInlineI18nLabelcall becameundefined), applied withablation-replace.mjs. The anchor count went from 1 to 0 and the file's blob hash changed.Tests 12 failed | 29 passed (41), covering every zh-CN row across layout and the four app-shell surfaces.git diff HEADis empty.packages/layout/src, so no build was needed between legs.Gates
pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' buildNavigationDesignerTS2345 ×4, the item above. After the fix it exits 0.@object-ui/app-shellbuilddist completeness … 974 emitted files verified)type-check@object-ui/types,components,runner,layout,plugin-designer,app-shellandconsoleall pass. Console needed five plugins built first (plugin-gantt,timeline,map,markdown,tree): its first run reported only TS2307 "cannot find module" for those packages, which means a prerequisite was missing, not that the type-check failed.node scripts/check-changeset-presence.mjspnpm changeset:checkcheck:control-bytes,check:spec-symbols,check:i18n-keyscheck:new-line-citationscheck:component-surface-paritycheck-governed-queue-guard --test.ts/.tsxfilesHow the lint was narrowed:
eslint.config.jslints, run as the per-packageeslint .runs (inline config honoured).tseslint.configs.recommended; there is noparserOptions.projectorprojectService), so this diff cannot change a verdict on an untouched file.--no-inline-configthere are 3 errors. All of them are on pre-existingeslint-disable-next-line react-hooks/static-componentslines.Changesets:
@object-ui/typesminor@object-ui/layoutminor, with the break spelled out: the retired props, the six-argument call, and the new prop@object-ui/app-shellpatch@object-ui/plugin-designerpatch: no visible changeAcceptance notes
None of these was filed as a card. Each has no carrier unless named.
useNavPinsstoresresolveNavItemLabel(item)with no locale, so a pinned map-labelled entry's portable favourite record keeps itsentext. The ruling names five mounts and this is not one of them. No measured pull: no readable app writes a map entry label.plugin-designer'snavigation-designerhas two gaps:resolveNavItemLabelwith no locale, so a map readsen.renamedLabel(objectui#11128).{}is accepted, as the spec accepts it, and renders''. The empty-string divergence was not extended to maps, because doing so would widen a divergence from the spec that no ruling covers..changeset/11201-nav-label-verbatim.mdsaid that the three props "are still accepted, so no caller breaks" and that the sidebar is not told the viewer's locale. Both sentences were corrected in patch round 1 (ccd2a9b296), together with two label-type parentheticals in.changeset/10867-app-wizard-saves-spec-app.md.ROADMAP.md(P1.15) still ticks "Wired intoNavigationRenderervia optionalresolveObjectLabel". It is a historical checklist and was left as is.Notes for review
packages/plugin-designer/src/AppCreationWizard.tsx(the signature),packages/plugin-designer/src/NavigationDesigner.tsx(the type), app-shellmetadata-admin/previews/navItemLabel.ts(the signature), and a docblock-only edit toAppSchemaRenderer.tsx.AGENTS.md's model-free pair.Implemented by the
domain:uiseat 1 dispatch, sessionhttps://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ.Generated by Claude Code