Repository navigation
Commit fbe344b
feat(spec)!: the action blocks spell the destructive variant
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBQfjnJWXwANqb5TscsSvrHR)_
Fixes #26779
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up owed — skills/objectui/evals/
(`ui/content/docs/guide/layout.md` now teaches `variant: 'danger'` on an
`action:button`; `check:doc-json` and `page-width-padding-refusal-11318`
judge that fence against the rows, but no eval exercises an action
block's variant)
## 维护者速读
- 改了什么:四个 action 区块(`action:button` / `action:icon` / `action:group` /
`action:menu` 及其成员)的 `variant` 把 `destructive` 改名为 `danger`,与
`ActionSchema.variant` 同一个拼法;旧值保存时拒收并给出改名提示,存量页面由 ADR-0087 转换自动改写。
- 为什么改:同一个危险动作在声明里写 `danger`、在区块里写
`destructive`,两套方言让每个渲染器都要翻译一次;协议只留一个拼法(分诊裁决 6448425754)。
- 风险与回滚:作者写的 `variant: 'destructive'` 区块在下次保存时被拒(收窄,带迁移处方);存量 `page`
行在加载时被转换改写。回滚 = revert 本 PR。
- 先看:`packages/spec/src/ui/component.zod.ts`(`ACTION_BLOCK_VARIANTS` 与
`actionBlockVariant`)、`packages/spec/src/conversions/registry.ts`(`pageActionBlockVariantDestructiveToDanger`)、`ui/packages/components/src/renderers/action/runner-variant.ts`。
- 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run
src/conversions/page-action-block-variant-destructive-to-danger.test.ts`,预期
35 个用例全绿(六行接受 `danger`、拒收 `destructive` 并给改名提示、存量行被改写)。
- 需要你做的:无,席位落地。
## What changes
**Spec (`packages/spec/src/ui/component.zod.ts`).** The five rows that
read `BUTTON_PRIMITIVE_VARIANTS` (`ActionButtonPropsSchema`,
`ActionIconPropsSchema`, the shared group/menu member shape,
`ActionGroupPropsSchema`, `ActionMenuPropsSchema`) now read
`ACTION_BLOCK_VARIANTS = default | danger | outline | secondary | ghost
| link`, plus `primary` on the three rows that took it before.
`BUTTON_PRIMITIVE_VARIANTS` had no other reader on `main` `8074feb6`, so
it is replaced, not kept beside. Each row builds its enum through
`actionBlockVariant(subject, extra)`, whose error map answers
`destructive` with the rename onto `danger` (naming the key the author
wrote) and leaves every other miss to the enum's own message. That is
the established enum-value retirement shape (`FORM_VIEW_LAYOUT_RETIRED`,
`OBJECT_FORM_LAYOUT_RETIRED`); it adds no `.refine()`, so no
dropped-refinements row. The `.describe()` texts say `danger` and what
it does.
**Data at rest.** ADR-0087 D2 conversion
`page-action-block-variant-destructive-to-danger` (`toMajor: 18`,
`retiredFromLoadPath: true`, `retiredAfter: '18.2.0'`, order 77). It
rewrites `properties.variant` on the four block nodes and `variant` on
each `action:group` / `action:menu` member, at every position
`mapPageComponents` visits. It also visits `action:bar`, renaming the
bar's own `properties.variant` and the `variant` of each `actions` /
`systemActions` member. The bar draws its members through the block
renderers with `member.variant || properties.variant`, so both spellings
would reach a renderer, and the bar has no props row to refuse
`destructive` at save (round 3, after the delta review `6464021995`).
Every other component (`element:button` keeps the primitive's
`destructive`) and every other value is left as stored. D3 entry
`ui-page-action-block-variant-destructive-refused`. One JOIN row in
`retired-key-removal-version.pin.test.ts` (six refusals; the pin derives
`@objectstack/spec 18` and `--from 17` from `retiredAfter`).
**ui renderers.** `runner-variant.ts` now holds two closed maps and
neither reads `destructive`: `toRunnerVariant` forwards `danger |
primary | secondary | ghost | link` as they are (and nothing for
`default` / `outline`), and `toButtonVariant` draws `danger` as the
primitive's `destructive` and `primary` as `default`. The four renderers
draw through `toButtonVariant`, and the group/menu dropdown items paint
red on `danger`. The designer inputs of `action:button` and
`action:icon` list `danger` in place of `destructive`, and
`sdui.manifest.json` is regenerated (two enum values).
`UIActionSchema.variant` follows the rows by reference, so its doc
comment is rewritten.
**The #26776 bridge is removed, not kept.** It existed because stored
and authored blocks said `destructive` while the runner reads `danger`.
After this PR the rows refuse `destructive` at save, a page's next save
meets the component-props gate, and every data-at-rest seam
(`applyConversionsToStoredItem` for stored `page` rows, the artifact
door inside its declared-floor window, `os migrate meta --write` for
sources) replays the conversion, retired entries included. So no stored
page reaches a renderer with `destructive`, and AGENTS.md Prime
Directive 12 leaves no room for a consumer-side alias kept "just in
case".
**Authored values converted.** No `examples/**`, `apps/**`,
`packages/platform-objects/**`, `skills/**` or `content/docs/**` source
writes `variant: 'destructive'` on an action block (measured with a `git
grep` over `variant` + `destructive` at `8074feb6`; every hit there is a
React `Button`/`Alert` primitive or a `type: 'button'` component). The
authored ones are in `ui/`: the record detail views' code-built Delete
entry for the `⋯` menu (`RecordDetailView.tsx`, `plugin-detail`
`DetailView.tsx`), the `action-bar.tsx` doc example, and the test
fixtures below.
**VS Code extension.**
`ui/packages/vscode-extension/src/providers/SchemaValidator.ts` judges
the action block variants against the block rows (`danger` accepted,
`destructive` answered with the rename; `primary` on the rows that take
it). The `button` component's list, which is the Button primitive's and
keeps `destructive`, is untouched. The package is private, so it needs
no changeset.
**Changeset** `.changeset/26779-action-block-variant-danger.md`:
`@objectstack/spec`, `@object-ui/components`, `@object-ui/types`,
`@object-ui/plugin-detail` minor, with the FROM → TO prescription, both
declaration lines and the `registered` ADR-0087 marker, plus a paragraph
that supersedes the variant sentences of the pending #26665 entry.
## Tests
- New
`packages/spec/src/conversions/page-action-block-variant-destructive-to-danger.test.ts`
(35 cases): the six rows accept `danger`; refuse `destructive` with the
rename at `variant` / `actions.0.variant`; CONTROL, another miss keeps
the enum message; the stored-row seam rewrites all four nodes and a
member alone (copy-on-write for the other); six controls left as stored;
canonical page returns the same reference; idempotent; the authoring
funnel does not replay it.
- Updated fixtures to the new spelling: spec
`component-report-items-action-members-typed.pin.test.ts` (two members),
ui `action-group-menu-inputs-11168.test.tsx` (two members, same
assertions: `bg-destructive` and `text-destructive`),
`action-variant-confirm-26665.test.tsx` (the four blocks confirm through
the destructive button on `danger`; new `toButtonVariant` rows; CONTROL
rows that `destructive` is no longer read), and one `pins:` sentence in
`registry-inputs-spec-parity.test.ts`.
## Acceptance notes
- **Claim wording.** The claim asks for "an `aliases` prescription".
`strictObject`'s `aliases` maps unknown KEYS to known ones; a renamed
VALUE has no key to alias, so the prescription lives in the enum's error
map, the shape the other enum-value retirements use.
- **File surface beyond the claim, each forced:** `RecordDetailView.tsx`
and `plugin-detail` `DetailView.tsx` (their Delete entry would lose its
red row once the menus paint on `danger`), `@object-ui/types`
`ui-action.ts` (doc comment of the by-reference type),
`sdui.manifest.json` + `scripts/sdui-manifest.record.json` (the
registry-change regeneration; the record's `objectuiWorkspaceVersion`
moves 18.1.0 → 18.2.0 because main's record trailed the version label),
the D3 entry and the JOIN row (both red without them, as on the locale
retirement).
- **Unchanged on purpose.** The page-header tests still give the host's
`sys_delete` fixture `variant: 'destructive'`: the header judges
destructiveness by `variant === 'danger' || name === 'sys_delete'`, and
that fixture is the by-name control. `ActionPreview.tsx` previews an
`ActionSchema`, not a block.
- **Unreleased #26665 changeset.**
`.changeset/26665-action-block-destructive-confirm.md` (not yet
released) describes the bridge this PR removes ("`danger` stays refused
there"). It is left byte-identical to the merge base, because editing
another card's pending note is the DELIBERATE CORRECTION class that
keeps `check-empty-changeset` red until a person confirms it. Instead,
this PR's changeset carries a paragraph that explicitly replaces that
entry's variant sentences in the same release and keeps its
confirm-dialog behaviour (seat 1 ruling).
- **Not measurable from here:** stored deployed rows and hotcrm sources.
The conversion covers stored `page` rows and built artifacts in their
window; a hotcrm source with a `destructive` block meets the refusal on
its next `os validate`.
## Verification
Readings on the pre-merge head `22846fae` unless marked; `main` was then
merged (`17ecbb46`, 28 commits, none touching a file this PR edits) and
the spec artefacts and spec tests below were re-run on the merged head.
<details><summary>Re-measure before any edit (main 8074feb)</summary>
A `safeParse` probe over the six rows: `action:button`, `action:icon`,
`action:group`, the group member, `action:menu` and the menu member each
REFUSE `danger` ("Invalid option: expected one of
default|destructive|outline|secondary|ghost|link…") and ACCEPT
`destructive`. The premise held on all six. `BUTTON_PRIMITIVE_VARIANTS`
had five readers, all action block rows, and no other.
</details>
<details><summary>Spec</summary>
- `pnpm --filter @objectstack/spec build`, then `check:generated`: 13/14
up to date, `check:docs` stale; `--fix` rewrote
`content/docs/references/ui/component.mdx`, then 14/14 up to date. On
the merged head `17ecbb46`: build + `check:generated`, all 14 up to
date, nothing to commit.
- `pnpm --filter @objectstack/spec typecheck`: exit 0. `pnpm --filter
@objectstack/spec test`: 739 files, 22139 tests passed (1 todo). Lock
VERDICT command-exit 0.
- Merged head: `vitest run src/conversions src/migrations
src/shared/retired-key-removal-version.pin.test.ts
src/ui/component-report-items-action-members-typed.pin.test.ts`: 29
files, 1054 tests passed.
</details>
<details><summary>ui</summary>
- Closure build `--filter '@object-ui/app-shell^...' --filter
'@object-ui/plugin-detail^...' --filter @objectstack/formula --filter
@objectstack/client`: VERDICT command-exit 0.
- `vitest run packages/components/src/renderers/action` + the three
`page-header-*` tests +
`apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`: 40
files, 921 tests passed.
- `type-check` on `@object-ui/types`, `@object-ui/components`,
`@object-ui/plugin-detail`, `@object-ui/app-shell`: all four Done,
VERDICT command-exit 0.
- Reverse check of the by-reference type, against the rebuilt spec
`.d.ts`: a scratch file assigning `'destructive'` and `'danger'` to
`UIActionSchema['variant']` reds only the first (`TS2322: Type
'"destructive"' is not assignable to type '"default" | "link" |
"primary" | "secondary" | "danger" | "ghost" | "outline" | undefined'`);
the file was then deleted.
- `node scripts/gen-sdui-manifest-node.mjs`: 106 components; the diff is
the two `destructive` → `danger` input enum values.
</details>
<details><summary>Derived gates and dogfood</summary>
- `dispatch-gates.mjs --commands`: 116 families. Run serially: 114 exit
0, `spec check:skill-examples` and `check:dual-build-cjs-loads` exit 3
(prerequisite not built). After the dogfood closure build both exit 0
(267 prose examples type-check; dual-build floors met). `--ran`
reconciliation: 116 derived, 116 accounted for.
- Dogfood (`--filter '@objectstack/dogfood^...'` build, then `pnpm
--filter @objectstack/dogfood test`, one lock): VERDICT command-exit 1.
Test Files 1 failed, 339 passed, 3 skipped (343); Tests 2 failed, 2752
passed, 20 skipped (2774). Both failures are in
`test/packaged-action-disabled-dispatch-live.dogfood.test.ts`
(acceptance 0 and acceptance 5/2). Run alone on the same head it passes
8/8, the load flake already recorded for that file; this diff touches no
action-disable path.
- `node scripts/check-nul-bytes.mjs`: exit 0. No exported symbol is
renamed or removed (`BUTTON_PRIMITIVE_VARIANTS` was module-private), so
`check-platform-checklist` was not owed.
</details>
## Human Minimum
Each item is one rule from
[CONTRIBUTING.md › Human
Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum),
which links the AGENTS.md text that states it.
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [ ] 3. No formatter ran; `pnpm lint` passes. (No formatter ran. `pnpm
lint` is CI's run.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzeYWDZN6LkXji5ziLF
---------
Co-authored-by: Claude <noreply@anthropic.com>danger, the word ActionSchema.variant uses (#26966)1 parent 8d79bbd commit fbe344b
24 files changed
Lines changed: 734 additions & 93 deletions
File tree
- .changeset
- content/docs/references/ui
- packages/spec/src
- conversions
- migrations/entries/semantic
- ui
- scripts
- ui
- apps/console/src/__tests__
- content/docs/guide
- packages
- app-shell/src/views
- components/src/renderers/action
- __tests__
- plugin-detail/src
- types/src
- vscode-extension/src/providers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| |||
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
130 | | - | |
| 130 | + | |
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| |||
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
171 | | - | |
| 171 | + | |
172 | 172 | | |
173 | 173 | | |
174 | 174 | | |
| |||
180 | 180 | | |
181 | 181 | | |
182 | 182 | | |
183 | | - | |
| 183 | + | |
184 | 184 | | |
185 | 185 | | |
186 | 186 | | |
| |||
0 commit comments