Skip to content

docs(skills): app-composition teaches object-nav target exclusivity, not a refused precedence - #9227

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-9059-app-composition-exclusivity
Sep 12, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-9059-app-composition-exclusivity

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

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, then filters, then viewName). The installed @objectstack/spec has no such order — its objectNavTargetExclusivity guard 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»:

  • filters is mutually exclusive with recordId / viewName (refused, issue path filters);
  • runAction is refused with recordId (it composes with viewName or filters);
  • recordId + viewName is tolerated.

No precedence sentence survives: grep -ci precedence on the file reads 0 after the edit (1 before). No code moves; the runtime fallback in resolveHref and its test are untouched by design (see the card).

Governed surface (skills/**, GOVERNED_SURFACES skills-catalog): this PR stays a DRAFT for the maintainer to merge. node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/app-composition.md printed ⛔ 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/spec in this worktree after pnpm install: 17.4.0 (node_modules/.pnpm/@objectstack+spec@17.4.0_…). From dist/ui/index.js of that package:

  • the filters describe text ends with the literal sentence Mutually exclusive with recordId/viewName.;
  • objectNavTargetExclusivity(item, ctx) adds an issue when item.filters && (item.recordId || item.viewName) (path filters) and when item.runAction && item.recordId (path runAction); no other pair is refused, and the recordId docblock says viewName is ignored if both are set.

Probe against objectui's own NavigationItemSchema.safeParse (packages/types/src/zod/app.zod.ts, run with pnpm exec tsx, eight fixtures):

REFUSED  | taught precedence shape (recordId + filters + viewName) | custom@filters
REFUSED  | filters + recordId                                        | custom@filters
REFUSED  | filters + viewName                                        | custom@filters
REFUSED  | runAction + recordId                                      | custom@runAction
ACCEPTED | recordId + viewName (tolerated)
ACCEPTED | runAction + viewName (composes)
ACCEPTED | runAction + filters (composes)
ACCEPTED | valid filters-only item

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 recordId followed by an arrow and filters/viewName, or precedence within 80 chars of recordId; excludes node_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's docs/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:562 and packages/app-shell/src/views/metadata-admin/inspectors/nav-target.ts:10 — code comments describing that same resolveHref runtime 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:189 and .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).
  • Whole skills/objectui package, SKILL.md + every guides/*.md: 3584 → 3585 lines; every *.md under skills/objectui (adds README.md and rules/): 4587 → 4588.
  • Token gate verdict (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_contain tokens filters, viewName, objectName, /data, {current_user_id} are still taught by the file.

Gates run locally (exit captured before any pipe; verdict lines quoted)

gate exit verdict line
pnpm check:skills-paths 0 ✅ check-skills-paths: OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined).
pnpm check:skill-eval-tokens 0 Every 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) 0 Every marked skill example holds up against the built types.
pnpm check:control-bytes 0 ✅ check-control-bytes: OK (scanned 7408 tracked text file(s); skipped 85 binary).
pnpm check:comment-mask-corpus 0 ✓ 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 0 ✅ No source or published contract of a released package changed in this range, so no changeset is owed. — so no changeset file is added
node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/app-composition.md 3 ⛔ GOVERNED — 1 of 1 path(s) are on a governed surface

All 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.yml and changeset-presence.yml run too.

Acceptance notes

  • Out of scope, noted, not filed: the two runtime code comments named in the sweep section describe resolveHref's fallback order for documents that predate the guard; they are accurate as runtime descriptions and are not authoring guidance. Carrier: none.
  • Angle-bracket-shaped tokens are spelled out in words in this body (for example the filters value type is a record of string to string, serialized as filter[FIELD]=VALUE) because GitHub strips tag-shaped fragments from stored bodies.
  • Session of the seat that authored this: 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

…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>
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

维护者速读(终稿)

改了什么 — 发布给使用者的 skill 指南 skills/objectui/guides/app-composition.md 里「Contract Reminders (spec-strict)」下的一行:原来教「对象导航项的目标字段有优先级:recordId、其次 filters、再次 viewName」,现在改成 spec 实际强制的互斥规则:filters 不能与 recordId / viewName 同时出现;runAction 不能与 recordId 同时出现(可与 viewName 或 filters 搭配);recordId + viewName 被容忍。净增一行,不动代码。

为什么改 — 旧句子写在「spec-strict」标题下,AI 照它写出的导航元数据会被安装版 @objectstack/spec@17.4.0 的 objectNavTargetExclusivity 和本仓 NavigationItemSchema 直接拒收——这是本车道分级最重的那类缺陷(教 AI 写出运行时拒收的元数据)。dev 在安装包上逐字核了拒收条件,并用八个样例实测了本仓 schema 的接受/拒绝。

风险与代价(含回滚) — 只改一行说明文字,不发版(changeset 门禁判无需声明),git revert 一个 commit 即回滚。仍写着旧优先级的两处刻意不碰:ADR-0055 第 5 项(决策记录,是否修订由你定,已另开 objectui#9223 —— 注意该 ADR 在 objectui 仓,不在 objectstack)和 resolveHref 的运行时兜底注释/测试(描述对旧文档的容错,不是编写契约)。

席位意见 — 席内按契约复审档复核通过:文件面只有这一份指南,行数 61→62 在派发预算内,token 门禁、路径门禁、控制字节本席在该 head 亲自重跑均绿;措辞与安装版 spec 的 describe 原文一致,两处不对称写得准确。建议合并。

你要做的 — 受管面(skills/**):请人工合并 PR #9227(本席不翻 ready、不入队、不挂 auto-merge)。ADR-0055 第 5 项是修订、加注还是留作历史,请在 objectui#9223 上说一个字。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 12, 2026 06:06
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit bedd734 Sep 12, 2026
34 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-9059-app-composition-exclusivity branch September 12, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants