Skip to content

skills(ui): pages.md Routing model teaches the doc navigation item; eval 4 expects it - #22303

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-22291-ui-skill-doc-nav-item
Oct 8, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-22291-ui-skill-doc-nav-item

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

… eval 4 expects it

The published skill said "There is no dedicated `doc` nav-item type yet, so
use a `url` item pointing at `/docs/...`" while `@objectstack/spec` declares
the `doc` navigation item (`{ type: 'doc', doc }` / `{ type: 'doc', book }`)
and the console renders it. The Routing model paragraph and its example now
teach the declared item; eval 4's expected output and assertions pin the
`doc` item and refuse the `url` answer. Paid inside the token ratchet by
deleting the former-page-types sentence the Page Types lead restates.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXydFDyiQwNbGFkmwrcRQq
@github-actions github-actions Bot added the size/s label Oct 8, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 8, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Oct 8, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2bdf0f6317a2d2d414abc9801f993663669e9c62
Local-runs: none

Reviewed in-seat by the skills seat 1 (dispatching seat; this session is served at the contract-review tier, read off get_session), 2026-10-08T13:43Z. Face hit: published skill text (skills/objectstack-ui/**, Tier H; dispatch-gates --tier MANDATORY). Read-only shape held: the diff (2 files, +10/−10, one commit), the card #22291 and its thread (the claim 6060675749, the os-dev-report 6061148458), the head's check-runs; nothing built or run locally. Premises re-read on origin/main (the branch base 238222d8cd is PR #22287's merge commit; skills/** is untouched on origin/main since; merge-tree clean).

① Derived judgments

  1. The contract: packages/spec/src/ui/app.zod.ts:108 names 'doc' among the navigation-item types; DocNavItemSchema (:715) takes book / doc with requiredOneOf(['book', 'doc']) (:731) and a doc-name regex (:674); the doc comment (:676-714) gives the two targets, the both-keys case and the inherited docs audience gate. The console at the .objectui-sha pin a58626c88 carries case 'doc' in NavigationRenderer.tsx (two arms) and the unreadable-target hide in nav-menu-renderer.tsx. The new Routing model paragraph says exactly those things and nothing more. Correct.
  2. The build-time check the paragraph names: packages/cli/src/utils/collect-docs.ts lintDocNavTargets, rule docs/nav-target (its test file: "a doc nav item must open something this package has"). Correct. The url clause ("for an external or custom URL") is SKILL.md's own table row. Correct.
  3. Eval 4: the expected_output tail now names the doc item; must_contain swaps /docs/ for type: 'doc' and must_not_contain gains type: 'url'. The evals README defines the assertions as string checks, under which /docs/ was contained in the sibling entry src/docs/ and could never fail alone — the swap is a repair, not a loss; the eval's prompt needs no url item. The file parses; evals 1–3 and 5–6 are byte-identical. Correct.
  4. Ratchet, recomputed by the seat from the head blobs (ceil of utf8 bytes over 4): pages.md 22741 bytes = 5686 tokens (ceiling 5692, headroom 6), the eval 6017 bytes = 1505 (ceiling 1505, headroom 0); no ceiling row changed, no new file, no re-wrap. The +148-byte paragraph is paid by one deletion in Page Types: the tombstone sentence naming six former page types; its rule ("only types with a dedicated renderer are authorizable", ADR-0049, exactly five values) is the section's lead, PageTypeSchema refuses the former names at parse, and record_detail stays in the Disambiguation sentence (eval 4's must_not_contain still has a source). The six names as a list are the one non-restated item — flagged for the approver in the brief. Correct.
  5. Scope: git diff --numstat base..head = the two claimed files (7/7 and 3/3); SKILL.md untouched (its table already lists doc); no packages/**, no content/docs/**. 23 path-derived commands re-derived on the dev's tree, identical to the seat's list, each exit captured, --ran reconciliation 23 / 23 / 0 NOT-MEASURED; the lint build under the verify lock before check:doc-formula-expressions. The commit is authored as objectstack-fleet[bot] (the order's identity line held). The CI reading at this record's clock is in the ACCEPT on skills(ui): pages.md Routing model says there is no doc nav-item type and eval 4 expects a url item, while the spec declares type: 'doc' (app.zod.ts:108) and SKILL.md lists it #22291.

② Semver level

None. Published skill text and one eval; no package content, API or schema. skip-changeset is the correct declaration — this time the dev's own label-write went through (labels skip-changeset, assignee huangyiirene read back), so the seat applied nothing but needs-user-decision.

③ Boundary flags

  • Dev observation (not a finding class): UrlNavItemSchema.url is described "Target external URL" while the console resolves internal paths through it — describe text, no behaviour; noted, no carrier.
  • Dev observation: the dead /docs/ assertion — repaired inside this PR; no other subsumed entry in the two ui eval files (the dev read them whole; the seat checked eval 4 only).
  • open_questions: none; premise_still_valid: true (the card's line numbers moved with PR skills(ui): the page skill learns the print page — print, the printable block subset and its lint (#22271) #22287; the sentence and the eval were unchanged).

Implemented-by: claude/issue-22291-ui-skill-doc-nav-item
Reviewed-by: session_01CXydFDyiQwNbGFkmwrcRQq

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

席位:skills 席 1,session_01CXydFDyiQwNbGFkmwrcRQq,2026-10-08T13:45Z。对照席位自己读的 diff(head 2bdf0f6317,两个文件 +10/−10)与 origin/main 上的 spec 源码(app.zod.ts:108 的 'doc'、:715 的 DocNavItemSchema 与 requiredOneOf(['book', 'doc']))、objectui pin a58626c88 的 NavigationRenderer.tsx(case 'doc')和 CLI 的 docs/nav-target 校正 dev 草稿;契约复核记录(PASS)在本 PR 上一条评论,ACCEPT 在卡片 #22291。

改了什么:发布给每个客户项目的 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 项(dev 在构建后的 spec 上实测:旧写法 success: true),所以只有改技能文本这一条路。本卡来自 #22271 dev 的顺带发现,由本席立卡并定级。

风险与代价(含回滚):发布技能包有 token 棘轮:pages.md 只剩 1 token 余量,新段落比旧段落多 148 字节,靠删除「Page Types」里被开头句重述的一句(「以前路线图类型(六个名字)已从枚举移除」,169 字节)付账,净 −5 token,落在 5686/5692;评测文件 +3 token,恰好 1505/1505(余量 0:下次动这个 eval 文件必须再删);上限未动、无换行凑数、无新文件。唯一不是逐字重述的信息是那六个旧类型名——spec 的 PageTypeSchema 在解析时会拒绝它们并列出五个合法值,record_detail 仍留在消歧句里,请你知情。评测里原来的 /docs/ 断言是死断言(被同列表的 src/docs/ 子串包含,从未能单独失败),这次换成活断言 type: 'doc'。风险:AI 作者把 doc 目标写成路径或文件名——spec 拒绝并给出正确拼写;写了包里没有的名字——os build 的 docs/nav-target 拒绝。回滚 = revert 落地的那一个 commit,无代码、无 changeset。

席位意见:同意合入,席内契约复核 PASS。席位自己核过:新段落的每个从句都有源码出处(两种目标与两键同给、受众门禁、docs/nav-target、url 行的措辞);删除句的规则由节首句承载;eval 4 的 JSON 可解析、其余评测逐字节未动;棘轮读数席位从 head blob 重算一致。本次 dev 的 label-write 未被其分类器拒绝(本班首次),skip-changeset 与 assignee 由 dev 自贴、席位回读核对。

你要做的:用授权账号(os-zhuang 或 hotlong)在本 PR 上 APPROVE 一次,或亲手合入。PR 保持 draft;收到授权 APPROVED 后由本席落地并收口 #22291(若批准者已自行翻 ready 入队,本席只做收口)。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 8, 2026 13:49
@os-zhuang
os-zhuang enabled auto-merge October 8, 2026 13:49
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit fe72ad7 Oct 8, 2026
41 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-22291-ui-skill-doc-nav-item branch October 8, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants