Repository navigation
Commit c2716f8
docs(skills): the objectui action-block rule teaches variant 'danger' for a destructive action; destructive is the Button primitive's word (#27588)
Part of #27243 — this PR lands the teaching line and the changeset. The
eval case the card also wants cannot be paid under
`skills/objectui/evals/page-builder.json`'s ceiling by honest deletion
(560 tokens, headroom 0; the case costs 124 tokens after every
tightening), and the claim forbids a `CEILINGS` raise, so it waits for
the seat's decision — Acceptance notes.
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:objectui 技能包 `rules/protocol.md` 的「Actions Are Node
Types」规则新增一句:`"variant": "danger"` 让动作块变红并经由 destructive
确认按钮提问;`destructive` 是 Button 原语的词,写在动作块上保存时被拒。同文件删去两句他处已讲的话付账。一条
`@objectstack/skills` patch changeset。
- 为什么改:PR #26966 把动作块的 `variant` 从 `destructive` 改名 `danger`,但技能包没教过动作块的
`variant`;AI 作者只能从 React 示例里抄到 `destructive`,写到动作块上即被保存拒收。
- 风险与回滚:只改一份技能 markdown 与一条 changeset,不动
spec、运行时、评测数据;`rules/protocol.md` 4357 → 4366 / 4368,上限不动;回滚即 revert 本
PR。
- 先看:`skills/objectui/rules/protocol.md`(新增段落 L275–L278;删去的两句原在
L139–L141 与 L161–L163,均为同文件他处已陈述的事实)。
- 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期见
`skills/objectui/rules/protocol.md 4366 / 4368 (-2)` 且末行为绿勾。
- 需要你做的:评测用例欠一个决定(见 Acceptance notes 第一条):按 2026-10-27
裁决「A:先削重复,余额按实测提高」把 `page-builder.json` 的上限抬 +124,或改走别的路;Tier H
技能面,席位复核后由维护者 APPROVED 落地。
## What changes
- `skills/objectui/rules/protocol.md`, rule "Actions Are Node Types, Not
An Event Bag": one paragraph after the `action:button` example.
`"variant": "danger"` draws the button red and asks its `confirmText`
question through the destructive confirm button; `destructive` is the
Button primitive's word (`variant="destructive"` on the React Button,
which `guides/auth-permissions.md` L250 and `rules/composition.md` L143
already show); on an action block the spec refuses it at save, naming
the rename.
- Paid in the same file, both deletions stated elsewhere in the file:
the provenance sentence "The `dataSource` wiring this was first measured
through is retired; the envelope readings below are unchanged by that"
(the retirement is taught under "Rule: Data Binding Path Resolution",
now L220–L229), and the closing restatement "What is *not* open: a
`${...}` on the node is never evaluated, and a key under `props` never
reaches a `ui:*` / `page:*` renderer at all" (both facts are the rule's
own opening paragraph, L105–L111, and rows 2 and 4 of its table). No
re-wrap counted as payment.
- `.changeset/27243-objectui-action-variant-eval.md`:
`'@objectstack/skills': patch`, carrying both declaration lines.
Every claim in the line, against `origin/main` `72b08eec`:
| Claim | Source |
|:---|:---|
| the action blocks' `variant` enum is `default`, `danger`, `outline`,
`secondary`, `ghost`, `link` (+ `primary` on `action:button`) |
`packages/spec/src/ui/component.zod.ts` L3451 `ACTION_BLOCK_VARIANTS`;
L3944 `actionBlockVariant("An `action:button`'s", ['primary'])` |
| `destructive` is refused on an action block, with the rename |
`component.zod.ts` L3460–L3463 `actionBlockVariant` error map keyed on
`issue.input === 'destructive'`; L3472–L3477
`refuseActionBlockDestructiveVariant`; the protocol-18 entry
`packages/spec/src/migrations/entries/semantic/18.ui-page-action-block-variant-destructive-refused.ts`
says where it is met: "the next save of the page meets the
component-props gate at the metadata save door; `os validate` reports
the old value as a `component-props-invalid` finding" |
| `danger` draws the destructive button and the confirm button
destructive | `component.zod.ts` L3945 describe;
`ui/packages/components/src/renderers/action/runner-variant.ts`
`toButtonVariant` (`danger` → `destructive`) and `toRunnerVariant`;
`ui/packages/components/src/renderers/action/action-button.tsx`
L207–L209 and L330–L333; `ui/packages/core/src/actions/ActionRunner.ts`
L551–L553 and L1611–L1613 (`confirmOptions`: a `danger` action paints
the confirm button destructive) |
| `destructive` is the Button primitive's own word |
`ui/packages/components/src/ui/button.tsx` L21 variant `destructive`;
`component.zod.ts` L2230 and L2342 describe it as "the Button
primitive's own vocabulary" |
| the ADR-0087 D2 conversion rewrites stored rows |
`packages/spec/src/conversions/registry.ts` L11547
`page-action-block-variant-destructive-to-danger`, `toMajor: 18` |
## Tests
Token budget (`ceil(utf8 bytes / 4)`, the ratchet's convention; no
ceiling moves):
| File | Tokens before / after (ceiling) | Lines before / after |
|:---|:---|:---|
| `skills/objectui/rules/protocol.md` | 4357 / 4366 (4368) | 374 / 377 |
| `skills/objectui/evals/page-builder.json` | 560 / 560 (560) —
untouched, see Acceptance notes | 56 / 56 |
- Bytes: 17428 → 17464, net +36 (changed lines: 341 bytes added, 305
deleted). Headroom 11 → 2 tokens.
- Whole-package reading: the sum of every `SKILL.md` under `skills/` is
unchanged (none touched); the `skills/objectui` bundle is 202254 bytes
before and 202290 after (`git ls-tree -r -l`); the whole shipped tree
reads 219136 → 219145 tokens on the ratchet's bundle-total line.
- `node ui/scripts/check-skill-eval-tokens.mjs` exits 0 before and after
("Every must_contain token is taught by its own skill bundle"). After
this PR the bundle teaches `"variant": "danger"` and `confirmText` as
whole tokens, which the pending eval case's `must_contain` needs.
## Acceptance notes
- **The eval case, measured and not landed.** Fixture:
`skills/objectui/evals/page-builder.json` (it owns the action-block
cases, 1 and 3; `protocol.json`'s diagnostic shape would put the refused
spelling in the prompt and fail an answer that quotes it). The case:
prompt "Add a 'Delete product' button to the product detail page. It
must read as destructive and ask the user to confirm before the record
is removed."; expected output names an `action:button` whose
`properties` carry `label`, `actionType`, `target`, a `confirmText`
question and `variant 'danger'`, red and confirming through the
destructive confirm button, with the primitive's spelling refused on a
block at save; `must_contain` `action:button`, `"variant": "danger"`,
`confirmText`; `must_not_contain` `"variant": "destructive"` (a JSON
key-value literal, so an answer that says `destructive` is the
primitive's word, or shows `variant="destructive"` on the React Button,
still passes; the expected output does not spell it). Cost: the file is
2238 bytes against a 2240-byte ceiling. With the case written lean (457
bytes of prose, 790 with its JSON frame) and every existing prompt and
rubric tightened the way PR #27426 paid (every `must_contain` token and
fact kept, 1206 → 914 bytes of prose), the file reads 2735 bytes = 684
tokens: **+124 over the ceiling**, and nothing left to delete without
losing an assertion. The route the ratchet header records for this shape
is the 2026-10-27 ruling 「A:先削重复,余额按实测提高」(decision card 「skills 修复卡的教学和
eval 都撞 token 天花板,怎么付?」), under which `CEILINGS` rows moved +160, +194,
+224, +286 and +297 on 10-27/10-28 for evals that could not be paid
down. The claim for this card forbids a raise, so the case is held: the
drafted fixture is in the report on the card, ready for a patch round on
this PR once the seat rules.
- **Out of scope, class b (contract text contradicts the schema), reach:
release text.**
`packages/spec/src/migrations/entries/semantic/18.ui-page-action-block-variant-destructive-refused.ts`
`reason` says "Other components that draw the Button primitive directly
(`element:button` and the like) keep `destructive`".
`ElementButtonPropsSchema.variant` (`component.zod.ts` L2968) is
`primary | secondary | danger | ghost | link` — no `destructive` — and
`ui/packages/components/src/renderers/basic/elements.tsx` L308–L314 maps
`danger` → shadcn `destructive`. The card body repeats the same sentence
("`destructive` stays the word for the Button primitive
(`element:button`)"). The entry's text is compiled into the upgrade
guide (`gen:upgrade-guide`), so the wrong sentence ships. This PR's line
says "the Button primitive's word (`variant="destructive"` in React)"
and does not name `element:button`. Not fixed here; noted for the seat
(dedupe words: `element:button destructive`, `upgrade guide variant`,
`18.ui-page-action-block-variant-destructive-refused`).
- The commit's author field reads `marchtian` with the configured
`noreply@anthropic.com` address: a `-c user.name` I passed on the commit
by mistake (the worktree's configured `user.name` is `Claude`). An amend
plus `--force-with-lease` was refused by the auto-mode classifier (Git
Destructive) and not retried; the trailer pair is model-free and
correct.
- Locally NOT MEASURED, prerequisites absent on an unbuilt tree, CI's
Lint and Repo Gates run them built: `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:sourcemap-no-sources-content` (exit 3: no `dist/` for most
packages), `@objectstack/lint check:doc-formula-expressions` (exit 3:
`@objectstack/formula` not built).
## Verification
Head `133ce6d8` on `origin/main` `72b08eec` (no merge needed: the branch
is one commit on the current main). Derived with `node
scripts/pm/dispatch-gates.mjs --commands` (no paths; 45 commands from
the committed diff).
<details><summary>Derived gates: 40 exit 0, 5 exit 3 (prerequisite), 1
measured in two legs</summary>
- Exit 0: `check-skills-token-ratchet` (84 authored bundle files within
their ceilings) and its `--self-test`; `@objectstack/spec
check:skill-examples` under the verify lock after `pnpm --filter
'@objectstack/client-react...' build` (VERDICT command-exit 0; "274
prose examples type-check across 3 surface(s)");
`check-adr-0087-registration --base origin/main` and `--self-test`;
`check-changeset-no-major`; `check-empty-changeset` and `--self-test`;
`check-closing-keyword-parity` and `--self-test`;
`check-comment-mask-corpus`; `check-doc-route-spelling --advisory` and
`--self-test`; `check-dts-emitted --self-test`;
`ci/assert-turbo-remote-cache`, `ci/plan-test-core-shards`,
`ci/prune-turbo-cache`, `pm/release-rehearsal-clone`,
`release-pending-publish` `--self-test`; `check:agent-test-spelling`,
`check:changeset-gate-self-tests`, `check:ci-filter-parity`,
`check:closed-ceremony-text`, `check:corpus-claim-drift`,
`check:cross-package-test-inputs`, `check:doc-authoring`,
`check:driver-memory-census`, `check:gitlink-declared`,
`check:nul-bytes`, `check:pm-changeset-deadline-census`,
`check:pm-governed-merges`, `check:pm-skill-id-lint`,
`check:published-files`, `check:refd-timer-probe`, `check:role-word`,
`check:skill-compatibility`, `check:skill-frame-sync`,
`check:skill-identifier-liveness`, `check:watch-hint-literal`.
- Exit 3, PREREQUISITE NOT MET (Acceptance notes): `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:sourcemap-no-sources-content`, `check:doc-formula-expressions`.
- `pnpm --dir ui exec vitest run --maxWorkers=2 scripts/` under the
verify lock, two legs. Leg 1, before any package was built: VERDICT
command-exit 1, Test Files 16 failed / 153 passed / 2 skipped (171),
Tests 56 failed / 4574 passed / 17 skipped; every failure is `Cannot
find package '@objectstack/spec/...'` (15 files) or
`date-only-zone-pins-10183.test.ts`, whose spawned plugin suites "ran no
case at all" on the unbuilt ui workspace. Leg 2, after building
`@objectstack/spec` and the client-react closure: the 15 spec-dependent
files plus the three skill gates' tests (`check-skill-eval-tokens`,
`check-skill-examples`, `check-skills-paths`) re-run as `vitest run
check-skill check-doc-expression-carriage spec-symbol-ledger
dollar-dialect check-component-surface-parity
check-designer-field-key-parity check-action-forward-parity
check-doc-json-fences check-doc-snippet-types console-optimize-deps`:
VERDICT command-exit 0, Test Files 19 passed (19), Tests 674 passed
(674). `date-only-zone-pins-10183.test.ts` stays NOT MEASURED locally
(it needs the ui plugin packages built); CI's Test UI runs it.
- `dispatch-gates --ran` with each exit code recorded (the vitest row
recorded as its leg-1 exit 1): 45 derived families accounted for — 40
run, 5 NOT-MEASURED, derived from the recorded exit 3; exit 0.
</details>
Also run, outside the derived list: `node
ui/scripts/check-skill-eval-tokens.mjs` exit 0; `grep -naP` for raw
control bytes over the two touched files finds none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ihL2dPRZXfgE1A2Vct3FU)_
---------
Co-authored-by: marchtian <noreply@anthropic.com>1 parent f7ff693 commit c2716f8
4 files changed
Lines changed: 58 additions & 11 deletions
File tree
- .changeset
- scripts
- skills/objectui
- evals
- rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1175 | 1175 | | |
1176 | 1176 | | |
1177 | 1177 | | |
1178 | | - | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
1179 | 1194 | | |
1180 | 1195 | | |
1181 | 1196 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
7 | | - | |
| 6 | + | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
26 | | - | |
| 25 | + | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
43 | | - | |
| 42 | + | |
| 43 | + | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
54 | 70 | | |
55 | 71 | | |
56 | 72 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
140 | | - | |
141 | | - | |
| 140 | + | |
142 | 141 | | |
143 | 142 | | |
144 | 143 | | |
| |||
159 | 158 | | |
160 | 159 | | |
161 | 160 | | |
162 | | - | |
163 | | - | |
| 161 | + | |
164 | 162 | | |
165 | 163 | | |
166 | 164 | | |
| |||
274 | 272 | | |
275 | 273 | | |
276 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
277 | 280 | | |
278 | 281 | | |
279 | 282 | | |
| |||
0 commit comments