Repository navigation
Commit 47e956d
docs(skills): app-composition guide — a nav item's
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](https://claude.ai/code/session_01FNKm1SmPpuJASnbjxWGtsJ)_
Co-authored-by: Claude <noreply@anthropic.com>label is optional and inherited when absent (objectui#11198) (#11427)1 parent f9c8c4e commit 47e956d
2 files changed
Lines changed: 7 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
40 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| |||
0 commit comments