Repository navigation
docs(skills): app-composition teaches object-nav target exclusivity, not a refused precedence - #9227
Conversation
…he spec refuses The app-composition guide's "Contract Reminders (spec-strict)" list taught an object-item target precedence (recordId, then filters, then viewName). The installed @objectstack/spec 17.4.0 has no such order: objectNavTargetExclusivity refuses `filters` combined with `recordId` or `viewName`, and `runAction` combined with `recordId`; only `recordId` + `viewName` is tolerated. Since the guard was chained into objectui's NavigationItemSchema, an author following the old line wrote a nav item safeParse refuses. Replace the one reminder line with the exclusivity rule in the spec's own wording, naming the two asymmetries so a reader does not over-correct to "all exclusive". Net +1 line in the published guide; no code moves. Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK Co-authored-by: Claude <noreply@anthropic.com>
维护者速读(终稿)改了什么 — 发布给使用者的 skill 指南 为什么改 — 旧句子写在「spec-strict」标题下,AI 照它写出的导航元数据会被安装版 风险与代价(含回滚) — 只改一行说明文字,不发版(changeset 门禁判无需声明), 席位意见 — 席内按契约复审档复核通过:文件面只有这一份指南,行数 61→62 在派发预算内,token 门禁、路径门禁、控制字节本席在该 head 亲自重跑均绿;措辞与安装版 spec 的 describe 原文一致,两处不对称写得准确。建议合并。 你要做的 — 受管面( Generated by Claude Code |
Fixes #9059
What changed
One reminder line in the published skill guide
skills/objectui/guides/app-composition.md, under 「Contract Reminders (spec-strict)」. The line taught an object-item target precedence (recordId, thenfilters, thenviewName). The installed@objectstack/spechas no such order — itsobjectNavTargetExclusivityguard refuses the combination — so an author following the line wrote a nav item the schema refuses. The line now states the exclusivity rule in the spec's own wording and names the two asymmetries the rule keeps on purpose, so a reader does not over-correct to «all exclusive»:filtersis mutually exclusive withrecordId/viewName(refused, issue pathfilters);runActionis refused withrecordId(it composes withviewNameorfilters);recordId+viewNameis tolerated.No precedence sentence survives:
grep -ci precedenceon the file reads 0 after the edit (1 before). No code moves; the runtime fallback inresolveHrefand its test are untouched by design (see the card).Governed surface (
skills/**,GOVERNED_SURFACESskills-catalog): this PR stays a DRAFT for the maintainer to merge.node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/app-composition.mdprinted⛔ GOVERNED — 1 of 1 path(s) are on a governed surface(exit 3).Evidence the taught shape is refused (installed package, not upstream main)
Installed
@objectstack/specin this worktree afterpnpm install: 17.4.0 (node_modules/.pnpm/@objectstack+spec@17.4.0_…). Fromdist/ui/index.jsof that package:filtersdescribe text ends with the literal sentenceMutually exclusive with recordId/viewName.;objectNavTargetExclusivity(item, ctx)adds an issue whenitem.filters && (item.recordId || item.viewName)(pathfilters) and whenitem.runAction && item.recordId(pathrunAction); no other pair is refused, and therecordIddocblock saysviewNameis ignored if both are set.Probe against objectui's own
NavigationItemSchema.safeParse(packages/types/src/zod/app.zod.ts, run withpnpm exec tsx, eight fixtures):The existing pin
packages/types/src/__tests__/nav-target-exclusivity-8563.test.ts(chained guard from objectui#8563):pnpm exec vitest run packages/types/src/__tests__/nav-target-exclusivity-8563.test.ts→Test Files 1 passed (1),Tests 15 passed (15), exit 0.Sweep: the skill was the last teaching site outside the ADR
Repo-wide grep on the edited tree for a precedence claim naming these fields (regex over
recordIdfollowed by an arrow andfilters/viewName, orprecedencewithin 80 chars ofrecordId; excludesnode_modules,dist, CHANGELOGs). Control: 566 lines mention «precedence» under the same exclusions, so the instrument is not selecting on emptiness. Remaining hits and why each is not a teaching site:docs/adr/0055-parameterized-bare-data-surface.md:52— the ADR item 5; a decision record, its amendment is the maintainer's call (sibling card per the PM). Note for that card: the file lives in THIS repo'sdocs/adr/, not in objectstack.packages/layout/src/__tests__/resolveHref.test.ts:11,60— the ruled-out test describing deliberate runtime fallback for pre-guard documents.packages/layout/src/NavigationRenderer.tsx:562andpackages/app-shell/src/views/metadata-admin/inspectors/nav-target.ts:10— code comments describing that sameresolveHrefruntime resolution order (fallback behaviour, not the authoring contract). Noted, not filed; same class as the test.packages/types/src/__tests__/nav-target-exclusivity-8563.test.ts:189and.changeset/8563-nav-target-exclusivity-chained.md:35— both quote the removed sentence as the thing that was removed.content/docs/guide/designing-app-navigation.md(the human-facing derivative this guide names) carries no precedence or exclusivity sentence, so nothing there moves.Published-skill budget (PM-set: net at most +1 line)
skills/objectui/guides/app-composition.md: 61 → 62 lines (3 lines replaced by 4; net +1, paid by the reminder itself, no re-wrap elsewhere).skills/objectuipackage,SKILL.md+ everyguides/*.md: 3584 → 3585 lines; every*.mdunderskills/objectui(addsREADME.mdandrules/): 4587 → 4588.pnpm check:skill-eval-tokens, exit 0):Red under the chosen oracle: 0 (0 beyond the baseline)./Every must_contain token is taught by its own skill bundle.The app-composition evals'must_containtokensfilters,viewName,objectName,/data,{current_user_id}are still taught by the file.Gates run locally (exit captured before any pipe; verdict lines quoted)
pnpm check:skills-paths✅ check-skills-paths: OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined).pnpm check:skill-eval-tokensEvery must_contain token is taught by its own skill bundle.pnpm check:skill-examples(after the scoped build the gate itself prescribes, 29/29 tasks under the shared verify lock, VERDICT command-exit 0)Every marked skill example holds up against the built types.pnpm check:control-bytes✅ check-control-bytes: OK (scanned 7408 tracked text file(s); skipped 85 binary).pnpm check:comment-mask-corpus✓ comment-mask corpus sweep … 4855 files, 1 disagree (0 comment bytes read as code, 1517 code bytes read as comment), 0 unparseable(same reading as the untouched baseline)node scripts/check-changeset-presence.mjs✅ No source or published contract of a released package changed in this range, so no changeset is owed.— so no changeset file is addednode scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/app-composition.md⛔ GOVERNED — 1 of 1 path(s) are on a governed surfaceAll readings are from head
efd2d6074. The skills workflows (skills-paths.yml,skill-examples.yml,skill-eval-tokens.yml) deliberately carry no path filter, so they run on this PR regardless;governed-surface-guard.ymlandchangeset-presence.ymlrun too.Acceptance notes
resolveHref's fallback order for documents that predate the guard; they are accurate as runtime descriptions and are not authoring guidance. Carrier: none.filtersvalue type is a record of string to string, serialized asfilter[FIELD]=VALUE) because GitHub strips tag-shaped fragments from stored bodies.https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK.维护者速读(草稿)
改了什么: 发布给使用者的 skill 指南
skills/objectui/guides/app-composition.md里「Contract Reminders (spec-strict)」下的一行:原来教 AI 「对象导航项的目标字段有优先级:recordId、其次 filters、再次 viewName」,现在改成 spec 实际强制的互斥规则:filters不能与recordId/viewName同时出现;runAction不能与recordId同时出现(但可以与viewName或filters搭配);只有recordId+viewName被容忍。净增一行,不动任何代码。为什么改: 旧的那句话是错的,而且错在最危险的方向上 —— 它写在「spec-strict」标题下,AI 按它写出的导航元数据会被
@objectstack/spec@17.4.0的objectNavTargetExclusivity和本仓的NavigationItemSchema直接拒收。已在安装好的 spec 包上逐字核对拒收条件,并用八个样例实测本仓 schema 的接受/拒绝结果(见上文 Evidence 节)。风险与代价(含回滚): 只改一行说明文字,不改运行时,不发版(changeset 门禁判定无需声明)。若措辞需要调整,直接改回或再改这一行即可;
git revert单个 commit 就是完整回滚。剩余仍写着旧优先级的两处是 ADR-0055 第 5 项(决策记录,是否修订由维护者定,PM 另开卡)和resolveHref的运行时兜底注释/测试(它们描述的是对旧文档的容错行为,不是编写契约),本 PR 都刻意不碰。席位意见:
你要做的: 这是受管面(
skills/**),agent 不能自行合并。请审阅这一行的措辞是否与 spec 一致,认可即合并;若希望连 ADR-0055 一起改,请在 PM 另开的那张卡上表态。Generated by Claude Code