Skip to content

Commit 9d69f74

Browse files
committed
fix(spec): register the page action rows' confirmText-beside-params refusal as a step-18 D3 entry
One SemanticMigration naming all four page action rows (action:button, action:icon, each action:group / action:menu member); the changeset's ADR-0087 marker becomes registered. The shared predicate module's docblock now counts the five doors that read it. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P
1 parent 0446795 commit 9d69f74

3 files changed

Lines changed: 49 additions & 10 deletions

File tree

‎.changeset/30017-action-icon-members-confirm-beside-params-refused.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Clause-②: no (narrowing) — `ActionIconPropsSchema` and the `action:group` /
1010

1111
Metadata-protocol: narrowed (a page `action:icon`, and an `action:group` or `action:menu` member, may no longer declare `confirmText` beside a non-empty array `params`)
1212

13-
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed or renamed: the icon row and the container member already declared both keys and keep them, and this adds the pair refusal the other three action doors carry. There is no rewrite that keeps what the author meant without reading it (the confirm sentence may or may not suit the param dialog's description line), so the author decides, and the refusal names the remedy. The in-repo census at the base found no shipped action:icon or container member carrying the pair; the one carrier was this package's own test fixture, repaired here. -->
13+
<!-- adr-0087: registered ui-page-action-row-confirm-text-beside-params-refused -->
1414

1515
**BREAKING** accept-set narrowing at `ActionIconPropsSchema` and the `action:group` / `action:menu` member rows (`@objectstack/spec`), served where a page's component `properties` are judged: the component-props gate (`component-props-invalid`, `@objectstack/lint`) at every CLI command (`os validate`, `os compile`, the publish gate) and at the runtime `page` save door. `defineStack` does not parse a component's `properties` bag, so it does not refuse the pair.
1616

@@ -19,4 +19,4 @@ Metadata-protocol: narrowed (a page `action:icon`, and an `action:group` or `act
1919
- **Unchanged.** `confirmText` with no `params`, beside `params: []`, or beside an object `params` (an icon's static parameter values; a `type: 'api'` member's request payload — neither opens a dialog) parses as before; so does an array `params` with `description`.
2020
- **Who sees it.** An author whose page declares an `action:icon`, or an `action:group` / `action:menu` member, with both keys. A stored page keeps loading and serving; its next save is refused, with the remedy in the finding.
2121

22-
**The remedy.** Move the confirm sentence to the action's `description`, which the param dialog draws under its title, and drop `confirmText`. On an `action:icon` with no `label`, `description` is also the tooltip text.
22+
**The one-line fix: move the confirm sentence to the action's `description`, which the param dialog draws under its title, and drop `confirmText`.** `os migrate meta --from 17` lists the D3 entry `ui-page-action-row-confirm-text-beside-params-refused`, which names all four page action rows (`action:button`, `action:icon`, and each `action:group` / `action:menu` member) — the `action:button` refusal shipped earlier in 18.x is covered by the same entry. The rewrite is by hand, because whether the sentence reads as the dialog's description line is the author's call. On an `action:icon` with no `label`, `description` is also the tooltip text; give the icon a `label` to keep them apart.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { SemanticMigration } from '../../types.js';
4+
5+
export const entry: SemanticMigration = {
6+
id: 'ui-page-action-row-confirm-text-beside-params-refused',
7+
surface: 'the `confirmText` of a page action row beside a non-empty array `params`: `properties.confirmText` '
8+
+ 'beside a non-empty array `properties.params` on an `action:button` or `action:icon`, and `confirmText` '
9+
+ 'beside a non-empty array `params` on each `action:group` / `action:menu` member of `properties.actions`. '
10+
+ 'Not `params: []`, not an object `params` (an `action:button` / `action:icon` row\'s static parameter '
11+
+ 'values, a `type: \'api\'` member\'s request payload), and not a row with no `params`',
12+
replacement: 'the confirm question on the row\'s `description`, which the param dialog draws under its title, '
13+
+ 'and no `confirmText`: one dialog that asks the question and collects the inputs. A row that collects '
14+
+ 'nothing keeps its `confirmText` and drops the `params`. ⛔ NOT mechanically convertible: whether the '
15+
+ 'confirm sentence reads as the param dialog\'s description line, or should be reworded, is the '
16+
+ 'author\'s call.',
17+
reason:
18+
'The console\'s action runner awaits the confirm dialog and only then opens the param dialog for an array '
19+
+ '`params`, and all four rows forward both keys to it (an array `params` as `actionParams`), so a row '
20+
+ 'carrying both showed the user TWO dialogs for one decision: a confirm that already read as "the action '
21+
+ 'ran" while nothing had been sent, then the param prompt. `ActionSchema` and `InlineActionSchema` already '
22+
+ 'refuse the pair; the four page rows now carry that rule with the same predicate and the same refusal '
23+
+ 'text. On an `action:icon` with no `label`, `description` is also the tooltip text, so moving the '
24+
+ 'question there changes the hover text too; give the icon a `label` to keep them apart. A page\'s '
25+
+ '`properties` is not parsed on the metadata load path, so a stored page keeps loading; `os validate`\'s '
26+
+ 'props gate, which the metadata save door also runs on the next save of the page, parses it against '
27+
+ 'these same rows and reports the refusal at the row\'s `confirmText`. Measured producers: none shipped '
28+
+ 'in this repository; the one carrier was a spec test fixture, respelled.',
29+
acceptanceCriteria:
30+
'No `action:button` / `action:icon` `properties` and no `action:group` / `action:menu` member declares '
31+
+ '`confirmText` beside a non-empty array `params`; `os validate` reports no `component-props-invalid` '
32+
+ 'finding at `properties.confirmText` or `properties.actions.N.confirmText` for the pair, and every row '
33+
+ 'with no `params`, `params: []` or an object `params` parses as before.',
34+
};

‎packages/spec/src/ui/action-confirm-beside-params.ts‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,28 @@
22

33
/**
44
* `confirmText` beside a non-empty `params`: TWO sequential dialogs for ONE
5-
* decision. ONE predicate and ONE refusal, read by the three doors that refuse
5+
* decision. ONE predicate and ONE refusal, read by the five doors that refuse
66
* the pair: `ActionSchema` and `InlineActionSchema` (`action.zod.ts`), and the
7-
* page `action:button` row, `ActionButtonPropsSchema` (`component.zod.ts`).
8-
* Pinned in `action-confirm-params-guard.test.ts`, which asserts the three
7+
* page action rows in `component.zod.ts` — `action:button`
8+
* (`ActionButtonPropsSchema`), `action:icon` (`ActionIconPropsSchema`) and each
9+
* `action:group` / `action:menu` member. Pinned in
10+
* `action-confirm-params-guard.test.ts`, which asserts the five doors'
911
* messages equal.
1012
*
1113
* The console action runner awaits the confirm, THEN the param prompt (objectui
1214
* `packages/core/src/actions/ActionRunner.ts`), so the first dialog already
1315
* reads as "the action ran" while nothing has been sent. The `element:button`
1416
* renderer hands an inline action to that same runner, and the `action:button`
15-
* block forwards its row's `confirmText` and an array `params` (as
16-
* `actionParams`) to it too, so the pair opens the same two dialogs on all
17-
* three doors.
17+
* and `action:icon` blocks forward their row's `confirmText` and an array
18+
* `params` (as `actionParams`) to it too, as the `action:group` /
19+
* `action:menu` containers do for each member, so the pair opens the same two
20+
* dialogs on all five doors.
1821
*
1922
* An empty `params` collects nothing and opens no second dialog, so it is not
20-
* the pair. Nor is an object-form `params`: on an `action:button` row an object
21-
* is the static parameter values, which open no dialog, and the
23+
* the pair. Nor is an object-form `params`: on an `action:button` /
24+
* `action:icon` row an object is the static parameter values, and on a
25+
* `type: 'api'` container member the request payload; neither opens a
26+
* dialog, and the
2227
* `Array.isArray` read below leaves it alone.
2328
*
2429
* Lives in its own module, not in `action.zod.ts`, so `component.zod.ts` can

0 commit comments

Comments
 (0)