Skip to content

docs(skills): app-composition guide — a nav item's label is optional and inherited when absent (objectui#11198) - #11427

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-11198-app-composition-nav-label-optional
Oct 2, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-11198-app-composition-nav-label-optional

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11198
Clause-②: no

What changes

One bullet in skills/objectui/guides/app-composition.md ("Contract Reminders"), plus an empty-frontmatter changeset. Nothing else in the guide moves: no code fence, no content/docs/**, no package source.

Before (the sentence the card quotes):

Every nav item needs a snake_case id and a label (both required by NavigationItemSchema).

After:

Every nav item needs a snake_case id; label is optional — when absent, the entry shows its target's current label at render time (objectui#9868), so write one only for a deliberate nav-only name or for a target with no label of its own (e.g. a page, report, URL or group).

Why

@objectstack/spec 17.5.0 made a nav entry's label optional with a declared semantic (maintainer ruling A on objectstack-ai/objectstack#19049: absent ⇒ the entry inherits its target's current label at render time; present ⇒ rendered as authored), and PR objectui#11186 (objectui#9868) relaxed objectui's validator to match. An AI following the old sentence wrote a label it did not need and lost the inheritance the ruling exists for: a renamed target no longer renamed its nav entry. The wording follows the human-facing twin, the "Requirements" list of content/docs/guide/designing-app-navigation.md, which PR objectui#11186 already corrected; the guide's own "Related" entry says the two are updated together.

objectui#11201 (ruled B: a present label renders verbatim, with no text-match exception) is not touched: the bullet states only what an absent label does and when to write one, which holds under that ruling.

Premise check against origin/main (e0a9c67)

  • packages/types/src/zod/app.zod.ts, NavigationItemObject: id is declared optional only so a bare separator parses (objectstack#4115) and is re-imposed for every other type by the superRefine; label is optional for every type (objectui#9868) and the refinement refuses only an empty string. So "id required" still holds and "label required" does not.
  • packages/app-shell/src/hooks/useNavTargetLabel.ts and packages/layout/src/NavigationRenderer.tsx (resolveNavItemLabel) document and implement absent ⇒ inherit, bottoming out at the target's machine name.
  • pnpm-lock.yaml pins @objectstack/spec@17.5.0; a direct probe of NavigationItemSchema from @objectstack/spec/ui in the installed worktree: an object item without label parses, one without id is refused (id: Invalid input: expected string, received undefined).
  • git grep over skills/**: this bullet was the only place a nav label was called required.

Line readings (the governed skills/** budget, +1 allowed)

Surface Before After Delta
skills/objectui/guides/app-composition.md (whole file) 62 63 +1
whole package: every skills/objectui/**/*.md plus every skills/*/SKILL.md 4808 4809 +1

objectui has no token ratchet; lines are the reading. No neighbouring line was re-wrapped; the bullet grew from 2 lines to 3.

Verification (at afc7603f, the branch's only commit)

Derived by hand from .github/workflows/*.yml on origin/main (objectui has no dispatch-gates tool). Each exit code was captured to a file before any pipe.

Gate (workflow) Command Verdict
Skills Paths node scripts/check-skills-paths.mjs exit 0 — OK (89/90 stated path(s) resolve across 20 guide file(s); 1 baselined)
Skill Example Check --build-filter (12 packages) → pnpm exec turbo run build … --concurrency=2 (29 tasks, 3m00s) → --self-test (60 cases) → the gate exit 0 — Every marked skill example holds up against the built types. (under os-verify-lock.sh, held 242s, waited 0s)
Skill Eval Token Check --self-test (29 cases) → the gate exit 0 — Every must_contain token is taught by its own skill bundle.
Line Citation Gate CITATION_GATE_BASE=origin/main node scripts/check-new-cross-file-line-citations.mjs exit 0 — 0 new citation(s), enforcement report-only
Control Byte Scan node scripts/check-control-bytes.mjs; plus grep -naP for control bytes over the two changed files exit 0 — OK (scanned 9944 tracked text file(s)); the grep found none
Changeset Declaration node scripts/check-changeset-presence.mjs exit 0 — 0 of them published source of a package the release covers … 1 changeset(s) added
Changeset Claim Re-read node scripts/check-changeset-claims.mjs --json exit 0 — No pending changeset names a file this change touches
Changeset Bump Policy; Changeset Overwrite Report check-changeset-no-major.mjs; check-changeset-overwrite.mjs exit 0; exit 0
Governed Surface Queue Guard --self-test (188 cases); --test on the two changed paths exit 0; exit 3 GOVERNED — expected for skills/**: this PR stays draft until an authorized APPROVED review, then the claiming seat lands it
Lint roster (lint.yml) its Decide whether this change needs a full run step NOT RUN locally: the diff is **/*.md and .changeset/** only, every path in that step's exclude list, so the job reports with its steps skipped; CI owns it
ci.yml and the always-on doc gates (docs-links, doc-fence-languages, doc-component-types, doc-example-ids, doc-snippet-types, docs-route-eager-closure, readme-exports) — NOT RUN locally: their scan roots are content/docs, package READMEs and source (check-doc-component-types.mjs states in its header that skills/** is deliberately not one of them) and this diff touches no fence; CI owns them

Acceptance notes

  • skills/objectui/evals/app-composition.json, eval id 3: its expected_output prose still lists "a snake_case id, and a label" for the rewritten item. Its must_contain tokens are objectName, type, id (no label), and the prompt's item already carries label: Projects, so keeping that label is the "present ⇒ as authored" half of the ruling, not a contradiction. Observation only; not changed here (outside this card's one-bullet fence) and not filed. Carrier: none.
  • @objectstack/spec 17.5.0 itself accepts an empty-string label; objectui's NavigationItemObject refuses it as its one stated divergence. The bullet does not mention the empty string (the docs guide does) because of the +1 line budget.

维护者速读(草稿)

改了什么。 只改 objectui 已发布技能 skills/objectui/guides/app-composition.md 里的一条「契约提醒」:原来写「每个导航项都必须有 label,NavigationItemSchema 要求」,现在写「id 必填;label 可省略 —— 省略时导航项显示目标当前的名字;只有刻意要起一个导航专用名,或目标本身没有名字(页面、报表、外链、分组)时才写」。外加一个空 frontmatter 的 changeset(声明不发版)。

为什么改。 @objectstack/spec 17.5.0 已把导航项的 label 改成可选,并由维护者裁定 A 规定语义:不写就跟着目标当前名字走;写了就原样显示。objectui 的校验器也已随 PR objectui#11186 放开。技能里这句旧话让 AI 作者多写一个不需要的 label,结果对象/视图改名后,导航上的名字不再跟着变 —— 正是裁定 A 要避免的事。改了之后,AI 生成的 app 导航默认跟随目标名字,少一处要手工维护的文字。

风险与代价(含回滚)。 纯文字,不动代码、不动校验、不动面向人的文档(那边 PR objectui#11186 已经改对,本句措辞照它写)。风险只在措辞:若有人认为导航项仍应强制写 label,这句会导向相反做法 —— 但那与 spec 17.5.0 和裁定 A 相悖。回滚 = revert 这一个 commit。本地门禁全部通过;skills/** 是受管面,本 PR 停在 draft,等授权审批后由席位落地。

席位意见。(留空,席位定稿时填)

你要做的。 读上面「Before / After」两句;认可措辞就留一条 APPROVED review,席位负责后续 ready 与入队;不认可就在评论里写你要的说法,我们改句子。


Generated by Claude Code

… is required by NavigationItemSchema (objectui#11198)

The "Contract Reminders" bullet of skills/objectui/guides/app-composition.md
told an AI author that every nav item needs a label, "both required by
NavigationItemSchema". @objectstack/spec 17.5.0 made a nav entry's label
optional and objectui#9868 relaxed the validator to match: an absent label
makes the entry show its target's current label at render time, so a label
written on the guide's say-so forfeited that inheritance. The bullet now says
the id is required, the label is optional and inherited when absent, and names
the two cases that still want one. One bullet and an empty-frontmatter
changeset; nothing else in the guide moves.

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

Copy link
Copy Markdown
Contributor Author

Contract review

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

Face: governed guide text (skills/objectui/guides/app-composition.md; objectui skills/**, the published catalog), reviewed in-seat by the dispatching seat at the constant's tier. Inputs: objectui#11198 (body and every comment: triage 5909803018, the claim 5944024007, the report 5944250186), this PR's body, its file list (2 files, +7 / −2) and patch against main, objectui packages/types/src/zod/app.zod.ts, packages/app-shell/src/hooks/useNavTargetLabel.ts and content/docs/guide/designing-app-navigation.md on main, and the check-runs on this head as read at this record.

① Derived judgments

  1. Scope: the one 「Contract Reminders」 bullet (2 → 3 lines) plus an empty-frontmatter changeset; the guide's other 60 lines, every fence and every table row are byte-identical. RIGHT.
  2. 「Every nav item needs a snake_case id」: NavigationItemObject declares id optional only so a bare { type: 'separator' } validates, and re-imposes it for every other type by refinement (objectstack#4115 comment in the source); the dev's NavigationItemSchema probe from @objectstack/spec/ui 17.5.0 refuses an id-less object item. RIGHT (the separator carve-out is below the guide's grain, as the dispatch allowed).
  3. 「label is optional — when absent, the entry shows its target's current label at render time」: label is .optional() on the shared base for every type (objectui#9868; only an EMPTY string is refused), the spec base declares label: I18nLabelSchema.optional(), and useNavTargetLabel.ts + NavigationRenderer.tsx implement absent ⇒ the target's current label — maintainer ruling A (spec(ui): NavigationItemSchema.label becomes optional with a DECLARED default — an app.navigation entry with no label inherits its target's current label at render time (view label, else object/dashboard label); an explicit label is verbatim (cloud#2021 sync ruling, objectui#9868 letter A) objectstack#19049, 5731297631) as the docs guide states it (designing-app-navigation.md :99–:108). RIGHT.
  4. 「so write one only for a deliberate nav-only name or for a target with no label of its own (e.g. a page, report, URL or group)」: the docs guide's fallback list — page / report / URL entries fall back to their target's name and a group to its id, 「so give those a label」; a label that is written renders verbatim. The compression promises nothing the renderer does not do. RIGHT.
  5. The ordering constraint in the card's Direction holds: PR objectui#11186 (the validator relaxation) merged as a8198de2 before this guide moved; pnpm-lock.yaml pins @objectstack/spec@17.5.0. RIGHT.
  6. Line budget: 62 → 63 (+1 of +1); bundle (skills/objectui/**/*.md ∪ skills/*/SKILL.md) 4808 → 4809; no neighbouring line re-wrapped. RIGHT.
  7. The citation form objectui#9868 follows rules/protocol.md's existing form (the guide had none). RIGHT.
  8. No fence moved ⇒ skill-examples unaffected (the dev still ran it under the verify lock: 60 self-test cases, every marked example holds against the built types). RIGHT.
  9. Noted, not filed (the dev's reading): skills/objectui/evals/app-composition.json eval 3's expected_output prose still says 「a snake_case id, and a label」 for an item whose prompt already carries label: "Projects" — grader prose for a present-renders-as-authored case; no runtime or gate reads it. Stays a note. RIGHT.

② Semver level

Docs-only in a governed surface; EMPTY-frontmatter changeset present; the phantom skip-changeset label correctly not applied; Clause-②: no. Consistent.

③ Boundary flags

  • Base: cut at objectui e0a9c676 (main moved past the dispatch's cf38a6ce before the fetch); skills/ carries no diff across that range. Accepted.
  • One write-pace refusal (exit 10, nothing written) on the assignee stroke, re-sent once after the printed reopen instant. Accepted.
  • pnpm install --config.engine-strict=false under the container's Node floor. Accepted (platform reading).
  • Check-runs on afc7603f at this record: 36 success, 3 skipped, 1 in_progress (Test (shard 1/8)), 0 failure. Tier H landing: an authorized APPROVED review, then every check green on the landing head; no other open objectui PR touches this guide.
  • open_questions: none.

Implemented-by: claude/issue-11198-app-composition-nav-label-optional
Reviewed-by: session_01FNKm1SmPpuJASnbjxWGtsJ

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

席位(skills 席 1,session_01FNKm1SmPpuJASnbjxWGtsJ)对照自读的 diff、objectui main 上的校验器与渲染器源码校正 dev 草稿后定稿,2026-10-02T02:18Z。

改了什么。 发布给作者的技能指南 skills/objectui/guides/app-composition.md 里一条「Contract Reminders」要点:原句说每个导航项都要 id 和 label,「两者都是 NavigationItemSchema 必填」;改为:id 必填;label 可选 —— 不写时条目在渲染期显示目标对象/视图/仪表盘的当前名称(objectui#9868),只在刻意要一个导航专用名、或目标自身没有名称(页面、报表、URL、分组)时才写。2 行变 3 行(预算 +1),整包 4808 → 4809 行;另加空 frontmatter changeset(不发版)。其它一字未动。

为什么改。 这句是 AI 写应用元数据时读的教材。自 @objectstack/spec 17.5.0 与 objectui PR #11186(objectui#9868)起,label 已可选且缺省继承;AI 照旧句每项都写 label,就丢掉了继承 —— 目标改名后导航不跟着改。校验器、渲染器、文档指南三处已改对,只剩这条技能句子在教错(北极星规则 4:给 AI 的文档说错一句即产品缺陷)。

风险与代价(含回滚)。 纯文档改动,不动任何发布包与运行时;风险只在措辞:dev 在 objectui main 上重验了 schema(id 由 refinement 对 separator 以外的类型强制;label 全类型可选、空串拒绝)、渲染期继承钩子与锁文件钉的 spec 版本,并用 spec 的 NavigationItemSchema 探针确认无 label 的条目通过、无 id 的被拒。席内达档契约复核 PASS(见本 PR 评论)。回滚 = revert 本 PR。

席位意见。 建议批准。无同文件在飞 PR,落地无需 update-branch 顺序。

你要做的(一个动作)。 在本 PR 上留一条 APPROVED review(os-zhuang / hotlong 任一)。之后由席位清标、翻 ready、挂 auto-merge 入队;不需要你合并。

@os-zhuang
os-zhuang marked this pull request as ready for review October 2, 2026 02:20
@os-zhuang
os-zhuang enabled auto-merge October 2, 2026 02:20
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 47e956d Oct 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-11198-app-composition-nav-label-optional branch October 2, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants