Skip to content

Commit 47e956d

Browse files
docs(skills): app-composition guide — a nav item's label is optional and inherited when absent (objectui#11198) (#11427)
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>
1 parent f9c8c4e commit 47e956d

2 files changed

Lines changed: 7 additions & 2 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Docs-only, in the published objectui skill: the "Contract Reminders" bullet of `skills/objectui/guides/app-composition.md` said every nav item needs a `label`, "both required by `NavigationItemSchema`" (objectui#11198). That has been false since `@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 the guide told an AI author to write forfeited that inheritance and a renamed target no longer renamed its nav entry. 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 (a deliberate nav-only name; a target with no label of its own). One bullet changes; every other sentence, table row and example of the guide is unchanged, and no published package source moves.

‎skills/objectui/guides/app-composition.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ source of truth for that mapping.
3636
`filters` is mutually exclusive with `recordId`/`viewName`; `runAction` is refused with
3737
`recordId` (it composes with `viewName` or `filters`); `recordId` + `viewName` is tolerated.
3838
`filters` is a `Record<string, string>` (equality; serialized as `filter[<field>]=<value>` on `/data`).
39-
- Every nav item needs a snake_case `id` and a `label` (both required by
40-
`NavigationItemSchema`).
39+
- Every nav item needs a snake_case `id`; `label` is optional — when absent, the entry shows its
40+
target's current label at render time (objectui#9868), so write one only for a deliberate
41+
nav-only name or for a target with no label of its own (e.g. a page, report, URL or group).
4142
- The `navigation` key is the spec'd root for app nav. `menu` is deprecated
4243
legacy (`MenuItem[]`, auto-migrated at runtime via
4344
`menuItemToNavigationItem`); never generate it.

0 commit comments

Comments
 (0)