Skip to content

fix(app-shell,plugin-designer): the designer's nav surfaces show a label-less entry's inherited label, the way the console draws it (objectui#11196) - #11224

Merged
objectstack-fleet[bot] merged 11 commits into
mainfrom
claude/issue-11196-designer-nav-label-inherit
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 11 commits into
mainfrom
claude/issue-11196-designer-nav-label-inherit

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Part of #11196
Clause-②: no

Most of objectui#11196: every designer surface this claim covers, including the Studio rail rows (after PR objectui#11202 reached main; this branch merges it). Two sites from the card's own sweep.SEAM list are outside this claim's file surface and are not touched (see Remainder), so this PR does not close the card.

What changed

A nav entry with no label inherits its target's CURRENT label at render time. The console already draws it that way (sidebar, nav:menu, command palette, search, since PR objectui#11186). The designer surfaces read the raw label instead. Every surface below now asks the runtime's own rule, resolveNavItemLabel from @object-ui/layout. None keeps a second ladder, and packages/layout/src/NavigationRenderer.tsx is not edited.

surface before, for a label-less entry now
AppPreview list (normalizeNav) and its landing line (unnamed) inherited text
AppNavCanvas card (navLabel) positional Item N inherited text; the inline rename shows it as the input's placeholder
AppNavInspector title label ?? title ?? name, else the positional selection id inherited text
AppNavInspector Label field empty value, no hint; a map label showed [object Object] value = authored label only; placeholder = inherited text
nav-selection.ts findNavPositionById label ?? title ?? name authored plain-string label only; the inspector names the entry from the entry itself
Studio Interfaces rail, StudioDesignSurface.tsx NavTree rows leaf: the target's internal name; group heading: blank inherited text; the internal identity stays on the tooltip
plugin-designer NavigationDesigner row and its live preview blank the rule's own answer: the machine-name rung, because it is handed no metadata
plugin-designer AppCreationWizard's Navigation step blank each object's label, from the wizard's own object list (a host NavTargetLabelResolver handed to the rule)
  • One rule, extended rather than forked. previews/navItemLabel.ts gains navEntryLabelText and inheritedNavEntryText. They call resolveNavItemLabel: an authored label reads through the existing navItemLabelText in the designer locale, and an absent one goes to the runtime's ladder. Carrier rider (5909012131): the module docblock now says navItemLabelText reads any I18nLabel (app and action labels too). The rename is not done, because it would touch StudioDesignSurface.tsx and ObjectActionsPanel.tsx, which are outside this claim.
  • Resolver. The app-shell surfaces pass the console's own useNavTargetLabel. Measured: it resolves in the console's i18next language, and the designer locale (useMetadataLocale) is that same language collapsed to en-US / zh-CN. So a label-less entry reads in the designer exactly as the console draws it. A designer-locale twin would have needed either a second metadata resolver or an edit to useNavTargetLabel.ts, which is outside this claim. Limit: in a session of one of the eight other shipped locales, inherited text follows the console language, while the designer resolves an authored map label in English.
  • Fallback. Same as the runtime's: the target's machine name (objectName, dashboardName, pageName, ...), else the entry's id. A target the host cannot read (a draft, an unsaved page, no metadata loaded) falls to the machine name, as it does in the console before its metadata loads. engine.appPreview.unnamed and the positional engine.appNav.item row stay only for what the rule names nothing: a separator, a label that is present but resolves to nothing (an empty map), and an entry with no target and no id. No i18n string is added or removed.
  • Editing keeps inheritance. On the canvas and in the inspector, inherited text is a placeholder, never a stored value, so an untouched entry stays label-less. Typing writes an author label; a locale map changes only the designer locale's entry, through the existing renamedLabel. Emptying the inspector field restores inheritance: the new clearedLabel (the inverse of renamedLabel) answers what is left, and when nothing is left the label key is removed, never written as ''.
  • plugin-designer. It now depends on @object-ui/layout. There is no cycle: layout depends on components, core, react and types, and none of them depends on plugin-designer. Bundle: plugin-designer's vite build externalizes every bare specifier. Measured on the built dist/index.js: one import ... from "@object-ui/layout" and 0 inlined definitions of the ladder. For a consumer, layout is side-effectful and brings @dnd-kit, but the console already ships it. The Navigation Designer is handed bare items and no metadata, so it shows the rule's machine-name rung. The app wizard's Navigation step hands the rule a host resolver built from its own object list, so a label-less object entry shows the object's label there. For pages (what the console's navigation sync writes) that is exactly what the console shows. A label-less dashboard shows dashboardName where the console shows its label. The seat has the options for that.

Remainder (not in this PR)

  • resolveSurface's Surface.label (navSurface.ts). It is unchanged: it still carries '' for a label-less entry. Its readers are the canvas caption, the breadcrumb and the copilot chip, all in StudioDesignSurface.tsx outside the NavTree rows this claim allows. The chip's type · name fallback for an unlabelled leaf is pinned by StudioDesignSurface.aiChipLabel.test.tsx (objectui#8219 item 3). Making them inherit is a ruling for the seat, not a line change.

  • StudioNavItemInspector (in StudioDesignSurface.tsx). Its label reads are in the card's sweep.SEAM list, but outside this claim's file surface ("NavTree rows only"). Two gaps remain there. Clearing its Label field writes '' through renamedLabel. Picking an object for a label-less entry stores the object's label, where the entry would otherwise keep inheriting it.

  • AppNavCanvas's addItem birth label. A new entry is born with the localized "New item" label, and AppNavInspector's object picker keeps it, so the entry never inherits.

The seat answered the dev's open questions (5914452475 gives the review's readings): resolveSurface inherits (A, with the objectui#8219 chip pin re-judged); StudioNavItemInspector follows AppNavInspector's shape (A); addItem births a label-less entry (A, with the objectui#11189 autosave pins re-judged). All three are this card's next slice, after PR objectui#11225 lands, because they share its StudioDesignSurface.tsx hunks. NavigationDesigner stays on the machine-name rung until a host with metadata appears.

Tests and gates

Measured at 2a6f0c5fb, after merging origin/main (it carries PR objectui#11202 and objectui#11206's fix). Later commits: 693024cc2 (the changeset), d78e28afd and dfbf137fa (the wizard step's host resolver and its pin), and 6e8f1d83f (a merge of main carrying PR objectui#11207 / objectui#11197). The re-runs at 6e8f1d83f are the group "At the merged head" below. Build, type-check and test runs went through the shared verify lock, from the worktree root; the check:* gates and eslint are lock-free.

  • Build: turbo run build --filter='@object-ui/app-shell^...' --concurrency=2, exit 0.
  • pnpm --filter @object-ui/plugin-designer type-check exit 0; pnpm --filter @object-ui/app-shell type-check exit 0 (both tsc --noEmit and tsc -p tsconfig.test.json).
  • pnpm exec vitest run, every run under timeout 300, with --testTimeout=20000 --reporter=verbose:
    • packages/plugin-designer/: 28 files / 203 tests passed.
    • packages/app-shell/src/views/studio-design/: 77 files / 434 tests passed.
    • packages/app-shell/src/views/metadata-admin/ in eight chunks: 358 files / 4228 tests passed, 0 failed.
  • New pins:
    • previews/navItemLabel.inherit-11196.test.ts: the parity pin. For every nav type, with and without a resolver, the designer's text for a label-less entry equals resolveNavItemLabel's own answer.
    • previews/AppPreview.labelInherits-11196.test.tsx covers the list and the canvas.
    • inspectors/AppNavInspector.labelInherits-11196.test.tsx covers the title, placeholder, typing, and clearing (which removes the key).
    • studio-design/StudioDesignSurface.railLabelInherits-11196.test.tsx covers the rail rows.
    • plugin-designer __tests__/NavigationDesigner.labelInherits-11196.test.tsx covers the rows, the live preview, the inline placeholder and the wizard step.
    • Each pin has an authored-label control.
  • Pins re-judged: three older pins asserted (unnamed) / Item N for a truly absent label: AppPreview.labelMaps-11100, AppNavCanvas.labelMaps-11128 and AppPreview.test.tsx. They now assert the inherited text, and keep the fallbacks for what the rule names nothing.
  • Ablations: through ablation-replace.mjs in wrap mode. Each mutation was proven to land on disk, and each restore was proven against HEAD (blob equals HEAD, and git diff HEAD is empty). The pins import src directly, so there is no dist leg.
    • A. AppPreview reads the raw label: 3 of 6 red, the three list cases.
    • B. NavigationDesigner live preview reads resolveKeyedI18nLabel(item.label): 1 of 4 red.
    • C. The inspector's placeholder removed: 1 of 6 red.
    • D. NavTree reads the raw label: 2 of 3 red.
    • E. The wizard step drops its host resolver: 1 of 4 red (the Contact assertion), at dfbf137fa.
  • Lint and gates:
    • pnpm exec eslint on the changed files: 0 errors. Warnings per source file are identical in count and rule set to the base versions, measured by linting git show BASE:path with --stdin-filename.
    • Exit 0 at 693024cc2: check-changeset-presence, check-control-bytes, check:new-line-citations (0 new), check:phantom-deps, check:unused-deps, check:changeset-claims, check:pending-changeset-literals, changeset:check, and markdown-test-inputs.mjs --audit, check:self-import and check:pre-install-import-graph.
    • Root scripts/__tests__/ (owed because this PR edits a markdown file, the changeset): NOT MEASURED when this PR was opened, because the verify-lock queue timed out twice. The reading is in the os-dev-report comment on the card.
  • check:changeset-claims asked for a read of four pending changesets that name files this PR edits: three pnpm-lock.yaml paragraphs about spec resolution, and 6681 about marketplace chunks. All four are still true; this PR only adds plugin-designer's workspace link to @object-ui/layout and one hook import.
  • Not measured locally, and owned by CI: the full farm, pnpm lint and the e2e suites. The page is not browser-verified. The pins render the real components under a real MetadataCtx.
  • CI: the required Spec Main Shape Gate red of objectui#11206 is fixed on main, and this branch carries that fix.
  • At the merged head 6e8f1d83f (under the lock): the closure build, exit 0; type-check for app-shell and plugin-designer, exit 0 each; vitest over metadata-admin/previews/, metadata-admin/inspectors/ and studio-design/ in 6 chunks, 284 files / 3203 tests passed; packages/plugin-designer/, 28 files / 203 tests passed. Lock-free, exit 0: markdown-test-inputs --audit, check-changeset-presence, changeset:check, check:changeset-claims, check:pending-changeset-literals, check-control-bytes, check:new-line-citations (0 new), check:phantom-deps, check:unused-deps.

Acceptance notes

  • AppNavInspector's ensureNavId builds an id from String(node.objectName ?? node.label ?? 'item'). For an entry with no id and no objectName whose label is a locale map, that reads [object Object] and would yield nav_object_object. This is a read-only inference, not reproduced; the spec requires every nav entry to carry an id. Not in scope here.
  • The designer-locale limit above: in a session of a locale other than en / zh, inherited text follows the console language, while the designer resolves authored map labels in English.

objectui#11197 landed as PR objectui#11207 (e6bc087a3), which also edited AppPreview.tsx, AppNavCanvas.tsx and NavigationDesigner.tsx. This branch merged main at 6e8f1d83f:

  • one textual conflict, in the AppPreview.tsx imports, where both lines are kept;
  • one semantic clash: objectui#11197's AppPreview.docEntry-11197 pin now finds a label-less doc entry's text twice (its target, and its inherited label), so its two assertions use getAllByText. The entry is still kept, badged doc, with its target shown.

The parity pin gains three label-less doc entries (with a page, with only a book, with neither). For each, the designer answers exactly what the runtime answers: the page, else the book, else the id.

Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec (domain:ui seat 2, dispatched dev for objectui#11196).


Generated by Claude Code

…ss entry by the runtime's inheritance rule

An entry with no `label` inherits its target's current label at render
time. The designer surfaces read the raw label and showed "unnamed", a
positional "Item N", off-spec `title` / `name`, or a blank for exactly
the entries NavigationSyncEffect now writes.

- navItemLabel.ts gains navEntryLabelText / inheritedNavEntryText, which
  call `resolveNavItemLabel` (no second ladder), and clearedLabel, the
  inverse of renamedLabel; its docblock now says it reads any I18nLabel.
- AppPreview, AppNavCanvas and AppNavInspector resolve an absent label
  through that rule with the console's own `useNavTargetLabel`. The
  canvas rename and the inspector Label field show inherited text as a
  placeholder; emptying the inspector field removes the key.
- nav-selection stops reading `title` / `name`.
- plugin-designer's NavigationDesigner and AppCreationWizard read labels
  through `resolveNavItemLabel`; it now depends on `@object-ui/layout`.

Part of objectui#11196.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…y designer surface, with a runtime-parity pin

- navItemLabel: the designer's text for a label-less entry equals
  resolveNavItemLabel's own answer for every nav type, with and without a
  target resolver; the control keeps an authored label verbatim;
  clearedLabel never writes an empty string.
- AppPreview list and canvas, AppNavInspector, NavigationDesigner and the
  wizard's Navigation step: a label-less entry shows its inherited text,
  an authored label renders verbatim, and editors show inherited text as
  a placeholder.
- Three older pins asserted "(unnamed)" / "Item N" for a truly absent
  label; they now assert the inherited text, and keep the fallbacks for
  what the rule names nothing.
- Changeset for app-shell and plugin-designer.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…e, and reach the wizard's Navigation step by Next

The step indicator only goes back or one step on, so the wizard pin now
presses Next twice (Basic, Objects, Navigation), as the keeps-navigation
pin does. The preview pin's onPatch double is typed as the prop's
function type, which `tsc -p tsconfig.test.json` refused as a bare mock.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…t, so its render settles

The double minted a new client on every call. The inspector's option
loader keys its effect on the client and sets state inside it, so each
render re-ran the effect and the file never finished. The real hook
returns a stable client; the double now does too.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…ry by the runtime's inheritance rule

The rail's NavTree rows read the raw label. A label-less leaf fell back
to its target's internal name, and a label-less group heading was
blank. Each row now reads `navEntryLabelText` with the console's own
`useNavTargetLabel`, so it shows the target's current label, else its
machine name, else the entry's id. The internal identity stays on the
row's tooltip.

`resolveSurface`'s Surface.label is unchanged: the canvas caption, the
breadcrumb and the copilot chip read it, and those are outside this
change.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…that now inherit

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…ct labels to the inheritance rule

The step called `resolveNavItemLabel(item)` with no target resolver, so a
label-less object entry showed its objectName even though the wizard
holds every object's label. The step now passes a host resolver built
from the wizard's own object list. It answers an object's `label`, as
the console's sidebar does, and leaves every other target to the rule's
machine-name rung. `NavigationBuilderStep` is internal, so no published
prop changes.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
… ObjectSelection requires

A listed object that is not selected has its nav entry dropped when the
Objects step is left (the wizard's deselection rule), and `selected` is
a required key, which `tsc -p tsconfig.test.json` refused.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dfbf137fa5b615a29a470ca4bf4cf150521e8729
Local-runs: none

Inputs: card #11196 (body; comments 5909012131, 5909773493, 5909998189, 5914143054), PR #11224 (body, its 19 files, the three-dot diff against main), the head's check-runs with filter=latest (polled 15:17Z, 15:25Z and at post time), card #8219 with its 13 comments for ③.1, and contents reads at main and at the head for context. Nothing built, run or re-run.

① Derived judgments

1. The head carries no gate verdict, and cannot: the PR does not merge into main. dfbf137fa has 0 check-runs at every poll. The five suites GitHub lists on it (vercel, fly-io, claude, cloudflare-workers-and-pages, objectstack-fleet) are app placeholders, each queued with 0 runs; the only commit status is Vercel. The branch has 0 workflow runs at ANY of its heads, where a PR head in this repo normally carries 45 github-actions runs (PR #11222's head 573373f93, for one). The cause is on the PR record: mergeable: false, mergeable_state: dirty, merge_commit_sha: null. The branch's merge base with main is 340dc718f (13:24Z); main is 7 commits past it, and e6bc087a3 (PR #11207, objectui#11197, 13:42Z) edits three files this PR edits: AppPreview.tsx, AppNavCanvas.tsx and NavigationDesigner.tsx. In AppPreview.tsx both sides touch the same import lines (this PR rewrites the resolveHref import and inserts after the resolveI18nLabel line; main inserts NavigationItemType between them), so the merge is dirty and a pull_request workflow does not run. The two base merges brought only main's own changes: a0f4a0de1 (second parent 9419df198, objectui#11206's fix) and 62e7aff21 (second parent 340dc718f) both merge main commits, and the three-dot diff lists exactly the 19 files with only this PR's hunks; but the main they merged was fetched before 13:35Z, so PR #11207 is not in the branch. Every derived gate family (Type Check, Lint, the 8 test shards, dist pins, Build and E2E, Changeset Declaration / Bump Policy / Fixed Group / Claim Re-read, Spec Main Shape Gate, Bundle Analysis, README Export Check, Pre-Install Import Graph, Control Byte Scan, Line Citation Gate) is unanswered at this head; the dev's local readings are the only evidence, and this brief makes the check-runs the verdicts. Wrong, and it decides the verdict. What clears it: merge main, resolve AppPreview.tsx (after the merge its NavKind is the spec-derived NavigationItemType, which has doc; the parity pin's LABEL_LESS list should gain a doc entry, whose runtime arm answers doc else book), push, let the roster complete, and a record on that head re-judges.

2. Each surface calls resolveNavItemLabel; no ladder is restated. Read hunk by hunk:

  • AppPreview list and landing line: normalizeNav asks navEntryLabelText(it, locale, targetLabel); the existence test keeps reading the AUTHORED label (navItemLabelText, the same resolveI18nLabel ?? '' it read before), so inherited text never lists an entry with nothing to open; label || unnamed applies only after the rule answered ''. The landing line derives from the same navItems (→ Leads pinned). Right.
  • AppNavCanvas card and inline rename: navLabel asks navEntryLabelText; the positional engine.appNav.item row is reached only on '' (a separator, an empty map, no target and no id). The rename input's state is the AUTHORED text (navItemLabelText(item.label)), placeholder={label} is the inherited text, blur writes only when v && v !== authored, Escape resets to the authored text. An untouched entry writes nothing (pinned). Right.
  • AppNavInspector title and Label field: labelOf = navEntryLabelText(...).trim() || selection.id; the field's value is navItemLabelText(node.label, locale) and its placeholder is inheritedNavEntryText(node, targetLabel); InspectorTextField takes placeholder (_shared.tsx). commitLabel('') goes to clearedLabel, and undefined becomes patch({}, ['label']), which DELETES the key: never ''. A map loses only the designer locale's own entry (exact tag, else bare language; an entry reached only as a fallback is kept), and a map with no text left clears to no key. NavItem.title / name are gone from the type and the reads. Right.
  • nav-selection.ts: findNavPositionById returns label only for a plain-string authored label; the title ?? name rungs are gone. Its two callers select by hit.selectionId; ResourceEditPage.tsx also forwards hit.label into the selection, which for a spec-valid label-less entry was already undefined (only an off-spec title / name document loses that rung), and the inspector now titles the entry from the entry itself. Right.
  • Studio rail NavTree rows: the group heading and the leaf both read navEntryLabelText(node, locale, targetLabel); the leaf's || surface?.name is reached only on ''; the tooltip keeps type · name (objectui#7254's reachable half; pinned as the accessible description). Right.
  • plugin-designer NavigationDesigner rows and live preview: resolveNavItemLabel(item) itself, and the rename input's placeholder is the same call. For an authored label the answer equals the old resolveKeyedI18nLabel(item.label): a string is itself; a keyed label is defaultValue || key; with no resolvers resolveNavItemLabel returns base. So the control is unchanged. Right.
  • AppCreationWizard Navigation step: NavigationBuilderStep builds targetLabel from draft.objects (name → label) and hands it as the rule's sixth argument; it answers object targets and undefined for everything else, leaving the ladder to the rule. That is a NavTargetLabelResolver implementation, the host's answer to "what is this target called", not a second resolver. Right.
  • Ladder census on label-less paths: || unnamed (AppPreview), tFormat('engine.appNav.item') (canvas), || selection.id (inspector), || surface?.name (rail) each apply only after the rule answered ''. No label ?? title ?? name ?? id remains. Right.

3. useNavTargetLabel over a designer-locale twin. Measured right: useMetadataLocale() is /^zh/i.test(language) ? 'zh-CN' : 'en-US' over the same useObjectTranslation().language the hook resolves in, and the hook is memoised on [metadata, objectLabel, viewLabel, dashboardLabel, language], so the useMemo in AppPreview holds. The direction's first option names exactly this (resolveNavItemLabel with the shared useNavTargetLabel). The residual for the eight other locales is acceptable and disclosed: the designer already draws its chrome and authored map labels in en-US for those sessions, so the language mix predates this PR, and the inherited text is by construction what the console draws, which is the parity the card asks for. A twin later is a one-argument swap at each call site; it does not change this PR's shape. Right.

4. navItemLabel.ts extended, not forked. inheritedNavEntryText calls resolveNavItemLabel({ ...entry, label: undefined }, undefined ×4, targetLabel); the typeof text === 'string' ? text : '' guard covers a loosely typed draft with no id (the runtime's last rung is item.id). navEntryLabelText keys on entry.label !== undefined, as the runtime keys on item.label === undefined; a present-but-empty map answers '' and is not treated as absent, matching the runtime's arm. clearedLabel is renamedLabel's inverse through the shared ownEntryKey; renamedLabel keeps its ?? locale.trim() fallback, so its behaviour is unchanged. The docblock carries the carrier rider (5909012131): navItemLabelText reads any I18nLabel; the rename was rightly left, since it would touch StudioDesignSurface.tsx and ObjectActionsPanel.tsx. The module is not exported from the package root (packages/app-shell/src/index.ts exports the metadata-admin pages only). Right.

5. plugin-designer's @object-ui/layout dependency. dependencies, workspace:*; the lock gains only the importer link. Layout depends on components, core, react, types, spec, dnd-kit and lucide; none reaches plugin-designer. Cycle-free, not inlined (the dev's measured 1 external import / 0 inlined ladders is consistent with a vite build that externalises bare specifiers). Right; the level is judged in ②.

6. engine.appPreview.unnamed and engine.appNav.item now cover only what the rule names nothing (a separator, an empty map, no target and no id); the three re-judged pins (AppPreview.labelMaps-11100, AppNavCanvas.labelMaps-11128, AppPreview.test.tsx) say so and keep the fallbacks for those cases. No hunk touches i18n.ts; no key added or removed. Right.

7. Ablations. A to D at 2a6f0c5fb (app-shell source unchanged after it; NavigationDesigner.tsx unchanged after it), E at dfbf137fa (1 of 4 red on Contact), the void E at d78e28afd correctly discarded. Each leg mutates the surface its pin covers. Right in shape, but every reading is the dev's own: with finding 1 no gate has confirmed a single pin green on this head.

② Semver level

Changeset 11196-designer-nav-label-inherit.md: @object-ui/app-shell: patch, @object-ui/plugin-designer: patch. No exported surface moves: navItemLabel.ts, nav-selection.ts, the inspector and the previews are not root exports of app-shell (they reach the registry through register-builtins.js, unchanged); NavigationDesignerProps and AppCreationWizardProps are unchanged, the wizard's new objects prop sits on the internal NavigationBuilderStep, and the rename input gains DOM attributes only. The new package-graph edge (plugin-designer → layout, a fixed-group sibling of the 39) is disclosed in the changeset's last line; the repo's policy forbids only major (AGENTS.md: the fixed group publishes together; check-changeset-presence deliberately leaves dependencies unguarded), so patch is admissible. Noted for the seat, not a finding: @object-ui/layout is sideEffects-true, so a host that imports plugin-designer without layout now also runs layout's registry registrations; the console already ships layout. Clause-②: no — right: no schema, prop, type or accepted set moves, stored nav documents are unchanged, and the PR body carries the line.

One defect in the changeset text (in the diff, and it ships in the CHANGELOG): its lines 27 to 29, "The Navigation Designer and the wizard are handed bare items and no metadata, so they show the rule's machine-name rung (pageName, dashboardName, objectName, …), which is what the console shows for a page." Since d78e28afd the wizard's Navigation step hands the rule a resolver built from its own object list, and a label-less object entry shows the object's label (pinned: Contact). The sentence is false for the wizard. The dev's deviation flagged the PR body only; the changeset carries the same sentence and must say that NavigationDesigner shows the machine-name rung while the wizard's Navigation step inherits each object's label from the wizard's object list. Wrong.

③ Boundary flags

  1. navSurface.ts resolveSurface listed in the claim, left unchanged (open question 1). Reading of objectui#8219 item 3, from its thread: the RULING is seat answer 5837066449 applying objectui#7254 — the chip reads the item's LABEL, and type · name stays reachable on the tooltip. The type · name TEXT for an unlabelled leaf is the pre-existing fallback that StudioDesignSurface.aiChipLabel.test.tsx records ("an unlabelled leaf: the chip falls back to type · name"), and the 8219 seat declined to change the unlabelled-leaf fallback because it would revisit Studio 工作台把内部 ID(app.b2r4 / customer_dashboard)与整块英文(属性面板、AI 工具卡)摆给中文客户;工具卡头部状态与卡体徽章不一致 #7254 (5836130195, item 4: "not a seat call"). Since then triage 5909773493 ruled for THIS card, above seat level, that every designer surface resolves an absent label through the runtime's rule, and the card's SEAM list names navSurface.ts (resolveSurface). So item 3 is a pin, and the unlabelled-leaf question is already re-ruled for the designer surfaces. Reading: A — resolveSurface takes an optional NavTargetLabelResolver, its callers in StudioDesignSurface.tsx (NavTree, the load effect's first-leaf pick, the ?surface= restore) pass useNavTargetLabel, and the aiChipLabel unlabelled-leaf case is re-pinned to the inherited text with type · name on the title, which keeps Studio 工作台把内部 ID(app.b2r4 / customer_dashboard)与整块英文(属性面板、AI 工具卡)摆给中文客户;工具卡头部状态与卡体徽章不一致 #7254's reachable half. Note for that re-pin: it clicks the rail row by the name b2r4_customer 对象, which under this PR is the inherited label whenever metadata labels the object; the pin has no MetadataProvider, so it still resolves to the machine name today. The dev was right to stop: the callers are outside "NavTree rows only". Serial after PR fix(app-shell,plugin-designer): standard nav entries are stored label-less, never with the target's machine name (objectui#11201, stage 1) #11225, which edits StudioDesignSurface.tsx.
  2. StudioNavItemInspector (open question 2). Reading: A, after PR fix(app-shell,plugin-designer): standard nav entries are stored label-less, never with the target's machine name (objectui#11201, stage 1) #11225 lands (it edits StudioDesignSurface.tsx and AppCreationWizard.tsx, both also edited here; whichever lands second merges main). Its '' write through renamedLabel, its missing placeholder and its bind storing the object's label are the same defect class this PR fixes in AppNavInspector, with the same module functions (clearedLabel, inheritedNavEntryText); two editors of one label must agree, which is navItemLabel.ts's stated purpose. Not a new card.
  3. addItem's "New item" birth label (open question 3). Reading: A as a follow-up after PR fix(app-shell,plugin-designer): standard nav entries are stored label-less, never with the target's machine name (objectui#11201, stage 1) #11225, seat-level: a stored birth sentinel is exactly what inheritance replaces; B is sentinel matching, which the runtime arm refuses for a label spelled like a machine name; the finding(app-shell): Studio's Interfaces nav autosave drops edits it has shown — "Done" inside the debounce leaves the edit unsent, and an Add while a save is in flight is silently lost (the objectui#11124 / objectui#11136 family's nav closure card) #11189 autosave pins that count New item saves are the seat's to re-rule. Rightly not done here.
  4. NavigationDesigner with no metadata (open question 4). Reading: A (leave). The direction accepts the rule's own answer for a host that supplies no resolver, and no producer in the repo renders navigation-designer with app metadata. B is a published prop (Clause-②: yes) for a card of its own when a host appears.
  5. The PR body is false at this head (objectui squash-merges with it). The false sentences, exactly:
    • "This designer is handed bare items and no metadata, so it shows the rule's machine-name rung." (the plugin-designer bullet, read for the package's two surfaces: false for the wizard step; the two sentences after it hold for NavigationDesigner only);
    • "The only later commit, 693024cc2, edits the changeset alone; the gates that read it were re-run there (last group below)." (d78e28afd and dfbf137fa follow it and edit plugin-designer source and its pin);
    • "objectui#11197 holds NavigationRenderer.tsx and edits NavigationDesigner.tsx's pin-lag type key; this PR edits only that file's label reads. The two regions are disjoint." (PR feat(types,layout): a doc nav entry validates and draws as a link into the docs portal (objectui#11197) #11207 also edited AppPreview.tsx and AppNavCanvas.tsx, and the branch now conflicts with main, ①.1);
    • "Known external red: none expected." (there is no CI on this head at all).
      Table row 7 ("AppCreationWizard's Navigation step … the rule's own answer") is not false but should split the wizard (inherits each object's label from the wizard's own list). Ablation E belongs in the ablation list (1 of 4 red at dfbf137fa, the Contact assertion), and the plugin-designer re-measure at dfbf137fa (28 files / 203 tests, type-check 0) belongs in the Tests section. The changeset fix is ② above.
  6. The 43-minute lock hold. The fixed double is stable in the code: client is a vi.hoisted singleton returned by the useMetadataClient mock; useMetadataOptions's effect deps are [client, type, rowFilter], where type is a constant per nav type and rowFilter is the module-level isStaticPageOption or undefined; the MetadataCtx value is a module-level constant, so useNavTargetLabel's memo is stable too. The render settles. Confirmed.
  7. ensureNavId nav_object_object (carrier: none). Confirmed in inspectors/nav-target.ts: with no id, no objectName, a map label and no seed, String(node.label) is [object Object], which slugs to object_object. Pre-existing and untouched here; the spec requires an id; a p3 card or a carrier note is the seat's call. Not a finding against this PR.
  8. Also answered: Part of #11196, not Fixes — right, two SEAM sites remain (③.1, ③.2). plugin-designer/package.json and pnpm-lock.yaml outside the claim's list — right, the claim's direction (call resolveNavItemLabel from plugin-designer) implies the dependency, and the lock gains only the link. Commit 7906f5f19's prose "Part of objectui#11196." — the squash uses the PR body; no trailer; nothing to do. The serial note for the seat: PR fix(app-shell,plugin-designer): standard nav entries are stored label-less, never with the target's machine name (objectui#11201, stage 1) #11225 and this PR both edit StudioDesignSurface.tsx and AppCreationWizard.tsx.

Findings that decide the verdict: (1) no gate ran on dfbf137fa because the PR conflicts with main (①.1); (2) the changeset's wizard sentence is false at this head (②). Everything else judged above is right.

Implemented-by: claude/issue-11196-designer-nav-label-inherit
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: FAIL


Generated by Claude Code

…11196-designer-nav-label-inherit

Conflict, one hunk in AppPreview.tsx's imports: this branch added
`type NavTargetLabelResolver` to the `@object-ui/layout` import, and
main added `import type { NavigationItemType } from '@object-ui/types'`.
Both are kept.

Semantic reconciliation:
- AppPreview.docEntry-11197 pinned a label-less `doc` entry's target text
  with getByText. The entry now also reads that text as its inherited
  label, so both assertions count two matches. #11197's intent (kept,
  badged, target shown) holds.
- The parity pin gains `doc` entries (page, book-only, neither), and the
  designer follows the runtime's merged doc rung: doc, else book, else id.
- The changeset no longer says the wizard shows the machine-name rung:
  its Navigation step inherits each object's label from its own list.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 4 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5793-spec-range-floors.md

  • names pnpm-lock.yaml → pnpm-lock.yaml — edited by this change

    Nothing a consumer installs today changes: normal resolution already picks the newest 17.x, and pnpm-lock.yaml still resolves 17.2.0 on this edge after the bump. The change is to the declared floor only, which is why it is scored patch rather than minor — the same reasoning objectui#5753 used for the other direction on this dependency.

.changeset/6361-spec-floor-17-2-0.md

  • names pnpm-lock.yaml → pnpm-lock.yaml — edited by this change

    Nothing a consumer installs today changes: normal resolution already picks the newest 17.x, and pnpm-lock.yaml still resolves 17.2.0 on both edges after the bump — only the recorded specifier: moves. No source and no behaviour changes, which is why this is scored patch, on the reasoning 111741454 used for the same remediation on @object-ui/plugin-detail.

.changeset/6681-declared-lazy-marketplace-routes.md

  • names views/studio-design/StudioDesignSurface.tsx → packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx — edited by this change

    Both marketplace chunks were held by CHUNK CO-TENANCY, not by any import of the page: rolldown had parked components/SuggestedBindingsPanel.tsx (statically imported by the eager views/studio-design/StudioDesignSurface.tsx) in the first, and console/marketplace/InstalledListWidget.tsx (bare-imported by the package barrel for its SDUI registration) in the second. An app-shell-eager-leaves group in apps/console/vite.config.ts isolates those co-tenants so the three declared-lazy pages chunk by their own dynamic-only reachability. The console eager closure moves 3180.2 KB → 3171.5 KB gzipped (−8,888 bytes, 48 → 45 eager chunks) with the three per-chunk ceilings unmoved.

.changeset/7122-objectstack-family-17-3-0.md

  • names pnpm-lock.yaml → pnpm-lock.yaml — edited by this change

    @objectstack/client, core, formula and lint each pin @objectstack/spec EXACTLY, so resolving the spec alone to 17.3.0 left the console bundling TWO copies of it. Moving the family with it in pnpm-lock.yaml collapses the duplicate; every declared range already admitted 17.3.0, so no manifest moved.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with dded788ad (merge-base with origin/main): 19 file(s) changed outside .changeset/, read against 1818 pending declaration(s) that publish a body (2428 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3576.9 KB 3607.4 KB
Main entry chunk (gzip) 149.7 KB 350 KB
Entry file index-CYI5xM77.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 569.07KB 136.05KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.19KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 216.08KB 44.49KB
plugin-detail (index.js) 242.01KB 63.55KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.44KB 15.11KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6e8f1d83f68176a1d1a53ef2e8ce20833df3a806
Local-runs: none

Re-record after 5914452475 (FAIL on dfbf137fa). Inputs: card #11196 (body and its 4 comments, unchanged since), PR #11224 (the corrected body, its 20 files, the three-dot diff against main at this head, and the gate comments 5914512282, 5914533377), the head's check-runs with filter=latest (read 16:04Z: every run already complete, so no wait was owed), and contents reads at main and at the head. Nothing built, run or re-run. Everything not named below stands as judged in 5914452475.

① Derived judgments

1. Gates on the head — complete, none red. 6e8f1d83f carries the full 45-run github-actions roster, all completed: 42 success, 3 skipped — Test (coverage), Test (coverage shard ${{ matrix.shard }}/4) and dependabot, the same three that skip on every PR head (PR #11222's 573373f93 shows them identically); no failure, neutral, timed_out or action_required. Named green among them: Type Check, Lint, Test and its 8 shards, Test (dist pins), Build & E2E, Live E2E (informational), Spec Main Shape Gate, Changeset Declaration / Bump Policy / Fixed Group Check / Overwrite Report / Claim Re-read, Lockfile Integrity and Dedupe, Pre-Install Import Graph, README Export Check, Bundle Analysis (Console Performance Budget PASS, 3576.9 KB of 3607.4 KB), Control Byte Scan, Line Citation Gate, Governed Surface Queue Guard. The PR reads mergeable: true, mergeable_state: clean, merge commit 8584500b0. Finding 1 of 5914452475 is cleared. main has moved one commit since the merge (c021b3529, 5 files, none this PR touches).

2. The merge brought only main's own changes plus the named resolutions. 6e8f1d83f has parents dfbf137fa (the reviewed head) and dded788ad (main's head at the time, now the merge base). The three-dot diff at this head differs from the one at dfbf137fa in exactly five places: (a) the changeset's wizard sentence (②); (b) the AppPreview.tsx import hunk, which now carries main's import type { NavigationItemType } from '@object-ui/types' as context — both sides kept, the head reads resolveHref, type NavTargetLabelResolver on one line and NavigationItemType on the next, with main's NavKind = NavigationItemType and NAV_KIND_SET (which has doc) intact below; (c) AppPreview.docEntry-11197.test.tsx, two getByText turned into getAllByText(...).toHaveLength(2) (③ below); (d) the parity pin's three doc rows and three assertions (①.4); (e) hunk line offsets in AppNavCanvas.tsx, AppPreview.tsx and NavigationDesigner.tsx from main's own edits. No source hunk of this PR changed by a byte. Right.

3. The docEntry-11197 pin change — a double match is right. A label-less doc entry with only a book names itself crm_manual by the rule's rung (doc else book), and the preview row also prints its target crm_manual (navTarget: doc else book). That is the two-cell shape every machine-name-rung entry already has in this PR's own pins (home_page twice, lead twice in the list; account twice on the canvas): the preview's target column predates this PR, shows for every row, and reads as "named by its target". Suppressing the label when it equals the target would be a second rule — a sentinel comparison — which the direction forbids and the runtime's authored-label arm refuses. objectui#11197's intent holds: the entry is kept, badged doc, its target and portal route shown, and it is never the landing. The canvas twin (crm_lead_guide twice: the card's text and its target chip) reads the same. Right.

4. The three doc parity rows against the runtime. NavigationRenderer.tsx at main, inheritedNavItemLabel's doc arm: item.doc, else item.book, else (falls out of the switch) item.id. The pin's rows answer crm_lead_guide for { book: 'crm_manual', doc: 'crm_lead_guide' }, crm_manual for { book: 'crm_manual' }, and nav_doc_empty for a bare doc entry — the arm's three rungs in order. They sit inside LABEL_LESS, so the PARITY case compares navEntryLabelText and inheritedNavEntryText with resolveNavItemLabel for each, with and without the resolver (which the rule never asks for a doc). After the merge a label-less doc entry is listed by AppPreview (its target exists) and named by the rule. Right.

5–7. Unchanged. The surface-by-surface reading (①.2), the useNavTargetLabel choice (①.3), navItemLabel.ts (①.4), the layout dependency (①.5), the two i18n rows (①.6) and the ablations (①.7) stand as in 5914452475. The ablation legs were not re-run at the merged head; acceptable, because their subject hunks are byte-identical here and the Test shards now confirm every pin green — the first gate to do so.

② Semver level

Unchanged: @object-ui/app-shell: patch, @object-ui/plugin-designer: patch, admissible on the reading in 5914452475 ②; Clause-②: no — right, and the body carries the line. The changeset defect (finding 2) is corrected: the head's text reads "The app wizard's Navigation step hands the rule its own object list, so a label-less object entry there shows the object's label. The Navigation Designer is handed bare items and no metadata, so it shows the rule's machine-name rung (pageName, dashboardName, objectName, …), which is what the console shows for a page." True at this head. Changeset Declaration, Bump Policy, Fixed Group Check and Overwrite Report are green; Claim Re-read (5914512282) names four pending changesets — 5793, 6361, 7122 on pnpm-lock.yaml and 6681 on StudioDesignSurface.tsx's chunk co-tenancy — and each paragraph re-read is still true: the lock gains only the workspace link, and the one new static import in StudioDesignSurface.tsx is app-shell's own hook, with the eager closure inside budget. Right.

③ Boundary flags

1–4. Open questions 1–4: the readings in 5914452475 ③.1–③.4 stand, and the seat's answers now sit in the PR body's Remainder — resolveSurface inherits (A, the objectui#8219 chip pin re-judged), StudioNavItemInspector follows AppNavInspector's shape (A), addItem births label-less (A, the objectui#11189 autosave pins re-judged), all as this card's next slice after PR objectui#11225; NavigationDesigner stays on the machine-name rung. Consistent with the readings.
5. The PR body, re-read in full. The four sentences named false in 5914452475 are gone: the plugin-designer bullet now separates the Navigation Designer (machine-name rung) from the wizard step (its own object list); table row 7 is split into rows 7 and 8 the same way; "The only later commit" is replaced by the list 693024cc2, d78e28afd, dfbf137fa, 6e8f1d83f; the objectui#11197 "disjoint regions" sentence is replaced by the Acceptance-notes paragraph naming PR objectui#11207's three files, the one textual conflict (both import lines kept) and the one semantic clash (the docEntry-11197 pin); "Known external red: none expected" is replaced by the Spec Main Shape Gate sentence, which is true (green). Ablation E is in the list, and the merged-head re-runs are their own group. I find no remaining false sentence. Two true-but-dated lines, not findings: the root scripts/__tests__/ reading lives in the dev report (5914143054: 177 files / 5371 tests at 693024cc2), and the ablation legs are dated 2a6f0c5fb / dfbf137fa (①.5–7).
6–8. Unchanged: the fixed test double (stable), the ensureNavId inference (confirmed, pre-existing, carrier: none), Part of not Fixes, the manifest and lock outside the claim, commit 7906f5f19's prose line, and the serial note that PR objectui#11225 also edits StudioDesignSurface.tsx and AppCreationWizard.tsx — all as in 5914452475.

Findings: none. Both findings of 5914452475 are cleared at this head, and the merge added nothing beyond what it names.

Implemented-by: claude/issue-11196-designer-nav-label-inherit
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 16:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 8741cb7 Sep 30, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11196-designer-nav-label-inherit branch September 30, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants