Repository navigation
Commit fe72ad7
Fixes #22291
Clause-②: no
The Studio author's page skill (`skills/objectstack-ui/rules/pages.md`,
shipped to every project by `npx skills add`) told the author in its
Routing model section that "There is no dedicated `doc` nav-item type
yet, so use a `url` item pointing at `/docs/NAME`", with a `url`
example, while `@objectstack/spec` declares the `doc` navigation item
(`packages/spec/src/ui/app.zod.ts:108` names `'doc'` in
`NavItemVariant`; `DocNavItemSchema` at `:715` with the contract
documented at `:676-714`), the console renders it at the `.objectui-sha`
pin `a58626c88` (`packages/layout/src/NavigationRenderer.tsx` `case
'doc'` at lines 498 and 917,
`packages/app-shell/src/views/nav-menu-renderer.tsx:281`,
`packages/app-shell/src/hooks/useNavDocTargetCheck.ts`), and
`SKILL.md`'s Navigation Item Types table already lists it. Eval 4 of
`skills/objectstack-ui/evals/views-apps-actions-pages.json` pinned the
wrong answer (`type: 'url'` pointing at `/docs/crm_user_guide`). This PR
teaches the declared item and makes the eval expect it. #22271 and PR
#22287 are landed references (PR #22287 rewrote `pages.md` today and
left 1 token of headroom); objectui#11197 is the console-side reference.
None of them is addressed here.
Premise re-checked on `origin/main` at `238222d8cd` (this branch's base,
PR #22287's merge commit): the sentence sits at `pages.md:361-362` (the
card read it at line 375 on `fbcbcf124f`, before PR #22287 moved it),
the `url` example at `:366`, eval 4 unchanged; the `doc` contract is at
the lines above (the card's `:681-686` doc comment is now `:676-714`).
Premise valid.
## What changes (two files, nothing else)
- `skills/objectstack-ui/rules/pages.md` — Routing model: the paragraph
becomes "To surface a doc inside an app, add a `doc` navigation item (a
`url` item is for an external or custom URL): `doc: 'NAME'` opens that
page, `book: 'BOOK'` opens the book at its first readable page (both:
that page in that book). It inherits the docs audience gate, and `os
build` refuses a target the package lacks (`docs/nav-target`):" and the
example line becomes `{ id: 'nav_help', type: 'doc', doc:
'crm_user_guide', label: 'User Guide', icon: 'book-open' }`. Every
clause is read from the source: the two targets and the both-keys case
from `DocNavItemSchema`'s doc comment and `requiredOneOf(['book',
'doc'])`; the audience gate from the same comment (ADR-0046 §6.7); the
existence check from `packages/cli/src/utils/collect-docs.ts`
(`lintDocNavTargets`, rule `docs/nav-target`, severity `error`); the
`url` item's use from `UrlNavItemSchema.url` ("Target external URL") and
`SKILL.md`'s table row ("External or custom URL"). The surrounding true
lines (the global `/docs/NAME` route paragraph, the `*.md` link
rewriting, the Live instances note) are untouched.
- `skills/objectstack-ui/evals/views-apps-actions-pages.json` — eval 4:
the `expected_output` tail becomes "surfaced by a `type: 'doc'` nav item
(`doc: 'crm_user_guide'`)."; `must_contain` swaps `"/docs/"` for `"type:
'doc'"`; `must_not_contain` gains `"type: 'url'"`. Measured, not
assumed: `"/docs/"` was a dead assertion — `"src/docs/"` is in the same
`must_contain` and contains `/docs/` as a substring, so under the eval
README's string-check semantics it could never fail on its own; with the
`doc` item a correct answer carries no `/docs/` route at all (the item
names the doc, the platform owns the route), so the entry is swapped for
the live pin rather than kept beside it (adding instead of swapping
would overshoot the eval's 1505 ceiling by 7 bytes). Eval 4's prompt (a
record page plus a user guide linked from the nav) needs no `url` item,
checked against the whole eval file; evals 1-3 and 5-6 are untouched.
## Ratchet readings (`scripts/check-skills-token-ratchet.mjs`,
convention ceil(utf8 bytes / 4); measured on `238222d8cd` before and on
this PR's head `2bdf0f6317` after)
| file | lines before → after | tokens before → after | ceiling |
headroom after |
| --- | --- | --- | --- | --- |
| `skills/objectstack-ui/rules/pages.md` | 433 → 433 (0) | 5691 → 5686
(−5) | 5692 | 6 |
| `skills/objectstack-ui/evals/views-apps-actions-pages.json` | 65 → 65
(0) | 1502 → 1505 (+3) | 1505 | 0 |
| whole package, all `skills/*/SKILL.md` (10 files) | 4409 → 4409 (0) |
47524 → 47524 (0) | — | — |
| whole `skills/objectstack-ui` authored bundle (9 priced files) | 2030
→ 2030 (0) | 26108 → 26106 (−2) | — | — |
| bundle total (whole shipped tree, the gate's own line) | — | 154842 →
154840 (−2) | — | — |
No ceiling moves, no re-wrap counted (the new paragraph is wrapped once,
as new text), no new file. The new Routing block is 477 bytes against
the 329 it replaces (+148); `pages.md` had 6 bytes of headroom, so the
difference is paid inside the file by one deletion:
| deleted (`pages.md`) | survives in |
| --- | --- |
| Page Types: "Former roadmap-only types (`dashboard`, `form`,
`record_detail`, `record_review`, `overview`, `blank`) were removed from
the enum because they never shipped a renderer." (169 bytes) | the
section's lead — "`PageTypeSchema` has exactly **five** values — only
types with a dedicated renderer are authorizable (ADR-0049
enforce-or-remove)" — and the five-row table; `record_detail` stays
named in the Disambiguation sentence (eval 4's `must_not_contain` still
reads it there). The six former names as a list are the one thing not
restated verbatim: a tombstone of removed values, not an authoring
instruction — `PageTypeSchema` refuses any of them at parse with the
five legal values in the error. Flagged here for the reviewer. |
| Routing model: "add a navigation item that **links into** that global
URL. There is no dedicated `doc` nav-item type yet, so use a `url` item
pointing at `/docs/NAME`:" and the `url` example line | replaced by the
true sentence and the `doc` example (the budget the card names) |
## Controls (against the built `@objectstack/spec` dist at the head;
`NavigationItemSchema.safeParse`)
- Positive: the skill's example `{ id: 'nav_help', type: 'doc', doc:
'crm_user_guide', label: 'User Guide', icon: 'book-open' }` → `success:
true`, parsed as `{"type":"doc","doc":"crm_user_guide",…}`; `book:
'crm_manual'` alone → success; both keys → success.
- Refused, with the remedy the skill paraphrases: neither key → "A `doc`
navigation item needs a target: set `book` (opens that book at its first
readable page), `doc` (opens that page), or both (that page in that
book's context)."; `doc: 'docs/crm_user_guide'` and `doc:
'crm_user_guide.md'` → "A doc target is a doc NAME — the source filename
stem in lowercase snake_case"; a `url` key on a `doc` item →
"Unrecognized key(s) on this `doc` navigation item: `url`. Did you mean
`url` → `type: 'url' (with url)`?".
- The old answer `{ type: 'url', url: '/docs/crm_user_guide' }` still
parses (`success: true`, `target: '_self'` defaulted): nothing on the
platform refuses it, which is why the skill text was the only thing
steering an author to it — the card's responsibility reading holds.
- The console consumer at the pin resolves a `doc` item to `/docs/DOC`,
`/docs/BOOK` or `/docs/BOOK/DOC` (`resolveDocHref`,
`NavigationRenderer.tsx:752`) and hides an entry whose target the member
may not read (`nav-menu-renderer.tsx:281`).
- The edited example is a `navigation:` fragment, not an `os:check`
block, so `check:skill-examples` is not its control and was not marked.
## Gates (derived on this tree by `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` at `2bdf0f6317`: 23
families, identical to the PM's list derived on `238222d8cd`; every exit
code captured before any pipe; `--ran` reconciliation: "23 derived, 23
run, 0 NOT-MEASURED, 0 UNRUN")
| command | exit |
| --- | --- |
| `node scripts/check-skills-token-ratchet.mjs` (+ `--self-test`, 65
cases) | 0 / 0 — "54 authored bundle file(s) within their ceilings" |
| `pnpm check:skill-identifier-liveness` · `check:skill-compatibility` ·
`check:skill-frame-sync` | 0 · 0 · 0 |
| `pnpm check:doc-authoring` · `check:corpus-claim-drift` ·
`check:nul-bytes` | 0 · 0 · 0 |
| `node scripts/check-doc-route-spelling.mjs --advisory` (+
`--self-test`) | 0 / 0 |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
0, after `turbo run build --filter=@objectstack/lint --concurrency=2`
under `scripts/pm/os-verify-lock.sh` (VERDICT command-exit 0, 4 tasks,
lock held 99 s) |
| `node scripts/check-ci-filter-parity.mjs` ·
`check-closing-keyword-parity.mjs` (+ `--self-test`) ·
`check-comment-mask-corpus.mjs` | 0 · 0 / 0 · 0 |
| `pnpm check:agent-test-spelling` · `check:cross-package-test-inputs` ·
`check:driver-memory-census` · `check:gitlink-declared` ·
`check:pm-governed-merges` · `check:refd-timer-probe` ·
`check:role-word` · `check:watch-hint-literal` | all 0 |
Not run locally, CI's own: the type-check lanes and `pnpm lint` (no
TypeScript or source file moves; the diff is two files under `skills/`).
No changeset: the diff publishes nothing from a released package
(`skills/**` is in no package's `files`), so `skip-changeset` is the
declaration.
## Acceptance notes (not filed; carrier named)
- `"/docs/"` in eval 4's `must_contain` was subsumed by `"src/docs/"`
(substring), so it had never been able to fail on its own — repaired
here by the swap above, recorded so the reviewer can see the mechanism
rather than infer it. No other eval in the two `objectstack-ui` eval
files carries an entry subsumed by a sibling entry (read, not grepped).
- `UrlNavItemSchema.url` is described as "Target external URL" while the
console resolves an internal path through it and `SKILL.md`'s table says
"External or custom URL" — a describe-text observation with no behaviour
behind it; not a finding class, noted only. Carrier: none.
## 维护者速读(草稿)
**改了什么** — 发布给每个客户项目的 Studio
作者技能(`skills/objectstack-ui/rules/pages.md`)「Routing model」一段原来写着「还没有专门的
`doc` 导航项类型,用 `url` 项指向 `/docs/…`」,示例也是 `url` 项;而 spec 早已声明 `doc`
导航项(`doc` 打开一页,`book` 打开一本书的第一可读页,两键同给即该书中的该页,继承文档受众门禁),控制台在当前 pin
已渲染,SKILL.md 的导航项类型表也已列出。本 PR 把这一段和示例改成教 `doc` 项(并带一句 `url`
项仍用于外部或自定义地址、`os build` 会拒绝包里不存在的目标),评测 4 的预期输出与断言改为要求 `type: 'doc'`、拒绝
`type: 'url'`。
**为什么改** — 技能文本说错一句话就是产品缺陷(北极星第 4 项):入口文件说有 `doc` 类型,规则文件说没有,AI 作者照规则文件写
`url` 项,平台有类型化入口却没人用,文档受众门禁也被绕开(`url` 项不继承它);评测把错答案钉成了标准答案。平台没有任何一处拒绝指向
`/docs/` 的 `url` 项,所以只有改技能文本这一条路。
**风险与代价(含回滚)** — 发布技能包有 token 棘轮:`pages.md` 只剩 1 token 余量,新段落比旧段落多 148
字节,靠删除「Page Types」里被开头句重述的一句(「以前路线图类型(六个名字)已从枚举移除」,169 字节)付账,净 −5
token,落在 5686/5692;评测文件 +3 token,恰好
1505/1505;上限未动、无换行凑数、无新文件。唯一不是逐字重述的信息是那六个旧类型名,spec
枚举会在解析时响亮拒绝它们并列出五个合法值,`record_detail` 仍在消歧句里。风险:AI 作者把 `doc` 目标写成路径或文件名
—— spec 拒绝并给出正确拼写;写了包里没有的名字 —— `os build` 的 `docs/nav-target` 拒绝。回滚:只动两个
markdown/json 文件,revert 即可,无代码、无 changeset。
**席位意见** —
**你要做的** — 核对 Routing model 新段落的措辞与删除的那一句;认可则 APPROVE 这个 draft
PR,由你或授权审批落地(`skills/**` 为 Tier H)。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CXydFDyiQwNbGFkmwrcRQq)_
Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
1 parent a3bcbcf commit fe72ad7
2 files changed
Lines changed: 10 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
41 | | - | |
| 40 | + | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
35 | | - | |
36 | | - | |
| 34 | + | |
37 | 35 | | |
38 | 36 | | |
39 | 37 | | |
| |||
357 | 355 | | |
358 | 356 | | |
359 | 357 | | |
360 | | - | |
361 | | - | |
362 | | - | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
363 | 363 | | |
364 | 364 | | |
365 | 365 | | |
366 | | - | |
| 366 | + | |
367 | 367 | | |
368 | 368 | | |
369 | 369 | | |
| |||
0 commit comments