Repository navigation
Commit 2c881fb
fix(spec)!: an inline action refuses
domain:spec#3 · os-support-ai · session_01WGufzfHVbsfKWji5VLrLbG
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBN6TwTfduBnZH2gwPifJvvc)_
Fixes #29383
Ruling-ref: 6595808462
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no `skills/**` file teaches an inline action
carrying `confirmText` beside `params`. The one rule line about the pair
(`skills/objectstack-ui/rules/actions.md` L22, "A registered action is
refused it beside a non-empty `params` (put the question on
`description`)") stays true and names the remedy this refusal
prescribes; the generated `content/docs/references/**` tables that
render the `confirmText` describe are regenerated here.
**BREAKING** accept-set narrowing at `InlineActionSchema`
(`@objectstack/spec`), served where a page's `element:button`
`properties.action` is judged: the component-props gate
(`component-props-invalid`, `@objectstack/lint`) at every CLI command
and at the runtime `page` save door.
## 维护者速读
- 改了什么:页面按钮上内联声明的动作(`element:button` 的 `properties.action`)同时写
`confirmText` 和非空 `params` 时,现在和已注册动作一样被拒收,提示把这句话挪到 `description`、删掉
`confirmText`。
- 为什么改:您在 #29383 裁定
A(「同意」)。内联动作和注册动作走同一个执行器,先弹确认框、再弹参数框,一个决定两个对话框;`description`
已能写在内联动作上,作者有地方挪这句话。
- 风险与回滚:仓内普查 0 处已发运的内联动作带这对组合,只翻转 spec 自己的一条测试 pin;不带 `params` 或
`params: []` 的 `confirmText` 照常通过,批量动作不受影响。已存页面照常加载,下次保存才被拒。回滚即 revert 本
PR。
- 先看:`packages/spec/src/ui/action.zod.ts`(新的共用判定
`declaresConfirmBesideParams` 与
`CONFIRM_BESIDE_PARAMS_REFUSAL`,`InlineActionSchema` 链上新增的那条
`.refine`),`packages/spec/src/ui/action-confirm-params-guard.test.ts`
末尾新的 describe
组,`.changeset/29383-inline-action-confirm-beside-params-refused.md`。
- 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run
src/ui/action-confirm-params-guard.test.ts`,预期 16 个测试全过,其中内联组 5 条(2
条拒收、3 条对照)。
- 需要你做的:无,席位落地。
## What changes
- **One predicate, one refusal, two doors.**
`packages/spec/src/ui/action.zod.ts` lifts the `ActionSchema` pair rule
out of its refine body into a module-level predicate,
`declaresConfirmBesideParams(data)` (`confirmText != null` and `params`
a non-empty array, unchanged), and a module-level refusal,
`CONFIRM_BESIDE_PARAMS_REFUSAL` (the existing message, byte for byte, at
path `['confirmText']`). `ActionSchema`'s refinement now reads `return
!declaresConfirmBesideParams(data)` with that refusal;
`InlineActionSchema` gains one `.refine` with the same two, between its
target-required rule and its url-target rule. No second copy of either.
- **The comment at the refinement** no longer says the inline pair is "a
separate narrowing question, not decided here". It states that
`InlineActionSchema` carries the same refusal on its own chain, by
ruling record 6595808462 letter A, and why (same runner, so the same two
dialogs; its pick carries `description`, so the remedy has a slot).
- **The `confirmText` describe** dropped its "On a registered action,"
qualifier ("pairing this with a non-empty `params` is refused …"), since
both doors that render this describe now refuse the pair; a view's
`bulkActionDefs` declares its own `confirmText`. The NB comment above
the field says so.
`content/docs/references/{data/object,kernel/metadata-plugin,ui/action,ui/component}.mdx`
are regenerated from it (`check:generated --fix`, which proved only
`check:docs` stale).
- **Changeset**
`.changeset/29383-inline-action-confirm-beside-params-refused.md`:
`@objectstack/spec` minor, both declaration lines, the ADR-0087
disposition marker, the narrowing banner, before / now / unchanged /
who, and the remedy.
### Before / after at the inline door, on the census case
The census case is the one inline carrier the census found, the pin in
`action-confirm-params-guard.test.ts`:
```ts
InlineActionSchema.safeParse({
type: 'url', target: '/approvals?reject=1', label: 'Reject',
confirmText: 'Reject this request?',
params: [{ name: 'reason', label: 'Reason', type: 'textarea' }],
});
```
- **Before** (`origin/main` `b3d14150`): `success: true`, pinned by the
test "InlineActionSchema is out of reach too — it picks fields, not this
refine chain".
- **After** (this head): `success: false`, one issue at
`['confirmText']`, `code: 'custom'`, and its message equals
`ActionSchema`'s own issue for the registered pair (asserted with
`toBe`). With `description` in place of `confirmText`, with no `params`,
and with `params: []`, the same action parses.
- **At the authoring door.** The page component-props gate
(`validateComponentProps`, `@objectstack/lint` src against this head's
spec `dist`), on a page whose `element:button` carries `{ type: 'api',
target: '/api/v1/approvals/reject', params: [reason], confirmText:
'Reject this request?' }`: one finding, `component-props-invalid`,
severity `error`, at
`pages[0].regions[0].components[0].properties.action.confirmText`,
message opening "action.confirmText: An action that declares
`confirmText` beside a non-empty `params` shows the user TWO dialogs for
one decision". The remedy body (`description`, no `confirmText`), the
param-less body and the `params: []` body: no finding.
- **`defineStack` does not judge it**, before or after: a component's
`properties` is an open bag that the stack parse does not dispatch
(measured: an inline `cancelText`, an inline url action with no
`target`, and the pair all pass `defineStack` on this head). The
changeset names the props gate as the door, not `defineStack`.
### ADR-0087 disposition
The changeset body carries the ADR-0087 marker comment with the
disposition `adr-0087: not-required (no-migration-prescription)`, the
form the repo's narrowing changesets use (223 of the `.changeset/*.md`
markers on `origin/main` are `not-required (no-migration-prescription)`;
the closest precedents are `26413-now-anchor-extra-key-refused.md` and
`26462-diff-missing-version-refused.md`). No `docs/adr/**` path is
touched. The reasoning: no key is added, removed or renamed; the census
found 0 shipped carriers; and the one rewrite there could be (move
`confirmText` into `description`) needs the author to read the sentence,
so the refusal names it and nothing converts it.
### Census, re-run at the base
At `origin/main` `b3d14150` (report 6580676225 counted at `a6441636`):
every file under `examples/`, `packages/**`, `ui/**`, `skills/**`,
`templates`, `apps/` carrying `confirmText` (excluding `CHANGELOG.md`),
each `confirmText` line read with a ±25-line window that holds an inline
marker (`element:button`, `InlineAction`, `properties:`, `action: {`)
and a non-empty `params: [`. 16 windows in 5 files, each read by hand:
- `packages/spec/src/ui/action-confirm-params-guard.test.ts` — the
inline pin above (the one carrier; flipped here) and `ActionSchema` /
`BulkActionDefSchema` fixtures.
- `packages/lint/src/validate-action-copy-templates.test.ts` L111–137 —
registered actions and `action:button` rows, not inline actions.
- `packages/spec/src/ui/action-cancel-text.test.ts` L29,
`action-copy-template.test.ts` L158, `action-row-update.test.ts`
L291–295 — registered actions, a roots helper, and a registered action.
The files changed between `a6441636` and `b3d14150` that carry both keys
are registered-action metadata (`platform-objects` identity objects,
`plugin-approvals`), generated translation bundles, and ui/spec tests
none of which parse an inline action with the pair. Cloud fixtures are
not in this repository and were not read. **Disposition unchanged: 0
shipped carriers, 1 spec test pin.**
## Tests
- `action-confirm-params-guard.test.ts`: the "out of reach" inline pin
is removed from the boundary block (renamed "the pair guard is scoped by
SCHEMA BOUNDARY — the bulk def keeps the pair") and replaced by a new
block, "`confirmText` + non-empty `params` is refused on
InlineActionSchema too": the refusal (path, `code`, message equal to
`ActionSchema`'s), the locale-map form refused, and three accept
controls: `description` with no `confirmText`, `confirmText` with no
`params`, `confirmText` with `params: []`. The file header names the
inline door.
<details><summary>Readings (head `65665759`, under
os-verify-lock)</summary>
- Targeted: `vitest run src/ui/action-confirm-params-guard.test.ts
src/ui/inline-action.test.ts src/ui/action-cancel-text.test.ts
src/ui/action-row-update.test.ts src/ui/component.test.ts`: `Test Files
5 passed (5)`, `Tests 459 passed (459)`.
- `pnpm --filter @objectstack/spec test && pnpm --filter
@objectstack/spec typecheck`: `Test Files 804 passed (804)`, `Tests
23884 passed | 1 todo (23885)`; VERDICT command-exit 0.
- Ablation, through `scripts/ablation-replace.mjs` (anchor hit 1 → 0,
blob `c1a9cf11` → `2b9084b3`): the inline refine's predicate replaced by
`true || …`; `action-confirm-params-guard.test.ts` → `Tests 2 failed |
14 passed (16)` (the two refusal tests); restored, blob equals HEAD,
`git diff HEAD` empty. No `dist` in that path (the test imports
`./action.zod`).
- Lint consumers: `pnpm --filter @objectstack/lint exec vitest run
--maxWorkers=2 src/validate-action-copy-templates.test.ts
src/validate-component-props.test.ts
src/validate-component-props-url-target.test.ts
src/validate-component-props-row-on-success.test.ts` (after the dogfood
closure build): `Test Files 4 passed (4)`, `Tests 207 passed (207)`;
VERDICT command-exit 0. The copy-template rule
(`validate-action-copy-templates`, its `INLINE_KEYS`) needs no change:
it lists copy keys, and this refusal adds none.
- `node ui/scripts/check-action-forward-parity.mjs`: exit 0
(`element:button owes 13, forwards 18`; no spec key moved, so nothing
new is owed).
</details>
**Full dogfood battery** (narrowing arm), on head `65665759`, one locked
command: `pnpm --workspace-concurrency=2 --filter
'@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood
test` (no `OS_TEST_SHARD`, no path): `Test Files 404 passed | 3 skipped
(407)`, `Tests 3326 passed | 28 skipped (3354)`, 0 failures; VERDICT
command-exit 0 (44m19s held).
## Acceptance notes
- `content/docs/protocol/objectui/actions.mdx` L444 (hand-written) still
says "On a registered action, pairing it with a non-empty `params` is
*refused*". It stays true but no longer names the inline door; outside
this claim's file surface, so left for the docs owner.
- `skills/objectstack-ui/rules/actions.md` L22 has the same "a
registered action" phrasing. True, and it names the remedy; a one-word
widening ("A registered or inline action") would be a `skills/**` (Tier
H, token-ratcheted) edit, kept out of this Tier S PR.
- The lint copy-template rule's `INLINE_KEYS`
(`validate-action-copy-templates.ts`) lists copy keys, not pair rules,
so it does not move.
## Verification
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 102 families on this tree (7 paths
vs merge base `b3d14150`).
<details><summary>102 derived gates: 102 exit 0</summary>
- Serial pass on `65665759`: 100 exit 0, 2 exit 3 (built-output
prerequisites: spec `check:skill-examples` needs the client SDK `dist`,
`check:dual-build-cjs-loads` needs every package's `dist`).
`check-plugin-teardown-shape --self-test` needed its pinned fixture
commit on the shallow clone (`git fetch origin 621a487`); fetched
before the pass, exit 0.
- After the full `pnpm turbo run build --concurrency=2` (`Tasks: 118
successful, 118 total`, VERDICT command-exit 0), the 2 were rerun: both
exit 0 (`283 prose examples type-check across 3 surface(s)`; the CJS
load probe at floors 90/58/520/1).
- `check:generated`, `check:authorable-surface`, `check:api-surface`,
`check:docs`, `check:liveness`: exit 0. No authorable-surface,
api-surface, liveness or dropped-refinements row moves: the refusal adds
no key and no export, and `InlineActionSchema` already carried a dropped
refinement at every position it is reached from.
- `dispatch-gates --ran`: "102 derived famil(ies) accounted for — 102
run, 0 NOT-MEASURED".
</details>
`origin/main` gained 19 commits after the base (`b0ef25f5` at open);
none touches `action.zod.ts` or the guard test, and two regenerate other
lines of `content/docs/references/data/object.mdx`. A local `merge-tree`
of head and `origin/main` is clean; not merged here, so the dogfood
reading stays pinned to this head.
`check-changeset-no-major` with this body as the event: exit 0 ("LEVEL
AXIS: this PR declares clause-② `no (narrowing)` and metadata-protocol
`narrowed`, and no package whose published source … it moves is graded
`patch`"). `check-closing-keyword-parity`: the only closing target is
`Fixes #29383`.
CI families are read from this PR's check-runs.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzfHVbsfKWji5VLrLbG)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzfHVbsfKWji5VLrLbG
---------
Co-authored-by: Claude <noreply@anthropic.com>confirmText beside a non-empty params, as a registered action does (#29829)1 parent c5ddbea commit 2c881fb
7 files changed
Lines changed: 146 additions & 42 deletions
File tree
- .changeset
- content/docs/references
- data
- kernel
- ui
- packages/spec/src/ui
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
495 | 495 | | |
496 | 496 | | |
497 | 497 | | |
498 | | - | |
| 498 | + | |
499 | 499 | | |
500 | 500 | | |
501 | 501 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
339 | 339 | | |
340 | 340 | | |
341 | 341 | | |
342 | | - | |
| 342 | + | |
343 | 343 | | |
344 | 344 | | |
345 | 345 | | |
| |||
0 commit comments