Skip to content

question(layout): does the spec's "present label ⇒ rendered verbatim" retire the nav renderer's convention that translates a PRESENT label equal to its target's name through the i18n bundle? #11201

Description

@objectstack-fleet

Ruled: 5921167691 · letter B (stage 2) · 2026-09-30T23:02Z

Filing-gate category: a contract-meaning question the seat cannot answer. It is carried from PR objectui#11186's contract reviews (5907554475 ③ and 5908819769 ③), both of which called it "a maintainer question the seat carries". ⛔ Not a defect report, and ⛔ not graded here.

The two texts that may disagree:

Why it is a question, not a bug.

  • Ruling A refused option B, "a machine-name sentinel: a label equal to the target name counts as unnamed", for inheritance, because it silently replaces an author's deliberate machine-name label.
  • The isCustomized guard has the same trigger (label equals name), for translation rather than inheritance.
  • So one reading makes it the refused sentinel in another form. The other reading makes it an orthogonal localisation layer: the server-side /meta boundary owns app-navigation localisation (translateApp), and this guard exists for standard entries whose label was never customised.
  • Retiring it changes what every standard nav entry renders in a non-English console. Keeping it means "verbatim" has a documented exception.

Options, for triage to route (the decision box if it is a contract call):

  • A. Keep the convention, and state it as a declared exception in the spec's label describe: "verbatim, except a label equal to the target name, which localises".
  • B. Retire it. A present label is verbatim, full stop. Standard entries that should localise leave label absent and inherit, which now works (objectui#9868), and useNavigationSync no longer writes the machine name.
  • C. Move it to the /meta boundary (translateApp), where app-navigation localisation already lives. The renderer then keeps no rule of its own.

Reader: triage. Filed by domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec) at the 10:32Z fire. It was held over from PR objectui#11186's ACCEPT (5908872194), which hit that fire's three-card filing cap.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    分诊:路由到维护者决策箱 — needs-user-decision · priority:p2 · domain:ui。建议 B(退役这条约定),但先做存量普查

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T10:59Z. ⛔ Not a claim, ⛔ not a dispatch.

    一句话问题。 spec 规定导航项“写了 label 就原样显示、永不改写”。但渲染器有一条老约定:label 恰好等于目标机器名时(比如写的是 account),不原样显示,而是去 i18n 里翻译。两者冲突。改哪边,会改变每个非英语控制台的标准导航怎么显示,所以交维护者拍板。

    Governing text

    选项 × 客户看得到的后果

    选项 做什么 客户看到什么
    A 保留并写进 spec spec 的 describe 加一句例外:“等于目标名则本地化” 显示不变;但“原样显示”从此带一个例外,AI 很难学会
    B 退役(荐) 写了 label 就原样显示,没有例外。需要本地化的标准导航项不写 label,靠继承(objectui#9868 已经可用:继承的是目标当前的、已本地化的名字)。同步逻辑不再写入机器名 新建的导航项照常本地化;存量里“label = 机器名”的项,在非英语控制台会显示成 account,除非先迁移
    C 挪到 /meta 边界 由服务端的 translateApp 统一做本地化,渲染器不再有自己的规则 显示不变;例外还在,只是换了位置

    业务直译

    • A:规则写成“原样显示,除了……”。
    • B:规则只有一条:“写了什么就显示什么;想跟着目标名走就别写”。
    • C:换个地方继续做翻译。

    四轴

    • 长远: B 让一条规则管到底,这正是裁定 A 否决哨兵时的立场;A 和 C 都留下一个“名字相等就特殊处理”的特例。
    • 拉动: 存量里有多少“label = 机器名”的导航项、分别是谁写的,没有测过。平台自己的同步逻辑写过机器名,作者也可能有意这么写。
    • 防 AI 犯错: B 最好学(写了就显示,不写就继承);A 要求 AI 记住一个例外。
    • 不扩散: B 删掉一条规则;A 往 spec 里加一条例外;C 搬动一条规则。

    建议:B,分两步走。

    1. 先止新增:同步逻辑不再写机器名 label(不写就走继承,显示照样本地化)。
    2. 对存量做一次普查:“label = 目标名”的导航项有多少、分别是谁写的。
      • 平台写入的:用一次性 ADR-0087 转换改成“不写 label”,并逐项通知。这和裁定 A 否决的运行时哨兵不同:哨兵是每次渲染都静默替换;这里是一次、公开、可复核的转换。
      • 作者写入的:原样保留。
      • 然后退役这条约定。

    回退: C。

    自检: 只看①选 B;②③④ 是否翻转:否(②只影响执行顺序:先普查)。

    置信缺口: 没做存量普查;平台写入和作者写入能不能在存储上区分开,还没有核实。如果区分不了,第 2 步就要请维护者再定一次。

    finding 在路由时摘掉。

  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 30, 2026
  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling recorded: B. A present label renders verbatim, with no exception. needs-user-decision → pm:queue for stage 1 (stop producing, plus the census). Stage 2 is routed on the census

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T11:37Z. ⛔ Not a claim, ⛔ not a dispatch.

    Provenance. In the live PM chat with the triage seat, the maintainer answered 「11201 B」 to the analysis 5909828367, as presented: B, carried out in two steps, with the census first.

    The ruling

    • One rule, the spec's. A present label renders verbatim and is never overwritten. An absent label inherits its target's current label at render time, and that inherited text is what localises.
    • The renderer's translate-if-equal-to-name convention (resolveNavItemLabel's isCustomized guard, packages/layout/src/NavigationRenderer.tsx:369 on objectui main 846cec0efe) is retired.
    • A (declare the exception in the spec) and C (move it to /meta's translateApp) are not taken.
    • The spec needs no change: its describe already states the rule.

    Stage 1: this card, now (domain:ui)

    1. Stop producing machine-name labels. useNavigationSync (packages/app-shell/src/hooks/useNavigationSync.ts) and any other objectui producer write an absent label for a standard entry, never the target's machine name. Display does not change: the absent label inherits the target's label, which already localises.

      • Pin: a synced standard entry is stored label-less and renders its target's localized label under en and zh.
    2. The census, reported on this card before any conversion. For stored app navigation, count the entries whose label equals their target's machine name (trimmed, case-insensitive, the guard's own test), split by who wrote them:

      • the platform's sync;
      • apply_blueprint;
      • an author.

      The report must also say whether the stored row can tell these apart: a provenance mark, a sync signature, or history. Measure what the repos and a seeded console can show. Where production is needed, name it as not measured.

    3. Check for a twin. Check whether /meta's translateApp (objectstack) applies the same equal-to-name rule. If it does, it is the same convention and retires with it. Report it, and triage routes the objectstack half.

    ⛔ Stage 1 does not remove the guard. Removing it before the census would switch every stored machine-name entry to its raw name in non-English consoles.

    Stage 2: routed by triage on the census

    • If platform-written entries are distinguishable, they get a one-time ADR-0087 conversion to label-less, with a per-entry notice. Author-written entries stay verbatim. Then the guard is retired, with the pins:
      • a present label equal to the target name renders verbatim;
      • an absent one inherits and localises.
    • The conversion lives in objectstack's conversion registry (domain:spec), so triage files that child from the census.
    • If they are not distinguishable, the card goes back to the maintainer: that is the confidence gap the analysis named, and ⛔ the seat does not guess.

    Related. objectui#11198 (the skills guide) is unaffected: its sentence states only absent ⇒ inherit. It may add "a present label is shown verbatim".

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Execution note: B retires only the text-match rule. Identity-keyed translation stays. Maintainer: 「同意」 to 「11201 按此」

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T11:45Z. ⛔ Not a claim, ⛔ no state change. This refines the ruling record 5910404448.

    Provenance. The maintainer asked whether "if there is an i18n translation, use it; otherwise fall back to the metadata label" is right. Triage answered that it is the platform's existing model, compared it with mainstream practice, and proposed this note. The maintainer answered 「同意」.

    What B retires, and what it keeps.

    • Retired: the renderer's isCustomized guard, which translates a present label because its text equals the target's machine name. Keying a translation on text cannot tell a deliberate account from a machine-written one.
    • Kept, untouched: the two identity-keyed routes.
      • The /meta boundary's translateApp (objectstack packages/spec/src/system/i18n-resolver.ts:1153) replaces a node's label with apps.<app>.navigation.<id>.label for the active locale chain, keyed by the node's id.
      • label's inline locale map (I18nLabelSchema).
      • ⛔ Stage 1 and stage 2 must not remove, bypass or duplicate either one.

    The one resolution order (the spec states it via objectstack-ai/objectstack#20849):

    1. Label present: the bundle entry for this entry's id in the active locale chain; else the inline map's value for that locale; else the text as authored. ⛔ Never replaced by the target's label. ⛔ Never translated by matching its text.
    2. Label absent: the target's current label at render time, localized by the target's own translation; else the target's machine name.
    3. The renderer and the designers read the resolved text. ⛔ They keep no rule of their own.

    This matches mainstream practice (compared from product knowledge, not re-verified online): Dataverse site-map titles per LCID with a base-language fallback, and an absent title showing the entity's localized plural name; Salesforce's Translation Workbench keyed by component, falling back to the master label; Odoo's per-record translatable menu names; SAP Fiori's keyed resource bundles. All key translations by element identity. The text-keyed exception, gettext's msgid, needs msgctxt precisely because equal text can mean different things.

    Consequence for stage 2. A stored entry whose label equals its target's machine name keeps its localized rendering by one of two routes:

    • converted to label-less, so it inherits the target's localized label;
    • or given an id-keyed bundle entry.

    The census decides which applies to which entries, and triage routes it on the census.

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11201-stop-machine-name-labels
    Worktree: objectui-issue-11201
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (ruling B, recorded in 5910404448, refined by the execution note 5910537969; stage 1 only):

    • Step 1: packages/app-shell/src/hooks/useNavigationSync.ts and its callers, plus any other objectui producer that writes a standard nav entry's label as the target's machine name. Each writes an ABSENT label. Pin: a synced standard entry is stored label-less and renders its target's localized label under en and zh.
    • Step 2: the census, reported on this card as a reading. No code is needed for it; any probe script lives under the scratchpad, not in the tree.
    • Step 3: the twin check of objectstack's /meta translateApp. It is a reading only; ⛔ no objectstack edit.
    • ⛔ Not packages/layout/src/NavigationRenderer.tsx: stage 1 does not remove the isCustomized guard, and objectui#11197 (seat 1) holds that file.
    • packages/plugin-designer/src/NavigationDesigner.tsx / AppCreationWizard.tsx: label WRITE sites only, and only outside objectui#11196's pushed hunks (that card edits their label reads). If a write site sits inside those hunks, stop and report it.
    • Pins beside each producer, and one changeset.
      Stop on breach; explain in the report.
      Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates has no objectui derivation, so there is no path-derived mandate; objectstack 41dcf118)
      Clause-②: no
      Why no: producers stop writing a present label where an absent one renders the same. No schema, prop, type or accepted set moves, and stored entries are not converted.
      Thread-read: 5910537969
      Serial constraints cleared: read 2026-09-30T13:19Z at objectui origin/main 0389650f3.
    • objectui#11196 (this seat, in flight) edits NavigationDesigner.tsx / AppCreationWizard.tsx's label reads and adds a @object-ui/layout dependency to plugin-designer. This card stays out of those hunks.
    • objectui#11197 (seat 1, PR objectui#11207) holds NavigationRenderer.tsx and app.zod.ts. This card touches neither.
    • No open PR names useNavigationSync.ts.

    Generated by Claude Code

  6. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from objectstack: the spec half of ruling B is in the merge queue, and one renderer gap is named for stage 2 · 2026-09-30T14:43Z

    domain:spec seat 5 of objectstack (session_01Sfe5YjBLwB9J3y8fvm2xq1). ⛔ Not a claim; no state change here.


    Generated by Claude Code

  7. 9 remaining items

  8. removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 30, 2026
  9. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #259 item 4 · letter B · maintainer 「同意」 2026-09-30T22:58Z

    Director seat, summon #31, session_01PGMD6TRDaZY8Ubyo7Ukm66 (GitHub os-litant; written as objectstack-fleet[bot] via the relay). Stage 2 of ruling B (5910404448, refined by the execution note 5910537969): the stage-1 census (dev report 5913610243, ACCEPT 5913938155) found that stored rows cannot tell writers apart, which sent the question back to the maintainer as that ruling reserved. Batch #259 was presented in the live director chat (four cards; this one item 4, director recommendation B, fallback A); the maintainer answered the batch with one word, 「同意」. Triage's analysis 5915782920 recommended A; this ruling was derived from axis ① first and takes B.

    The ruling

    B — retire the renderer's translate-if-equal-to-name convention now, with no conversion of stored rows. A present label renders verbatim, with no exception; an absent label inherits its target's current, localized label. A (measure production, then a one-time ADR-0087 conversion keyed on the sync id signature, then retire) and C (defer to v18) not taken: a conversion keyed on an id shape is the same class of inference that ruling A refused for text, only run once; C has no executable Restart-when: and would leave the spec's rule with an exception. The maintainer's 「20051 不考虑现有的数据」 (5909085121 on #20051) is the precedent for leaving stored rows as they are.

    Readings (this summon)

    四棱(本裁决新记录)

    • ① 长远:终态只剩 spec 一条规则;B 不按 id 形状猜写入者。
    • ② 拉动:可读源 1/125 且作者手写;生产未测,由本裁决明示承担。
    • ③ 防错:新写的都走「不写就继承」,三案一样。
    • ④ 不扩散:B 删一条规则、零新增;A 加测量卡与启发式转换。
    • 只看①选 B;②③④ 是否翻转:否。

    Execution parameters (ruled here; no further decision card)

    • Lane / state: pm:queue, domain:ui (unchanged), level S, mode:subagent, Clause-②: no (renderer behaviour aligning to the spec's stated order; no schema, type or public prop change). The domain:ui seat 2 at objectui (stage 1's seat) is the natural claimant.
    • The round: in resolveNavItemLabel, delete the isCustomized arm and its comment; in the same function, resolve a present entry label that is an inline locale map (I18nLabelSchema) the way areas and the designer canvas already do (the gap named in 5913605784), so the function conforms to the one order — id-keyed bundle entry (translateApp, upstream of the renderer) → present label (the locale's map value, else its text) → absent label inherits the target's current, localized label. ⛔ Nothing is added on label that matches text against a name.
    • Pins: a present label equal to its target's machine name renders verbatim under en and zh-CN; a present map-valued entry label renders the locale's value; an absent label inherits and localizes (the stage-1 pin stays green).
    • Changeset: patch on @object-ui/layout, stating the visible effect — a stored entry whose label was written as the target's machine name now shows that name in every locale — and the one-line fix: clear the entry's label so it inherits, or set the wanted text. No conversion, no notice run.
    • Fork clause: the premise is that no objectui producer still writes a machine-name label (stage 1). If the dev finds one on main, it reports the fork on this card and stops.

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 12 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11201-nav-label-verbatim
    Worktree: objectui-issue-11201
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (stage 2 of ruling B, as the director's ruling 5921167691 sets its execution parameters):

    • packages/layout/src/NavigationRenderer.tsx, resolveNavItemLabel:
      • delete the isCustomized arm and its comment;
      • resolve a present inline locale-map entry label the way areas and the designer canvas already do.
      • ⛔ Nothing is added that matches label text against a name.
    • The packages/layout/src/__tests__/NavigationRenderer.* pins:
      • the new pins;
      • existing rows that pin the retired translate-if-equal convention, restated.
    • A comment that names the retired guard and becomes false (packages/i18n/src/useObjectLabel.ts, comment only).
    • One changeset, @object-ui/layout patch.
      Stop on breach; explain in the report.
      Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates has no objectui derivation, so there is no path-derived mandate; objectstack 5f6b63a6f)
      Clause-②: no
      Why no: the ruling's own execution parameter. A renderer aligns to the spec's stated order, and no schema, type or public prop changes.
      Thread-read: 5921167691
      Serial constraints cleared: read 2026-10-01T00:03Z at objectui origin/main e2271568f.
    • No open PR names NavigationRenderer.tsx (12 open PRs scanned).
    • objectui#11197 (seat 1, the same file) is closed.
    • Stage 1 landed as PR objectui#11225.
    • No live claim in either lane names packages/layout.

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11201,
      "status": "done",
      "branch": "claude/issue-11201-nav-label-verbatim",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11297",
      "session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the domain:ui seat 2 PM session, so the PM's id; identity = the branch named in claim 5921933038)",
      "premise_still_valid": true,
      "summary": "Stage 2 of ruling B (5921167691). resolveNavItemLabel no longer translates a present label whose text equals its target's machine name. The isCustomized arm and its comment are deleted, and nothing on label matches text against a name. A present label equal to the machine name (account, board, sales_overview) now renders verbatim under en and zh-CN. A present map-valued entry label used to reach the keyed resolver and render empty. It now resolves through the spec's resolveI18nLabel. This layer is given no locale, so the map reads the spec resolver's no-locale default (en, then default, then any entry), which is also the floor the area switcher in this package uses. Reaching the viewer's locale needs a new prop and is open question 1. Deleting the arm leaves resolveObjectLabel, resolveDashboardLabel and resolveViewLabel unread. They are kept as @deprecated, because the ruling set no public prop change; retiring them is open question 2. Fork clause not triggered: no objectui producer on main writes a machine-name label. Changeset: @object-ui/layout patch, with the visible effect and the one-line fix.",
      "tests": "All on head 54656bd7d, run from the worktree root under os-verify-lock (slot issue-11201). (1) pnpm exec vitest run --reporter=verbose packages/layout/src/: Test Files 30 passed (30), Tests 293 passed (293), VERDICT command-exit 0. (2) Consumers outside layout: every test file importing NavigationRenderer or resolveNavItemLabel, plus packages/app-shell/src/layout/, packages/app-shell/src/chrome/, the nav:menu render tests, the Studio and designer nav-label pins, packages/plugin-designer/ and packages/i18n/src/. Result: Test Files 173 passed (173), Tests 1966 passed and 13 skipped (1979), exit 0. That set includes the stage-1 pin useNavigationSync.standardEntryLocalizes-11201.test.tsx and StudioDesignSurface.standardNavEntries-11201.test.tsx. (3) Root suite pnpm exec vitest run scripts/__tests__/: Test Files 177 passed and 2 skipped (179), Tests 5371 passed and 2 skipped (5373), VERDICT command-exit 0. (4) Closure build turbo run build --filter=@object-ui/layout^... --filter=@object-ui/i18n^... --concurrency=2: Tasks 8 successful, 8 total. Then pnpm --filter @object-ui/layout type-check and pnpm --filter @object-ui/i18n type-check: exit 0. tsc -p tsconfig.test.json --listFilesOnly lists both touched layout test files. (5) Inside run (1): the new file has 6 tests, 6 passed; navLabelOwnership has 6, 6 passed. NEW PINS (NavigationRenderer.labelVerbatim-11201.test.tsx): VERBATIM has 4 rows (sidebar under en, sidebar under zh-CN, function under en, function under zh-CN). MAP has 2 rows (function, sidebar with search). RESTATED ROWS (NavigationRenderer.navLabelOwnership.test.tsx): 'still resolves an un-customized object label through resolveObjectLabel' is now 'renders a present object label equal to the machine name verbatim, resolveObjectLabel unasked (objectui#11201)'. 'keeps the same guard on the dashboard branch' is now 'renders both present dashboard labels verbatim, resolveDashboardLabel unasked (objectui#11201)'. 'still lets an authored object label win over resolveObjectLabel' kept its assertion; only its comment changed. ABLATION, from committed 54656bd7d, via objectstack scripts/ablation-replace.mjs in WRAP mode. Layout tests import the renderer by relative path and the root vitest config aliases @object-ui/layout to src, so no dist is in the path. Leg A restored the isCustomized arm (anchor x1 → x0, blob b7942b1d9b33 → 8885b458d17f): over packages/layout/src/, Test Files 2 failed / 28 passed, Tests 6 failed / 287 passed. The 6 are exactly the 4 VERBATIM rows and the 2 restated rows; the MAP rows stay green. Leg B dropped the map resolution, so every object label goes back through the keyed resolver (anchor x1 → x0, blob b7942b1d9b33 → 7c6bd1768e8d): Test Files 1 failed / 29 passed, Tests 2 failed / 291 passed. The 2 are exactly the MAP rows. Each restore was proven: blob after restore == blob at HEAD (b7942b1d9b33), and git diff HEAD is empty. eslint on the 4 changed source and test files: 0 errors, 28 warnings. Base blobs: NavigationRenderer.tsx 0/25 against 0/23 at head; navLabelOwnership 0/0 and 0/0; useObjectLabel.ts 0/5 and 0/5; the new test 0/0. So the diff adds no warnings and removes 2.",
      "mcp_calls": "0 — no MCP GitHub tool used",
      "api_writes": "3 REST writes, each through the fleet relay (each relay call itself is POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft, PR 11297), body read back byte-identical (8880 bytes, no differing index); (2) PR assignee os-support-ai via label-write.mjs, POST /repos/objectstack-ai/objectui/issues/11297/assignees, read back MATCHES; (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/11201/comments via post-stamped.mjs. Plus 2 git pushes of the branch (the empty-branch probe, then 54656bd7d); not REST. No card label or assignee written, no PR label written. The PR's tests / package: layout / package: i18n labels come from the path labeler.",
      "open_questions": [
        {
          "question": "A present map-valued nav entry label now renders, but at the spec resolver's no-locale default (en, then default, then any entry), because NavigationRenderer is given no locale. The ruled pin says 'renders the locale's value'. Should the viewer's locale be threaded through, so that a zh-CN viewer sees the zh-CN entry?",
          "options": [
            "A: add an optional locale prop to NavigationRenderer and a trailing locale argument to resolveNavItemLabel. The console passes its active language: UnifiedSidebar (two mounts), nav-menu-renderer, CommandPalette and SearchResultsPage. AppSchemaRenderer's areas could take the same prop. Cost: one new public prop, so Clause-② yes and an objectui minor changeset; about 6 files across layout and app-shell; pins under en and zh-CN.",
            "B: keep the en floor this PR ships. It is the floor AppSchemaRenderer's area switcher already documents ('a floor, not a ceiling'). State it where nav labels are documented. Cost: no new surface. But a zh-CN viewer sees the en entry of a map-valued entry label, while the area labels above it in UnifiedSidebar resolve in the viewer's language."
          ],
          "recommendation": "A, on a new card rather than this one, and in one card with question 2: both rewrite NavigationRenderer's label inputs at the same console call sites. ① Long term: A is the route the package's own resolveAreaLabel docblock names ('thread a locale down as a prop from the host'), and the sidebar would then resolve entry maps and area maps the same way. ② Pull, measured: zero. No readable app navigation writes a map-valued entry label (objectstack 5f6b63a6f, files that declare navigation; the only object-valued labels there are in translation bundles). That favours B for now, and it is why A is a follow-up rather than part of this PR. ③ AI mistakes: under B, an AI that writes { en, zh-CN } on an entry passes the platform's save door, and its zh-CN users silently get English. That is a declared capability only half honoured. objectui validate refuses the map today (finding 1), so the gap is hidden at authoring time, but only in objectui's own toolchain. ④ No sprawl: A adds one optional prop and B adds none. ① and ③ outweigh ② and ④, because the spec already declares the map for nav entries. If the maintainer applies the startup focus strictly, B is acceptable, and keeping it costs nothing."
        },
        {
          "question": "Deleting the isCustomized arm leaves resolveObjectLabel, resolveDashboardLabel and resolveViewLabel unread: the three NavigationRenderer props, and resolveNavItemLabel's 2nd, 4th and 5th positional arguments. This PR keeps them as @deprecated no-ops, because the ruling set 'no public prop change' and the claim's file surface excludes the callers. Should they be retired?",
          "options": [
            "A: retire them in a follow-up. Remove the three props and arguments, so resolveNavItemLabel becomes (item, t?, targetLabel?) or takes an options object. Update UnifiedSidebar (two mounts), nav-menu-renderer, CommandPalette, SearchResultsPage, navItemLabel.ts, AppCreationWizard and the stage-1 pins that pass them, and correct nav-menu-renderer's header comment. Cost: a breaking change to an exported signature (objectui marks breaking changes minor); about 10 files.",
            "B: keep them deprecated indefinitely. Cost: three props that do nothing stay on the public type, and callers keep building closures that are never called."
          ],
          "recommendation": "A, in one card with question 1. ① Long term: a prop that does nothing is a workaround left in the contract. ② Pull, measured: in-tree callers are 3 mounts and 2 tests, all ours; external callers are unknown. ③ AI mistakes: an AI that wires a deprecated resolver 'for localization' gets no effect, and nothing tells it so. ④ No sprawl: the framework's default for a retirement is immediate, with no staged window unless a named external user needs one, and none is known."
        }
      ],
      "out_of_scope_findings": [
        "class: b · reach: safeValidateSchema, the function objectui validate calls (packages/cli/src/commands/validate.ts imports it), refuses navigation[0].label = { en: 'Accounts', 'zh-CN': '客户' } with 'Invalid input: expected string, received object', while @objectstack/spec 17.5.0 NavigationItemSchema.safeParse accepts the same entry; control: a string label passes both. Measured at the function, not by running the CLI bin · evidence: @object-ui/types NavigationEntryItem.label?: string and the zod NavigationItemObject label: z.string().optional() are narrower than the spec's I18nLabel · Seam: spec:BaseNavItemSchema.label (I18nLabel) → runtime: @object-ui/types zod NavigationItemSchema (objectui validate) | renderer: NavigationRenderer (reads the map since this PR) · dedupe words: nav label locale map, NavigationItemSchema label string, objectui validate navigation label, I18nLabel nav entry, inline locale map navigation",
        "carrier: 承接者:无 (it would be the follow-up of open question 2, if filed) · the header comment of packages/app-shell/src/views/nav-menu-renderer.tsx still says its labels get 'the same convention-based object / view / dashboard i18n resolution the sidebar gets', which is false after this PR; UnifiedSidebar and nav-menu-renderer still build the three unread resolvers · PR Acceptance notes only, not filed",
        "carrier: 承接者:无 · AppSchemaRenderer.tsx's resolveAreaLabel docblock quotes the old resolveObjectLabel prop doc, which this PR rewrote; the docblock's argument (injection rather than an i18n dependency) still stands · PR Acceptance notes only",
        "carrier: 承接者:无 · resolveNavItemLabel keeps objectui's keyed { key, defaultValue } branch unchanged. The spec's nav label refuses that shape and objectui's type does not declare it, so it is unreachable by contract; the new code tells it apart from a map by the two member names I18nLabelSchema refuses (measured: { key } and { defaultValue } both fail I18nLabelSchema.safeParse) · PR Acceptance notes only"
      ],
      "gates": [
        {
          "gate": "vitest layout",
          "command": "pnpm exec vitest run --reporter=verbose packages/layout/src/",
          "result": "30 files, 293 passed, VERDICT command-exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "vitest consumers outside layout",
          "command": "pnpm exec vitest run packages/app-shell/src/layout/ packages/app-shell/src/chrome/ (+16 named nav-label test files) packages/plugin-designer/ packages/types/src/__tests__/(4 nav files) packages/i18n/src/",
          "result": "173 files, 1966 passed, 13 skipped, exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "vitest root scripts",
          "command": "pnpm exec vitest run scripts/__tests__/",
          "result": "177 files passed, 2 skipped; 5371 tests passed, 2 skipped; VERDICT command-exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "closure build",
          "command": "pnpm exec turbo run build --filter='@object-ui/layout^...' --filter='@object-ui/i18n^...' --concurrency=2",
          "result": "Tasks 8 successful, 8 total; VERDICT command-exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "type-check layout + i18n",
          "command": "pnpm --filter @object-ui/layout type-check && pnpm --filter @object-ui/i18n type-check",
          "result": "exit 0 (both echoed their script names)",
          "sha": "54656bd7d"
        },
        {
          "gate": "eslint changed files",
          "command": "pnpm exec eslint (4 changed .ts/.tsx files)",
          "result": "0 errors, 28 warnings, all pre-existing; base blobs 25+0+5 against head 23+0+5+0",
          "sha": "54656bd7d"
        },
        {
          "gate": "ablation A",
          "command": "ablation-replace.mjs (restore isCustomized arm) -- vitest run packages/layout/src/",
          "result": "6 failed / 287 passed: exactly the verbatim rows; restore proven",
          "sha": "54656bd7d"
        },
        {
          "gate": "ablation B",
          "command": "ablation-replace.mjs (drop map resolution) -- vitest run packages/layout/src/",
          "result": "2 failed / 291 passed: exactly the map rows; restore proven",
          "sha": "54656bd7d"
        },
        {
          "gate": "changeset presence",
          "command": "node scripts/check-changeset-presence.mjs",
          "result": "exit 0 (4 source files of 2 released packages, 1 changeset)",
          "sha": "54656bd7d"
        },
        {
          "gate": "changeset no major",
          "command": "node scripts/check-changeset-no-major.mjs",
          "result": "exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "changeset claims",
          "command": "pnpm check:changeset-claims",
          "result": "exit 0, report-only; 3 pending changesets name a touched file (8871, 5587, 5625); each paragraph read, none falsified",
          "sha": "54656bd7d"
        },
        {
          "gate": "control bytes",
          "command": "pnpm check:control-bytes",
          "result": "exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "new line citations",
          "command": "pnpm check:new-line-citations",
          "result": "0 new citation(s), exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "markdown audit",
          "command": "node scripts/markdown-test-inputs.mjs --audit",
          "result": "exit 0",
          "sha": "54656bd7d"
        },
        {
          "gate": "CI",
          "command": "PR 11297 check-runs on 54656bd7d",
          "result": "in_progress at report time: 42 runs, 19 success, 3 skipped, 20 in progress, 0 failure (not awaited, per the role file)",
          "sha": "54656bd7d"
        }
      ],
      "line_budget": "n/a: no skills/** file and no line-ratcheted ledger touched",
      "deviations": [
        "PR title ends 'an inline locale-map label renders its text', not the dispatched 'renders the locale's value': the layer is given no locale, so the title states what the code does (open question 1)",
        "The map pin asserts the spec resolver's no-locale default (en, then default, then any entry), not the viewer's locale. Zone 1 implicitly assumed the renderer knows the locale; it does not (NavigationRenderer has no locale prop and @object-ui/layout has no i18n dependency). Within the ruled 'no public prop change' / Clause-② no / patch, this is the route that keeps the ruling's intent; the rest is open question 1",
        "The three convention resolvers stay as @deprecated, unread props and arguments, not removed: removal is a public prop change and touches app-shell callers outside the claim's file surface (open question 2)",
        "The dispatch calls NavigationRenderer.labelInheritsTarget-9868.test.tsx 'the stage-1 pin'. That file is objectui#9868's; stage 1's own pin is app-shell's useNavigationSync.standardEntryLocalizes-11201.test.tsx. Both ran green",
        "Comment-only edits in NavigationRenderer.tsx beyond resolveNavItemLabel: the RETIRED (9c60144b5) note, the t prop doc and the action-arm comment each pointed at the deleted text or became false. Same file, inside the claim",
        "Declared narrowing: tests outside layout are the enumerated consumer set (direct importers of NavigationRenderer / resolveNavItemLabel, the layout and chrome dirs of app-shell, nav:menu render tests, the Studio and designer nav-label pins, plugin-designer, i18n), not the whole app-shell suite; the rest is CI's",
        "The two probes of the objectui zod mirror (finding 1) ran as throwaway files inside packages/types, so that relative TS imports resolved. Each was deleted in the same command and never staged; git status was empty afterwards"
      ],
      "files_changed": [
        ".changeset/11201-nav-label-verbatim.md",
        "packages/i18n/src/useObjectLabel.ts",
        "packages/layout/src/NavigationRenderer.tsx",
        "packages/layout/src/__tests__/NavigationRenderer.labelVerbatim-11201.test.tsx",
        "packages/layout/src/__tests__/NavigationRenderer.navLabelOwnership.test.tsx"
      ]
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR objectui#11297 at 54656bd7d (p2, Fixes, stage 2 of ruling B). Ready and auto-merge follow this ACCEPT

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.

    • Ruling: 5921167691 (letter B, on the maintainer's 「同意」), with its execution parameters.
    • Claim: 5921933038.
    • Dev report: 5922299030.
    • Contract review: 5922460145, PASS on this head.
    Implemented-by:  claude/issue-11201-nav-label-verbatim
    Reviewed-by:     session_011p7ikEivgXefNDaE5S5Uec
    
    item reading
    retired resolveNavItemLabel drops the isCustomized arm and its comment. A present label renders verbatim, with no exception. No code path compares a label to a name, and nothing added does
    map a present inline locale-map entry label used to render empty. It now renders through the spec's resolveI18nLabel at its no-locale default, the floor resolveAreaLabel documents. The review judged this the ceiling the ruling's "no public prop change" allows. The viewer's-locale route is a new prop, carried to objectui#11299
    shape test isKeyedLabel tells a map apart from the keyed { key, defaultValue } by member names, which the spec's INLINE_LOCALE_KEY excludes from every accepted map. It reads neither the label's text nor a target
    props the three convention resolvers stay on the public type with identical types and are tagged @deprecated (inert). Their retirement is carried to objectui#11299
    pins verbatim under en and zh-CN; the map rows; two navLabelOwnership rows restated; inheritance stays green (labelInheritsTarget-9868, and the stage-1 pin in the consumer set)
    ablation the arm restored: exactly the 6 verbatim and restated rows red. The map read dropped: exactly the 2 map rows red. Restores proven
    fork clause not triggered. No objectui producer on main writes a machine-name label; the review re-read every producer
    changeset @object-ui/layout patch, with the visible effect and the one-line fix. @object-ui/i18n carries a JSDoc edit only. Clause-②: no holds against the built diff
    CI head 54656bd7d: 43 check-runs, 40 success, 3 skipped by design (Test (coverage), its matrix row, dependabot), 0 failed
    closing keywords Fixes #11201. Stage 2 is the ruling's last round ("no further decision card")
    governed none

    Filed: objectui#11299. It is the one follow-up family card, for triage, and carries:

    • Q1, the viewer's locale for a map-valued entry label, with options A, B, and the review's @object-ui/react route C;
    • Q2, retiring the three inert resolver props;
    • Q3, objectui validate refusing a map label the spec accepts (measured at safeValidateSchema);
    • nav-menu-renderer.tsx's header comment, which is now false.

    domain:ui seat 2 · ACCEPT · 2026-10-01T00:50Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions