Skip to content

docs(skills,docs): teach page:header actions as declared action ids - #20257

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20173-page-header-action-ids
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20173-page-header-action-ids

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20173

Clause-②: no

The objectstack-ui skill (rules/pages.md and its eval) taught full Action objects in page:header.properties.actions, and the layout-dsl docs page taught actions: [edit, delete, share] with no such actions declared. The contract is action ids: packages/spec/src/ui/component.zod.ts declares actions: z.array(z.string()).optional().describe('Action IDs to show in header'), and ruling B on #11592 (a completed card, not reopened here) made the canonical page:header implement that id lookup. This PR teaches the id and where it resolves; it changes no renderer and no spec (out of scope: the objectui object arm is an undeclared transition tolerance, retired on its own card).

What changed

  • skills/objectstack-ui/rules/pages.md — catalogue row: actions: string[] (action ids), 「inline」 dropped; example: actions: ['convert_lead'] naming the action the same block declares on lead; the 「Actions in header」 note now says the ids resolve against the bound object's declared actions and that there is no built-in id registry (the 「no sibling action node」 rule stays).
  • skills/objectstack-ui/evals/views-apps-actions-pages.json — eval 4 expected_output: 「the declared action's id in properties.actions」 instead of 「the Action object passed in」. The must_contain list is unchanged on purpose: the prompt does not fix the action's machine name, so pinning convert_lead there would grade the author's naming, not the shape.
  • content/docs/protocol/objectui/layout-dsl.mdx — Standard Template: one sentence states the rule (ids of the bound object's declared actions; edit / delete / share resolve nowhere; the host draws its own Edit / Delete chrome), a tagged action block declares send_statement on account with locations: [record_header], and the page names actions: [send_statement]. Customer 360: the undeclared ids are replaced by a one-line YAML comment pointing at that rule (the header keeps its title; nothing on that page declares an action on customer, and a second declaration block was not worth the length).

Where the id resolves (objectui at the pin f8a9d0fb, read with git -C ../objectui grep)

packages/components/src/renderers/layout/containers.tsx (PageHeaderRenderer): the authored array goes through resolveDeclaredActionIds (@object-ui/types, ui-action.ts) against useMetadataItem('object', headerObjectName).actions — the bound object's own declared actions. An id that resolves nowhere logs 「object … declares no action by that name. Declared: …」 and draws nothing. There is no built-in id registry; the host's Edit / Delete chrome arrives separately as headerSystemActions (sys_edit, sys_delete in app-shell/src/views/RecordDetailView.tsx), so authored edit / delete ids neither resolve nor dedupe against it.

Reach, measured at the public door (os validate)

Four plain-object configs in a scratch directory, run with the CLI's source entry the CLI's own e2e tests use: node_modules/.bin/tsx packages/cli/bin/run-dev.js validate objectstack.config.ts (spec, lint, core, metadata-core built first; origin/main at 14ae40b0, which includes PR #20171).

fixture shape result
skill, as published ConvertLeadAction object in properties.actions exit 0 with ⚠ page "lead_detail_page" · page:header: actions.0: Invalid input: expected string, received object (component-props-invalid)
skill, as taught now actions: ['convert_lead'], action declared with objectName: 'lead' ✓ Validation passed, no advisories
docs, as published actions: ['edit', 'delete', 'share'], none declared exit 1, ✗ Author-time rules failed (3 issues) — action-name-undefined at properties.actions[0..2]: 「names action "edit", which is defined by no action in this stack … The header draws no button for it」
docs, as taught now send_statement declared on account, actions: ['send_statement'] ✓ Validation passed, no advisories

(The source entry prints oclif MODULE_NOT_FOUND noise for unrelated commands whose packages are not built here — i18n:check, migrate:meta; the validate command loaded and ran in every case, as the ◆ Validate block that follows the noise shows.)

Line and token budget (skills/**, token = ceil(utf8 bytes / 4), the ratchet's own convention)

file lines before → after tokens before → after (ceiling)
skills/objectstack-ui/rules/pages.md 454 → 454 5692 → 5692 (5692; bytes 22765 → 22767 of 22768)
skills/objectstack-ui/evals/views-apps-actions-pages.json 65 → 65 1505 → 1505 (1505; bytes 6020 → 6020)
whole package skills/objectstack-ui/** (11 tracked files) 2239 → 2239 sum of per-file ratchet readings unchanged; raw bytes 123483 → 123485

Net lines: 0 per file, 0 for the package. No ceiling moved; node scripts/check-skills-token-ratchet.mjs is green at head 9c49e874 (pages.md … 5692 tokens (ceiling 5692; headroom 0)).

Gates (derived from the diff by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run at head 9c49e874)

46 commands derived, 46 run, every exit code captured before any pipe as 0; --ran reconciliation: 「46 derived famil(ies) accounted for — 46 run, 0 NOT-MEASURED」. The rows this diff bears on:

  • pnpm --filter @objectstack/spec run check:skill-examples — 「259 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass」 (the os:check block in pages.md is one of them; needed @objectstack/client / client-react built).
  • pnpm --filter @objectstack/spec run check:yaml-examples — 「19 tagged YAML example(s) … validate against their declared live spec schemas」 (the new os:check-yaml action block and both page blocks included).
  • node scripts/check-skills-token-ratchet.mjs — 54 authored files within ceilings.
  • pnpm check:doc-authoring, check:doc-anchors, check:docs-single-h1, check:docs-spec-enumerations, check:skill-frame-sync, check:skill-identifier-liveness, check:skill-compatibility, check:nul-bytes — all 0.
  • No package's source is touched, so no package typecheck / test is owed; the diff has no ① build closure.

NOT MEASURED, CI's own: the Build Docs job (fumadocs build of content/docs), Test Core shards, the type-check lanes.

Changeset

skip-changeset: the diff is skills/** and content/docs/** only. No workspace package lists either path in files[] (grep over every packages/**/package.json); create-objectstack ships dist / README.md / CHANGELOG.md and installs the catalog at scaffold time via npx skills add objectstack-ai/objectstack/skills (its src/skills-install.ts), not from the tarball. Built-dist grep after the build: the diff's text (send_statement, 「ids of lead's actions」, 「(action ids)」) hits nothing under any built package's files[]; the positive control 「Action IDs to show in header」 hits packages/spec/dist/ui/index.js.

Acceptance notes

  • Family sweep at base 14ae40b0: git grep -nE "actions: \[[A-Z][A-Za-z]*Action" -- skills content/docs → 1 hit (pages.md:90); page:header examples with undeclared ids → 2 (layout-dsl.mdx:96, :997) plus the skill's object-shaped one. At head: the regex sweep → 0 hits; every page:header example on both surfaces names only declared ids or none (layout-dsl.mdx:109 send_statement, declared at :88; pages.md:90 convert_lead, declared at :69; Customer 360 names none).
  • Observation, not filed (read-only, outside this family): layout-dsl.mdx:636 record:related_list with actions: [standard_new] — action-name-undefined deliberately does not walk that key (actions is declared separately on record:related_list), so it is not the header family; whether standard_new is a renderer built-in was not measured here. Carrier: none.
  • origin/main moved from the dispatch's 08c8484a to 14ae40b0 before the branch was cut; every site in the card was re-read there and is unchanged.
  • The commit trailer pair is the model-free form the pre-push hook requires; the harness-suggested co-author spelling was refused once and amended before the first push.

维护者速读(草稿)

改了什么 — 修正 objectstack-ui 技能包(rules/pages.md、eval 4)与 layout-dsl 文档页里 page:header 的 actions 写法:从「传入完整 Action 对象」/「[edit, delete, share]」改为声明在绑定对象上的 action id,并说明 id 在哪里解析(对象自己声明的 actions,没有内建 id 注册表)。文档示例补了一段 send_statement 的 action 声明,让示例自洽。

为什么改 — 契约是 PageHeaderProps.actions: string[](#11592 裁决 B,objectui 已实现)。技能与文档教的形状,os validate 一个报 component-props-invalid,一个报三条 action-name-undefined(本 PR 正文有实测命令与输出);跟着技能写的 AI 作者会直接写出被校验器拒收、渲染器不画按钮的元数据。

风险与代价(含回滚) — 只改教学文本,不动 spec、不动渲染器;skills/** 净行数 0、token 棘轮读数不变(5692/1505 均持平,pages.md 字节 22765→22767,仍在 22768 上限内)。回滚 = revert 本 PR,无其他依赖。

席位意见 — (留空)

你要做的 — 阅读 pages.md 与 layout-dsl.mdx 两处措辞后,在本 PR 上给出 APPROVED review(Tier H,skills/** 只由维护者批准落地)。


Generated by Claude Code

`PageHeaderProps.actions` is `z.array(z.string())` — ids resolved against the
bound object's declared actions, with no built-in id registry. The
objectstack-ui skill (pages.md catalogue row, example and note) and its eval
taught full `Action` objects, and the layout-dsl docs page taught
`actions: [edit, delete, share]` with no such actions declared; `os validate`
flags both shapes. Teach the id, show where it resolves, and declare the
action the docs example names.

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9c49e8746dbe6ef28e835b5c92670dc811ad473c

Face: skills/** (governed, shipped to customer projects by npx skills add) and content/docs/** (the docs face). Reviewed in-seat at tier by the dispatching seat, on the diff (three files, +22 / −9) and the card only.

① Derived judgments

  • pages.md catalogue row, example and 「Actions in header」 note now teach page:header.properties.actions as the ids of actions declared on the bound object, with no built-in id registry — correct against the contract (packages/spec/src/ui/component.zod.ts:519: z.array(z.string()), 「Action IDs to show in header」) and against objectui at the pin f8a9d0fb (containers.tsx: resolveDeclaredActionIds against the object's declared actions; the refusal text reads 「PageHeaderProps.actions is a list of ACTION IDS」). The example id 'convert_lead' is the action the same block declares (:69, objectName: 'lead', locations: ['record_header']).
  • The eval's expected_output names the declared action's id; must_contain deliberately unchanged (the prompt fixes no machine name) — correct.
  • layout-dsl.mdx: a tagged action block declares send_statement on account and the Standard Template page names it; Customer 360 drops the undeclared ids and points at the rule. Reach measured at the public door both ways in the report (component-props-invalid on the object shape; action-name-undefined ×3 on the undeclared ids; ✓ Validation passed on the taught shapes) — consistent with PR fix(lint): action-name-undefined walks the record:alert CTA and page:header action ids #20171's rule on main. check:yaml-examples validates the new block (19 tagged examples).
  • No accept set and no public surface changes; no renderer or spec change (the objectui object arm stays an undeclared tolerance, retired on its own card). Token ceilings unchanged: pages.md 5692 / 5692 (22767 of 22768 bytes), eval 1505 / 1505; net lines 0. Family sweep at head: 0.

② Semver level

skills/** and content/docs/** only, in no workspace files[] (positive control 「Action IDs to show in header」 present in spec's dist) ⇒ no changeset; skip-changeset correct; consistent with Clause-②: no.

③ Boundary flags

No open_questions. Deviations accepted: Customer 360 gets a pointer comment instead of a second declaration block (the rule is demonstrated once and both examples validate); the commit trailer amended to the model-free pair before the first push (the pre-push hook governs); the probe ran through the CLI's own e2e source entry. No flag remains open.

Implemented-by: claude/issue-20173-page-header-action-ids
Reviewed-by: session_01MjvgiFAmjHqsxy1XLiVYfH

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)— PR #20257

改了什么:修正 objectstack-ui 技能包(rules/pages.md、eval 4)与 layout-dsl 文档页里 page:header 的 actions 写法:从「传入完整 Action 对象」/「[edit, delete, share]」改为声明在绑定对象上的 action id,并说明 id 在哪里解析(对象自己声明的 actions,没有内建 id 注册表)。文档示例补了一段 send_statement 的 action 声明,让示例自洽;Customer 360 示例去掉未声明的 id,留一行指向规则的注释。

为什么改:契约是 PageHeaderProps.actions: string[](#11592 裁决 B,objectui 已实现)。技能与文档教的形状,os validate 一个报 component-props-invalid,一个报三条 action-name-undefined(PR 正文有实测命令与输出);跟着技能写的 AI 作者会直接写出被校验器拒收、渲染器不画按钮的元数据。

风险与代价(含回滚):只改教学文本,不动 spec、不动渲染器;skills/** 净行数 0、token 棘轮读数不变(5692 / 1505 持平,pages.md 字节 22767,上限 22768)。回滚 = revert 本 PR。objectui 里仍容忍对象形状的那条过渡臂不在本 PR,分诊已另有卡。

席位意见:已对分支与 pin 处的 objectui 逐条核过 —— 三处技能文字与契约一致,示例 id convert_lead 就是同块声明的 action;pin 处 containers.tsx 只按对象已声明的 action 解析 id;文档页新增的 action 块过 check:yaml-examples,Build Docs 已绿;家族扫描在 head 上为 0。契约复核记录 PASS 在本 PR 上。建议批准。

你要做的:在本 PR 上 Approve。之后由本席清标、翻 ready、挂 auto-merge 走队列落地。


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 02:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 8af914a Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20173-page-header-action-ids branch September 28, 2026 02:34
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