Repository navigation
Commit 8af914a
docs(skills,docs): teach page:header actions as declared action ids (#20257)
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](https://claude.ai/code/session_01MjvgiFAmjHqsxy1XLiVYfH)_
Co-authored-by: os-dev <steve@objectstack.ai>
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6704717 commit 8af914a
3 files changed
Lines changed: 22 additions & 9 deletions
File tree
- content/docs/protocol/objectui
- skills/objectstack-ui
- evals
- rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
82 | 95 | | |
83 | 96 | | |
84 | 97 | | |
| |||
93 | 106 | | |
94 | 107 | | |
95 | 108 | | |
96 | | - | |
| 109 | + | |
97 | 110 | | |
98 | 111 | | |
99 | 112 | | |
| |||
994 | 1007 | | |
995 | 1008 | | |
996 | 1009 | | |
997 | | - | |
| 1010 | + | |
998 | 1011 | | |
999 | 1012 | | |
1000 | 1013 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
90 | | - | |
| 90 | + | |
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
124 | | - | |
125 | | - | |
126 | | - | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| |||
0 commit comments