Skip to content

fix(spec)!: action:icon and the action:group / action:menu members refuse confirmText beside a non-empty array params, as action:button does - #30624

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-30017-confirm-beside-params-icon-members
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-30017-confirm-beside-params-icon-members

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
Fixes #30017
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no skills/** file teaches an action:icon, or an action:group / action:menu member, carrying confirmText beside an array params (git grep of skills/ for action:icon|action:group|action:menu: 0 hits). 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/ui/component.mdx rows that render the three confirmText describes are regenerated here.

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 and at the runtime page save door.

维护者速读

  • 改了什么:页面上的 action:icon,以及 action:group / action:menu 里的每个成员,同时写 confirmText 和非空数组 params 时,现在和 action:button、已注册动作、内联动作一样被拒收,提示把这句话挪到 description、删掉 confirmText。
  • 为什么改:您在「Discuss pending spec rulings」线程对本卡裁定 A(「以上同意」)。这两类块把两键都交给同一个执行器,先弹确认框、再弹参数框,一个决定两个对话框;五道门现在用同一条判据、同一句提示。
  • 风险与回滚:仓内普查 0 处已发运的载体,唯一命中是 spec 自己的一个测试夹具,本 PR 已改;不带 params、params: []、对象形 params(图标的静态值;type: 'api' 成员的请求体,都不弹参数框)照常通过。已存页面照常加载,下次保存才被拒。回滚即 revert 本 PR。
  • 先看:packages/spec/src/ui/component.zod.ts(ActionIconPropsSchema 链尾与 buildActionGroupMember / buildActionMenuMember 链尾新增的 .refine),packages/spec/src/ui/action-confirm-params-guard.test.ts 末尾新的 describe 组,packages/spec/src/ui/component-report-items-action-members-typed.pin.test.ts 改过的夹具。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/ui/action-confirm-params-guard.test.ts,预期 29 个测试全过,其中新组 8 条(5 条拒收、3 条对照)。
  • 需要你做的:无,席位落地。

What changes

  • One predicate, one refusal, five doors. ActionIconPropsSchema gains .refine((data) => !declaresConfirmBesideParams(data), CONFIRM_BESIDE_PARAMS_REFUSAL) at the end of its chain, exactly as ActionButtonPropsSchema carries it. The action:group and action:menu members each gain the same refine at the end of their builder's chain (buildActionGroupMember, buildActionMenuMember, the two consumers of actionContainerMemberShape()), so the issue lands on the member, at actions.N.confirmText. No second predicate or message: both are imported from packages/spec/src/ui/action-confirm-beside-params.ts, whose predicate and message are unchanged (the shared message needed no member-path variant: its remedy key, description, is declared on the icon and on both members); only its docblock now counts the five doors that read it.
  • The shared predicate already fits the ruling. Its Array.isArray(params) && params.length > 0 read refuses a non-empty array and leaves params: [] and an object params alone: on action:icon an object is the static parameter values, on a member it is a type: 'api' member's request payload (a non-api member's object params stays refused by actionContainerMemberParamsFitType, unchanged).
  • The confirmText describes of action:icon and of the container member shape now append the sentence action:button's carries: refused beside a non-empty array params, correct with no params, beside params: [] and beside an object params (the member's wording names the type: 'api' payload). content/docs/references/ui/component.mdx is regenerated from them (check:generated --fix, which proved only check:docs stale).
  • Census carrier repaired. component-report-items-action-members-typed.pin.test.ts §1 "a member with every forwarded scalar" (action:menu) carried confirmText beside an array params; its params moves to a new byte-identical case, "a member collecting params", which carries the question on description.
  • Changeset .changeset/30017-action-icon-members-confirm-beside-params-refused.md: @objectstack/spec minor, both declaration lines, the ADR-0087 disposition marker (registered), the narrowing banner, before / now / unchanged / who, and the one-line fix naming the D3 entry.
  • Step-18 D3 entry packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-confirm-text-beside-params-refused.ts (added in the patch round, head 9d69f747cd): one SemanticMigration naming all four page action rows (properties.confirmText beside a non-empty array properties.params on an action:button / action:icon, and confirmText beside a non-empty array params on each action:group / action:menu member), replacement on description, NOT mechanically convertible, acceptance as os validate reporting no component-props-invalid at those paths. It also covers the action:button refusal PR fix(spec)!: an action:button refuses confirmText beside a non-empty array params, as the two action doors do #29869 shipped without an entry.

ADR-0087 disposition

The changeset body carries the ADR-0087 marker comment with the disposition adr-0087: registered ui-page-action-row-confirm-text-beside-params-refused (patch round, after contract review 6666209629: the refusal prescribes a hand rewrite, so ADR-0087 D3 owes an entry rather than an exemption). node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 and reads it as registered, the id new in this diff. No docs/adr/** path is touched. The first head carried not-required (no-migration-prescription); that marker is gone.

Verification

All readings at head 0446795a12 (the branch merged with origin/main 5b748dfede) unless a line says otherwise; the patch-round readings at head 9d69f747cd are in the os-dev-report patch-round comment on the card; the implementation commits are 0c6f39767c, 73fb6b963e (regenerated reference), 34eb15a15d (envelope pin).

Census at the base (origin/main 2ffdb649)

  • AST pass over every tracked *.ts / *.tsx / *.js / *.mjs / *.cjs / *.mts (no dist/, no .d.ts): 213 files contain confirmText; every object literal carrying both confirmText and a params that is not an empty array or an object literal was listed with the nearest enclosing type / actionType and the nearest enclosing array or call naming an action:icon / action:group / action:menu literal (the tuple-shaped fixtures). 48 such literals; 4 inside an action-block context, each read by hand:
    • packages/spec/src/ui/component-report-items-action-members-typed.pin.test.ts L217: an action:menu member (type: 'api') with confirmText beside params: [{ name: 'reason', … }]. The one carrier; a test fixture, repaired here (above).
    • packages/lint/src/validate-action-copy-templates.test.ts L131: an action:button fixture (the action:group literal is a later array element); it feeds the copy-template rule, which does not parse these rows; the lint suite runs it green.
    • packages/spec/src/ui/action.zod.ts L1156 and component.zod.ts L4138: schema declarations, not carriers.
  • Non-code pass over tracked *.json / *.yaml / *.yml / *.md / *.mdx naming both confirmText and one of the three blocks (CHANGELOG.md, generated references, json-schema/, api-surface, sdui.manifest.json excluded): content/docs/frontend/layout.mdx, content/docs/protocol/objectui/actions.mdx, content/docs/ui/components/action.mdx, packages/spec/liveness/action.json; each read by hand — prose and key tables, no example carrying the pair.
  • Cloud fixtures are not in this repository and were not read. Disposition: 0 shipped carriers, 1 test fixture (repaired).

Before / after at the authoring door

validateComponentProps (@objectstack/lint src against this branch's spec dist), one page per case:

case findings
action:icon with confirmText + array params 1 · component-props-invalid · error · …components[0].properties.confirmText · "confirmText: An action that declares confirmText beside a non-empty params shows the u…"
action:group member, same pair 1 · component-props-invalid · error · …components[0].properties.actions.0.confirmText
action:menu member, same pair 1 · component-props-invalid · error · …components[0].properties.actions.0.confirmText
menu member, array params + description (the remedy) 0
menu member, confirmText + params: [] 0
type: 'api' menu member, confirmText + object params 0
action:icon, confirmText, no params 0

Before: the ablation below is the base's behaviour for these three rows (the refine is the only change on them), and it parses the pair clean.

Pins (packages/spec/src/ui/action-confirm-params-guard.test.ts, new top-level describe)

  • Refusals (5): action:icon — one issue, code: 'custom', path: ['confirmText'], message toBe action:button's issue for the same pair, and contains TWO dialogs, `description` and param-LESS; action:group and action:menu members — one issue, code: 'custom', path: ['actions', 1, 'confirmText'] (the second member), message toBe action:button's; and each member on the template-envelope confirmText form, [['custom', 'actions.1.confirmText']].
  • Preservation (3): action:icon and each container member accept confirmText with no params, beside params: [], beside an object params (type: 'api' on the members), and the remedy (array params + description), each a full safeParse success.

Ablation (committed state, node scripts/ablation-replace.mjs, test source read from src/)

three legs, each red then restored
leg (refine removed) on-disk proof result restore
ActionIconPropsSchema anchor 1 → 0, blob 3eec6db7ac20 → ff6c2420ddbb 1 failed / 28 passed: the action:icon refusal blob == HEAD, git diff HEAD empty
buildActionGroupMember anchor 1 → 0, blob → e77714c1e61d 2 failed / 27 passed: the group refusal and its envelope pin blob == HEAD, git diff HEAD empty
buildActionMenuMember anchor 1 → 0, blob → aeb719fd841f 2 failed / 27 passed: the menu refusal and its envelope pin blob == HEAD, git diff HEAD empty

After the three legs, from the committed state: 29 / 29 passed. A first member run whose replacement text was a substring of its anchor was refused by the tool before running (replacement count 1 → 1), restored, and rerun with a distinct replacement; it is not counted above.

Tests

command result
pnpm --filter @objectstack/spec test (merged head) 816 files passed · 25669 tests passed, 1 todo
pnpm --filter @objectstack/spec typecheck (merged head) exit 0
pnpm --filter @objectstack/lint exec vitest run (head 34eb15a15d, lint closure built) 195 files · 8087 passed, 5 skipped
pnpm --filter @object-ui/types test (head 34eb15a15d) 387 files · 10464 passed
@object-ui/components spec-symbol-action-renderer-props-11073.test.ts + src/renderers/action/ (head 34eb15a15d) 38 files · 616 passed
pnpm --filter @objectstack/dogfood test (head 34eb15a15d, closure built) 420 files passed, 3 skipped · 3496 passed, 36 skipped
npx eslint --no-inline-config on the three changed .ts files exit 0

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives the same 102 families at 34eb15a15d and at 0446795a12. Exit codes are captured before any pipe.

gate table, head 0446795 (102 families, exit codes)
family exit
node scripts/check-adr-0087-registration.mjs --base origin/main 0
node scripts/check-adr-0087-registration.mjs --self-test 0
node scripts/check-adr-links.mjs 0
node scripts/check-adr-links.mjs --self-test 0
node scripts/check-changeset-no-major.mjs --base origin/main 0
node scripts/check-closing-keyword-parity.mjs 0
node scripts/check-closing-keyword-parity.mjs --self-test 0
node scripts/check-comment-mask-adoption.mjs 0
node scripts/check-comment-mask-adoption.mjs --self-test 0
node scripts/check-comment-mask-corpus.mjs 0
node scripts/check-dev-prereqs.mjs --self-test 0
node scripts/check-doc-route-spelling.mjs --advisory 0
node scripts/check-doc-route-spelling.mjs --self-test 0
node scripts/check-docs-section-name.mjs 0
node scripts/check-docs-section-name.mjs --self-test 0
node scripts/check-dts-emitted.mjs --self-test 0
node scripts/check-empty-changeset.mjs --base origin/main 0
node scripts/check-empty-changeset.mjs --self-test 0
node scripts/check-issue-citations.mjs 0
node scripts/check-plugin-teardown-shape.mjs 0
node scripts/check-plugin-teardown-shape.mjs --self-test 0
node scripts/check-section-landing-index.mjs 0
node scripts/check-section-landing-index.mjs --self-test 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test 0
node scripts/ci/plan-test-core-shards.mjs --self-test 0
node scripts/ci/prune-turbo-cache.mjs --self-test 0
node scripts/docs-audit/check-affected-docs.mjs 0
node scripts/docs-audit/check-drift-comment.mjs 0
node scripts/pm/release-rehearsal-clone.mjs --self-test 0
node scripts/release-pending-publish.mjs --self-test 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0
pnpm --filter @objectstack/lint run check:doc-security-posture 0
pnpm --filter @objectstack/spec run check:api-surface 0
pnpm --filter @objectstack/spec run check:authorable-surface 0
pnpm --filter @objectstack/spec run check:browser-reachable-entries 0
pnpm --filter @objectstack/spec run check:docs 0
pnpm --filter @objectstack/spec run check:dual-source-exports 0
pnpm --filter @objectstack/spec run check:duration-unit-keys 0
pnpm --filter @objectstack/spec run check:empty-state 0
pnpm --filter @objectstack/spec run check:entry-nameability 0
pnpm --filter @objectstack/spec run check:export-origins 0
pnpm --filter @objectstack/spec run check:exported-any 0
pnpm --filter @objectstack/spec run check:generated 0
pnpm --filter @objectstack/spec run check:liveness 0
pnpm --filter @objectstack/spec run check:llms-txt 0
pnpm --filter @objectstack/spec run check:skill-examples 0
pnpm --filter @objectstack/spec run check:skill-refs 0
pnpm --filter @objectstack/spec run check:strictness-ledger 0
pnpm --filter @objectstack/spec run check:variant-docs 0
pnpm --filter @objectstack/spec run check:yaml-examples 0
pnpm check:changeset-gate-self-tests 0
pnpm check:ci-filter-parity 0
pnpm check:closed-ceremony-text 0
pnpm check:corpus-claim-drift 0
pnpm check:cross-package-test-inputs 0
pnpm check:dispatcher-error-vocabulary 0
pnpm check:doc-anchors 0
pnpm check:doc-authoring 0
pnpm check:doc-frontmatter 0
pnpm check:docs-audit-scope 0
pnpm check:docs-redirects 0
pnpm check:docs-single-h1 0
pnpm check:docs-spec-enumerations 0
pnpm check:docs-transcript-drift 0
pnpm check:driver-memory-census 0
pnpm check:dts-closure 0
pnpm check:dual-build-cjs-loads 0
pnpm check:engine-double-contract 0
pnpm check:gitlink-declared 0
pnpm check:issue-citations 0
pnpm check:keyed-text-bounds 0
pnpm check:lean-entry-closure 0
pnpm check:logger-receiver-detach 0
pnpm check:merge-driver 0
pnpm check:nul-bytes 0
pnpm check:objectql-double-limit 0
pnpm check:org-identifier 0
pnpm check:page-declaration-shape 0
pnpm check:platform-object-tenancy-census 0
pnpm check:pm-prior-rulings 0
pnpm check:published-files 0
pnpm check:published-readme-links 0
pnpm check:query-options-erasure 0
pnpm check:react-page-adapter-contract 0
pnpm check:refd-timer-probe 0
pnpm check:registry-log-declared 0
pnpm check:rest-log-spy-declared 0
pnpm check:role-word 0
pnpm check:skill-identifier-liveness 0
pnpm check:slot-lookup 0
pnpm check:sourcemap-no-sources-content 0
pnpm check:spec-docblock-symbol-anchors 0
pnpm check:spec-parsed-alias 0
pnpm check:system-context-census 0
pnpm check:test-source-alias 0
pnpm check:tier-file-adoption 0
pnpm check:type-check-coverage 0
pnpm check:type-check-debt 0
pnpm check:undeclared-dep-imports 0
pnpm check:vendor-version-stamps 0
pnpm check:watch-hint-literal 0
pnpm check:where-matcher 0

node scripts/pm/dispatch-gates.mjs --ran on this record: 102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 102 lines carry an exit code). Also run: pnpm check:adr-0087-registration 0, pnpm check:doc-authoring 0, pnpm check:skill-frame-freshness 0, pnpm check:dev-prereqs 1 (see Acceptance notes).

Acceptance notes

  • The predicate module's docblock now counts the five doors that read it (patch round, surface widened by amendment 6666273551); the predicate and the message are unchanged.
  • The description remedy on an action:icon. With no label, an icon's description is also its tooltip text, so moving the confirm sentence there changes the tooltip too. The changeset's remedy says so; the shared message is unchanged.
  • check:dev-prereqs (local-only family) exits 1 in this worktree: 35 ui/ workspace packages have no dist/ because no full pnpm build ran here. It judges whether this checkout can boot a dev server, not this diff.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P

…ext beside a non-empty array params

The fourth and fifth doors of the confirmText + params pair refusal, with
action:button's predicate and message (declaresConfirmBesideParams,
CONFIRM_BESIDE_PARAMS_REFUSAL). Both blocks forward confirmText and an
array params (as actionParams) to the same runner, which opens the confirm
and then the param dialog. Still accepted: no params, params: [], and an
object params (an icon's static values; an api member's payload).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P
…pe confirmText form

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P
…nfirm-beside-params-icon-members

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 7 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/ui/action-confirm-beside-params.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/ai/connect-mcp.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/automation/flows.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/deployment/validating-metadata.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/frontend/layout.mdx (via actionType (literal, a string literal in ActionIconPropsSchema), confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/frontend/quick-start.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/frontend/schema-rendering.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/protocol/kernel/i18n-standard.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/protocol/objectui/actions.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/protocol/objectui/record-alert.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/ui/actions.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/ui/create-vs-edit-form.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/ui/translations.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))

⛔ 8 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/releases/v17/17-0.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/releases/v17/17-1.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/releases/v17/17-6.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/releases/v17/17-7.mdx (via actionType (literal, a string literal in ActionIconPropsSchema))
  • content/docs/releases/v18/18-1.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/releases/v18/18-2.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))
  • content/docs/releases/v18/18-3.mdx (via confirmText (literal, a string literal in ActionIconPropsSchema; a string literal in actionContainerMemberShape))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/action-confirm-beside-params.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 170 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json aad1603cc2180b3eefa8cee41f5b895b8ee45909 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4fdac8501027eb012d47a4cc0af01eb3f2bc5e66 — the merge of head 9d69f747cd545a5e11690329ea16436a77d8a49a into base aad1603cc2180b3eefa8cee41f5b895b8ee45909, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4fdac8501027eb012d47a4cc0af01eb3f2bc5e66 && git checkout 4fdac8501027eb012d47a4cc0af01eb3f2bc5e66
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aad1603cc2180b3eefa8cee41f5b895b8ee45909 9d69f747cd545a5e11690329ea16436a77d8a49a && git checkout -B drift-repro aad1603cc2180b3eefa8cee41f5b895b8ee45909 && git merge --no-ff 9d69f747cd545a5e11690329ea16436a77d8a49a

node scripts/docs-audit/affected-docs.mjs --json aad1603cc2180b3eefa8cee41f5b895b8ee45909

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs aad1603cc2180b3eefa8cee41f5b895b8ee45909 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0446795a1283e93773b8eb760cac30fa14b1fccf
Local-runs: none
Scope: packages/spec/src/ui/component.zod.ts + packages/spec/src/ui/action-confirm-beside-params.ts, packages/spec/src/ui/action-confirm-params-guard.test.ts, packages/spec/src/ui/component-report-items-action-members-typed.pin.test.ts, .changeset/30017-action-icon-members-confirm-beside-params-refused.md, content/docs/references/ui/component.mdx, packages/lint/src/validate-component-props.ts, ui/packages/components/src/renderers/action/action-icon.tsx, ui/packages/components/src/renderers/action/action-group.tsx, ui/packages/components/src/renderers/action/action-menu.tsx, ui/packages/core/src/actions/ActionRunner.ts, packages/spec/src/migrations/types.ts, packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-beside-executor-refused.ts, packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts, packages/spec/dropped-refinements.baseline.json, packages/spec/CHANGELOG.md, scripts/check-changeset-no-major.mjs, scripts/check-adr-0087-registration.mjs, docs/adr/0087-metadata-protocol-upgrade-contract.md, skills/objectstack-ui/rules/actions.md, AGENTS.md

Inputs read: card #30017 body and its four comments (ruling 6624208841 letter A, triage 6624670416, claim 6663933383, os-dev-report 6665968613); PR #30624 body, its five-file list and the net diff of 0446795a12 against origin/main (6701c08a44, which the head already merges); the check-runs on 0446795a12, read twice, the second read at 2026-11-05T18:41Z; the cited code at the head: component.zod.ts (ActionButtonPropsSchema L4021-4083 with its .refine at L4083, ActionIconPropsSchema L4128-4189, ACTION_ROW_COPY_DESCRIBE L3508, actionContainerMemberShape L4293-4381, actionContainerMemberParamsFitType L4382-4400, buildActionGroupMember L4411-4432, buildActionMenuMember L4440-4465, ActionGroupPropsSchema.actions L4525, ActionMenuPropsSchema.actions L4584, the import at L71), action-confirm-beside-params.ts whole, the guard test whole, the pin test's repaired fixture, the changeset whole, the three regenerated component.mdx rows (ActionGroupProps.actions[number], ActionIconProps, ActionMenuProps.actions[number]), validate-component-props.ts L312-500, action-icon.tsx (the paramsPayload and confirmText forward, the tooltip branch schema.label || schema.description, the description input), action-group.tsx L350-380, action-menu.tsx L273-312, ActionRunner.ts L1659-1700, migrations/types.ts (SemanticMigration and its docblock), the two step-18 semantic entries named in Scope, dropped-refinements.baseline.json (the four ui/Action*Props sites), CHANGELOG.md L2148 and L2321, the level checker's header, the registration gate's not-required section and findMigrationPrescription, ADR-0087 D3 and its #8299 addendum, the skills rule line L22, AGENTS.md Post-Task Checklist step 3 and § Touched packages/spec; and the precedent review 6599507683 on PR #29869. The face: --paths returns hits = component.zod.ts (published schema source); the PR body and changeset both carry Clause-②: no (narrowing), so the tier is CONTRACT_REVIEW_TIER on either face.

① Derived judgments

  1. What is newly refused, exactly. Three rows gain one .refine at the end of their chain, whose predicate is the negation of declaresConfirmBesideParams(row) and whose issue is CONFIRM_BESIDE_PARAMS_REFUSAL: ActionIconPropsSchema (L4189), the action:group member (L4432) and the action:menu member (L4465). The predicate is the one action:button reads: confirmText neither null nor undefined, and params an array of positive length. So the only authored values newly refused are confirmText (bare string or the { dialect: 'template', source } envelope — both != null) beside an array params of length at least one, on an action:icon's properties and on each member of an action:group / action:menu properties.actions. Right, and it is the ruling's letter A exactly.
  2. What is still accepted, exactly. No params (undefined, not an array); params: [] (length 0); an object params (not an array) — on the icon the static values, on a type: 'api' member the request payload; and every confirmText form on those three shapes. The guard test's two acceptance groups pin all four of these per door with a full safeParse success. A blank confirmText ('') beside an array is refused, as on the other three doors; a locale-map confirmText is refused by the template slot before the predicate runs. Right.
  3. One predicate, one message. Both symbols are imported once (L71) and used four times; component.zod.ts carries no second literal of the message (0 hits for TWO dialogs outside the import's source). The new tests pin each door's message toBe action:button's own issue, which the earlier group pins toBe ActionSchema's and InlineActionSchema's. Five doors, one wording. Right.
  4. The member issue path. The refusal's own path is ['confirmText']; it is attached to the member schema, and ActionGroupPropsSchema.actions / ActionMenuPropsSchema.actions are z.array(actionGroupMember()) / z.array(actionMenuMember()), so zod prefixes the element index: ['actions', i, 'confirmText'] on both containers. The test pins index 1 on both (the offending member is the second). validateComponentProps joins it under the node's properties, so the finding lands at ….properties.actions.N.confirmText (and ….properties.confirmText on the icon), which is what the dev's door probe printed. Right.
  5. Interplay with actionContainerMemberParamsFitType. That refine returns early on params === undefined || Array.isArray(params) || type === 'api', and refuses at ['params'] otherwise. The two refines are disjoint on the params axis: an array params is fit-type-silent and pair-judged; a non-array params on a non-api member is fit-type-refused and pair-silent (Array.isArray is false); an object params on an api member passes both. No row gets two verdicts for one fact, and no previously-accepted shape outside the ruling is newly refused. Right.
  6. The remedy key exists on every door. description is declared on the icon row (L4153: "the tooltip text when there is no label, and forwarded to the runner for the parameter dialog") and on the member shape (L4313: "the parameter dialog shows it under its title"); action-icon.tsx, action-group.tsx and action-menu.tsx forward it as description to the runner, whose param dialog draws it. The message's prescription is actionable on all three rows. Right.
  7. The measurement grounding the ruling holds in the cited code. action-icon.tsx forwards an array params as actionParams and confirmText as itself; action-group.tsx L350-352/L380 and action-menu.tsx L273-275/L312 do the same per member; ActionRunner.execute L1675-1684 awaits the confirm, then L1690-1693 reads actionParams and opens the param dialog when its length is positive. Two dialogs, in that order, for exactly the shape refused. Right.
  8. Public surface and types. Each of the three schemas was already a refined object (four superRefines), so one more .refine changes no z.input / z.output type, no ComponentPropsMap row and no export; api-surface/ and authorable-surface/ are untouched in the diff, and dropped-refinements.baseline.json already lists ui/ActionIconProps, ui/ActionGroupProps and ui/ActionMenuProps. No authorable key moves. Right.
  9. Describe and generated docs. The icon's and the member shape's confirmText describe append action:button's sentence, the member variant naming the type: 'api' payload (true per 5); ACTION_ROW_COPY_DESCRIBE.confirmText stays shared and unchanged. The three component.mdx rows reflect those describes verbatim and nothing else moves in that file. No tracker id in any runtime string (the refusal message and the three describes carry none; #30017 appears in comments only). Right.
  10. Census carrier repaired honestly. The pin test's "a member with every forwarded scalar" keeps confirmText and loses its params; a new case "a member collecting params" carries the array with description. Both still parse byte-identical. Right.

② Semver level

  • '@objectstack/spec': minor, Clause-②: no (narrowing), Metadata-protocol: narrowed, the **BREAKING** banner, the fix(spec)!: summary, exactly one adr-0087 marker, the same two declaration lines on the PR body. AGENTS.md step 3: a kind takes at least minor on the moved package; narrowed is the right kind ("a value refused … is narrowed"); (narrowing) is BREAKING and the banner is present. The Check Changeset run on the head (both checkers) concluded success. Level, kind and arm: correct.
  • The ADR-0087 disposition is not honest for this refusal — must-fix. The marker reads not-required (no-migration-prescription) and its why argues that no key is added, removed or renamed and that "there is no rewrite that keeps what the author meant without reading it, so the author decides, and the refusal names the remedy". That is an argument for no D2 conversion. ADR-0087 D3 says that exact shape is what a semantic entry is for: "A migration that cannot be expressed declaratively gets a structured TODO entry (surface, reason, acceptance criteria) rather than silence, so the agent knows exactly what judgment is being delegated to it", and SemanticMigration's docblock: "One retirement family's D3 entry — owed even when a lossless D2 conversion carries the data repair … (ADR-0087 D3: 'never silence')". The ledger's own practice on these very rows confirms it: 18.ui-page-action-row-on-success-beside-executor-refused.ts registers "onSuccess beside an executor other than api / script on an action:button / action:icon and each action:group / action:menu member", explicitly "⛔ NOT mechanically convertible … the author's call", with "Measured producers: none"; and 18.ui-action-group-menu-member-params-array-only.ts registers a member params refusal "D3 only … there is no D2 conversion … and the census found no writer to respell". A pair refusal on the same rows, with a hand remedy and a zero census, is the same class, and this changeset's body carries that remedy ("The remedy. Move the confirm sentence to the action's description … and drop confirmText") — the category's own definition in the gate (L147-152: a body "that ships instructions for rewriting a consumer's code cannot also claim no consumer has to rewrite anything") says the two cannot coexist. The gate let it through only because findMigrationPrescription reads arrow-form and framed-table rewrites, not a "The remedy." paragraph; ADR-0087's ADR-0087's changeset disposition has no category for published runtime TS interfaces with no metadata surface — the #8277 exemption argument is correct, unverifiable, and will be re-litigated #8299 addendum (the disposition vocabulary) names an exemption "held by a DETECTOR MISS, not by a positive finding" as the anti-pattern this vocabulary exists to close. PR fix(spec)!: an action:button refuses confirmText beside a non-empty array params, as the two action doors do #29869 is the precedent the changeset cites, and it did ship this way (CHANGELOG.md L2321, in the released 18.x line, as spec(ui): an inline action accepts confirmText beside params, the pair ActionSchema refuses as two dialogs; once #29369 gives the inline pick description, decide whether the inline door refuses it too #29383 did at L2148) — that makes the omission two doors wide, not honest. What the consumer loses: an upgrader carrying the pair on an icon or a member gets no os migrate meta TODO for step 18; the first word is the save-door refusal. Fix: one D3-only entry packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-confirm-text-beside-params-refused.ts in the shape of the on-success-beside-executor-refused entry — surface: properties.confirmText beside a non-empty array properties.params on an action:button or action:icon, and confirmText beside a non-empty array params on each action:group / action:menu member; replacement: the question on description, confirmText dropped (the icon tooltip note from the changeset belongs in reason); reason: the runner order and the save-door read; acceptance: os validate reports no component-props-invalid at those paths. Naming all four rows closes fix(spec)!: an action:button refuses confirmText beside a non-empty array params, as the two action doors do #29869's gap in the same family entry. Then the marker becomes registered ui-page-action-row-confirm-text-beside-params-refused (the registration gate verifies the id resolves at HEAD and is new in the diff; gen:migration-registry reads the directory, and runs under build, test and typecheck). The changeset text otherwise stands.

③ Boundary flags

  1. Dev "stopped on": the action-confirm-beside-params.ts docblock still says "read by the three doors", "asserts the three messages equal", "on all three doors". Confirmed stale at the head (L5-6, L8-9, L16-17 of that module): five doors now read it. It is a comment; the claim's file surface admitted the module only for a message variant, which was not needed, so the dev was right to stop. Should-fix, on this PR's fix round (the seat owns the claim and can widen the surface by one comment block): count five doors and name the two new rows. No behaviour rides on it.
  2. Dev "stopped on": the description remedy changes a label-less icon's tooltip. Confirmed in the cited renderer: action-icon.tsx wraps the button in a tooltip reading schema.label || schema.description, and aria-label is schema.label || schema.name, so on an icon with no label the moved question becomes the hover text (the accessible name does not move). The icon row's own description describe already declares that dual role (L4153), the renderer's description input says the same, and the changeset's remedy line states the side effect. The author's escape is to give the icon a label. A renderer fact outside the ruling, not a spec defect; answered, no change asked. Should-fix (optional, wording only): the icon's confirmText describe could end "on a label-less icon description is also the tooltip, so give it a label" — the changeset already says it, so this is not required.
  3. check:dev-prereqs exit 1 (35 ui/ packages without dist). A local-only family that judges whether that checkout can boot a dev server; it is not derived for this diff and no check-run carries it. Answered: machine state.
  4. open_questions: none. Answered.
  5. out_of_scope_findings: the docblock in 1. Answered there.
  6. Skills-docs claim. skills/objectstack-ui/rules/actions.md L22 ("A registered action is refused it beside a non-empty params (put the question on description)") stays a true sentence and names this refusal's remedy; it is a Tier H path and outside this PR. Answered.
  7. Check-runs on 0446795a12 at the second read. Concluded success: Auto Label, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint · CI/release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped (query-options erasure; slot lookup & corpus; tooling self-tests), Live E2E UI, PR-body guards, Plan Test Core shards, Spec property liveness, Type Check · source gates (carries check:docs and check:authorable-surface), filter. skipped: 18.x upgrade harness, Dogfood shard matrix, Dogfood Verify CLI, Lint UI, Packed-tarball smoke, Schedule gate, Temporal Conformance, UI Published Artifacts. Still in_progress, no conclusion: Build Core, Build & E2E UI, Test Core 1-8, Test UI 1-16, Type Check UI, Type Check · consumer gates (carries check:api-surface), Type Check · debt ledger, Type Check · workspace. Nothing failed. Reported as seen, not waited for; the PR body's local readings (25669 spec tests, 102 derived families at exit 0, the three ablation legs, the door probe) are the dev's account and are not adopted here.
  8. Governed surfaces. None of the five paths is under docs/adr/**, skills/**, .claude/**, AGENTS.md or .github/workflows/**; the Governed Surface Queue Guard concluded success. The fix in ② adds a file under packages/spec/src/migrations/entries/semantic/, which is not a governed surface either.

④ Design judgment

packages/spec/src/ui/component.zod.ts — holds; the must-fix is on the changeset it ships with, not on the schema. Judged against ruling 6624208841 letter A and its first consumers (the lint validateComponentProps door at the CLI commands and the runtime page save; the action:icon, action:group and action:menu renderers through ActionRunner).

  • Does it make the spec's own refusal real at the consumer? Yes. The door dispatches each node's properties to ComponentPropsMap[type].safeParse and reports each issue at properties + the issue path under component-props-invalid, so the icon's lands at properties.confirmText and a member's at properties.actions.N.confirmText, with the message naming the description remedy that every one of the three rows declares and forwards. A stored page keeps loading (the load path parses no component properties), and its next save is refused — the same read as the four sibling step-18 entries on these rows state.
  • Does it take a declared capability away beyond the ruling? No. The predicate's Array.isArray + positive-length read is the complete reading of "non-empty array"; the four controls per door pin the kept shapes (① 2), and the fit-type refine stays the sole judge of a non-array params on a non-api member (① 5).
  • Attempts to break it. (a) The member alias table (ACTION_CONTAINER_MEMBER_ALIASES) folds actionType → type and the target spellings; it never renames params or confirmText, so the predicate sees the authored pair. (b) Each superRefine before the .refine adds issues without fatal, so the pair refusal is reported beside any other row verdict rather than hidden behind it; the tests read the issue by path, not index, and the pair-only fixtures assert exactly one issue. (c) action:bar members are ActionSchema rows, which already refuse the pair, so no composition that reaches these renderers through the bar is newly refused there. (d) A member with confirmText and an object params on a non-api type draws the fit-type verdict alone; once the author turns params into an array the pair verdict appears — two rounds, but each verdict is true when given and the second is the ruling's; not a defect. (e) The lazySchema wrappers and the *MemberOnce caches are unchanged, so the refine is built once per member schema and the ActionGroupProps / ActionMenuProps actions arrays share it.
  • Must-fix: none in this file.
  • Should-fix: none in this file; the docblock count in ③ 1 lives in the sibling module.

The narrowing against the published spec on main (the narrowing face, read on the whole diff): it makes the spec's own two-dialog refusal real on the two doors that still let the pair through, with the predicate and wording three released doors already carry; it removes no capability the renderer honoured, since the pair never produced one dialog. The one thing the narrowing owes and does not carry is its step-18 ledger entry (②).

Implemented-by: claude/issue-30017-confirm-beside-params-icon-members
Reviewed-by: session_01L6bhgVugtCCwBs98FeR27P

VERDICT: FAIL


Generated by Claude Code

…efusal 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
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9d69f747cd545a5e11690329ea16436a77d8a49a
Local-runs: none
Scope: packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-confirm-text-beside-params-refused.ts, packages/spec/src/ui/action-confirm-beside-params.ts, packages/spec/src/ui/component.zod.ts + .changeset/30017-action-icon-members-confirm-beside-params-refused.md, packages/spec/src/ui/action-confirm-params-guard.test.ts, packages/spec/src/ui/component-report-items-action-members-typed.pin.test.ts, content/docs/references/ui/component.mdx, packages/spec/src/ui/action.zod.ts, packages/lint/src/validate-component-props.ts, packages/spec/src/migrations/types.ts, packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-beside-executor-refused.ts, packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts, packages/spec/src/migrations/entries/README.md, packages/spec/scripts/build-migration-registry.ts, packages/spec/src/migrations/migrations.test.ts, packages/spec/src/migrations/semantic-relevance.test.ts, scripts/check-adr-0087-registration.mjs, docs/adr/0087-metadata-protocol-upgrade-contract.md, ui/packages/components/src/renderers/action/action-button.tsx, ui/packages/components/src/renderers/action/action-icon.tsx, ui/packages/components/src/renderers/action/action-group.tsx, ui/packages/components/src/renderers/action/action-menu.tsx, ui/packages/core/src/actions/ActionRunner.ts, packages/spec/CHANGELOG.md, AGENTS.md

Re-review after FAIL 6666209629 on 0446795a; this record judges head 9d69f747 in full. Inputs read: card #30017 body and its six comments (ruling 6624208841 letter A, triage 6624670416, claim 6663933383, os-dev-report 6665968613, amendment 6666273551, patch-round os-dev-report 6667781526); PR #30624 body, its seven-file list and the net diff of 9d69f747 against origin/main (merge base 5b748dfede; main has since added 18.sqlite-filename-names-no-database-refused.ts, a different entry file, which the id-named shard layout merges clean); the two PR comments (docs-drift 6665974405 and the previous record); the check-runs on 9d69f747, read at 2026-11-05T19:55Z; and the cited code in the live tree at the head: the predicate module whole (comment-stripped bytes diffed against main), component.zod.ts L4163-4189, L4321-4329, L4422-4430, L4453-4461 and the import at L71, action.zod.ts L2340 and L2655, the guard test's new describe whole, the pin test's new case, the three regenerated component.mdx rows, the changeset whole, validate-component-props.ts L155, L198 and L427-430, SemanticMigration and its relevantWhen / conversionIds docblocks (types.ts L60-166), the two sibling step-18 entries whole, the entries README and build-migration-registry.ts L43-118 (filename derived from id, enforced), migrations.test.ts L135-141, semantic-relevance.test.ts L1-70 (the criterion and the enumerated table), the registration gate's registered arm (L5490-5512) and extractIds (L2083), ADR-0087 D3 (L179-210) and the #8299 addendum (L732-770), action-button.tsx L257 and L333, action-icon.tsx L153-156, L176, L199 and L321-334, action-group.tsx L351 and L380, action-menu.tsx L274 and L312, ActionRunner.ts L1659-1693, CHANGELOG.md L2148 and L2321, AGENTS.md Post-Task Checklist step 3. The face: --paths returns hits = the three files above (a published schema source and a governed ledger entry), and the PR body and changeset both carry Clause-②: no (narrowing), so the tier is CONTRACT_REVIEW_TIER on either face. Nothing was built, run or re-run.

① Derived judgments

  1. What is newly refused (schema, unchanged since the first head). Three rows end their chain with one .refine whose predicate is the negation of declaresConfirmBesideParams(row) and whose issue is CONFIRM_BESIDE_PARAMS_REFUSAL: ActionIconPropsSchema (L4189), buildActionGroupMember (L4430) and buildActionMenuMember (L4461). The predicate reads confirmText not null and params an array of positive length, so the only values newly refused are confirmText (bare string or the template envelope) beside an array params of length one or more, on an action:icon's properties and on each member of an action:group / action:menu properties.actions. The ruling's letter A exactly. Right.
  2. What is still accepted. No params; params: []; an object params (the icon's static values per readStaticParamValues at action-icon.tsx L153-156; a type: 'api' member's request payload, the only non-array a non-api member may not carry, judged by actionContainerMemberParamsFitType alone); and the remedy, an array params with description. Each pinned per door as a full safeParse success. Right.
  3. One predicate, one message, six refine sites on five doors. action.zod.ts L2340 and L2655 (ActionSchema, InlineActionSchema), component.zod.ts L4083 (action:button) and the three new sites all import the same two symbols; component.zod.ts carries no second literal of the message. The guard test pins the icon's and both members' message toBe action:button's issue, and the earlier group pins action:button's toBe ActionSchema's and InlineActionSchema's, so the equality is transitive across all six sites. The predicate module's docblock counts the shared member shape as one door, which is how the card, the claim and the previous record counted it. Right.
  4. Issue path at the consumer. The refusal's own path is ['confirmText'] on the member schema; zod prefixes the element index under actions, so the container reports ['actions', i, 'confirmText'] (pinned at index 1 on both containers). validateComponentProps L427-430 joins base + issue.path, with base ending in .properties, under component-props-invalid (L155), so the finding lands at ….properties.confirmText and ….properties.actions.N.confirmText. These are exactly the two paths the new entry's acceptanceCriteria names. Right.
  5. The D3 entry is a real registration. id ui-page-action-row-confirm-text-beside-params-refused equals the file stem under 18.; the generator derives the filename from the id and refuses a mismatch (L107-114), and gen:migration-registry runs before build, typecheck and test, so the green Build Core / Test Core / Type Check runs on the head are proof the entry parsed and registered. extractIds (L2083) reads the id from the id: '…', line, which the file spells exactly so. The changeset marker is registered ui-page-action-row-confirm-text-beside-params-refused, one marker, the id resolving at HEAD and new in the diff (the gate's L5490-5512 arm), and Check Changeset concluded success twice on the head, the second after the PR-body patch. Right.
  6. Entry fields against the gate that enforces them. surface names exactly the four page rows (properties.confirmText beside a non-empty array properties.params on action:button / action:icon; confirmText beside a non-empty array params on each action:group / action:menu member of properties.actions) and exactly the refused pair, and excludes params: [], an object params and no params: this is declaresConfirmBesideParams read on the four sites, including action:button's shipped refine (L4083, PR fix(spec)!: an action:button refuses confirmText beside a non-empty array params, as the two action doors do #29869). replacement is the shipped message's own remedy (description, which every one of the four rows declares and forwards to the runner, whose param dialog draws it under its title), plus the author's other option (keep confirmText, drop the params when nothing is collected), and is marked NOT mechanically convertible, as its sibling is. acceptanceCriteria is what the props gate checks (④). relevantWhen is absent, rightly: the criterion semantic-relevance.test.ts L30-35 states admits only an entry whose acceptance sends the author to no runtime door, and this entry's reason and acceptance name the metadata save door and os validate, exactly as the on-success-beside-executor-refused sibling does, which is also absent from the enumerated table; the table and its 26-entry count stay untouched. conversionIds is absent, rightly: no D2 conversion exists for this family. No tracker id in any field (grep for #NNNN, 30017, 29869, 29844, 29383, the ruling id: none). Right.
  7. Predicate module: comment-only. The comment-stripped bytes of action-confirm-beside-params.ts at the head and at origin/main are identical; declaresConfirmBesideParams and CONFIRM_BESIDE_PARAMS_REFUSAL are byte-for-byte what main ships. The docblock now names the five doors, the two new rows, both forwarders and the member's type: 'api' object payload, all true in the cited renderers. Right.
  8. Public surface and types. Each refined schema was already a refined object, so one more .refine changes no z.input / z.output type, no ComponentPropsMap row and no export; api-surface/ and authorable-surface/ are untouched and Type Check · consumer gates and Type Check · source gates concluded success. The entry is registry data, not an export. Right.
  9. Describes and generated docs. The icon's and the member shape's confirmText describe append the sentence action:button's carries, the member variant naming the type: 'api' payload; the three component.mdx rows reflect them verbatim and nothing else moves. Right.
  10. Census carrier repaired honestly. The pin test's action:menu member keeps confirmText and loses its params; the new case carries the array with description. Right.

② Semver level

  • '@objectstack/spec': minor, Clause-②: no (narrowing), Metadata-protocol: narrowed, the **BREAKING** banner, the fix(spec)!: summary, exactly one adr-0087 marker, the same two declaration lines on the PR body. AGENTS.md step 3: a kind takes at least minor on the moved package, narrowed is the kind for a value refused, (narrowing) is BREAKING and the banner is present. The Check Changeset run on the head concluded success. Level, kind and arm: correct.
  • The ADR-0087 disposition is now honest. The marker reads registered ui-page-action-row-confirm-text-beside-params-refused; the entry exists at HEAD and is new in this diff (① 5). The body's remedy paragraph is now the one-line fix that names the D3 entry and os migrate meta --from 17, and the earlier claim that no consumer has to rewrite anything is gone, so the body and the marker say the same thing: a hand rewrite, registered. The registered arm carries no prescription check to contradict, and the gate's own registered verification passed on the head. The sentence that the entry also covers the action:button refusal shipped earlier in 18.x is true in the cited code: CHANGELOG.md L2321 is that released line, and the entry's surface names action:button. The must-fix of 6666209629 is closed.

③ Boundary flags

Findings of the previous record 6666209629, each against head 9d69f747:

  1. Must-fix (ADR-0087 disposition): closed. The one D3-only entry asked for exists in the shape of the on-success-beside-executor-refused sibling, names all four page rows, carries the description replacement, the NOT-mechanically-convertible marker, the runner order, the label-less icon tooltip note and the load-path / save-door read in reason, and the os validate / component-props-invalid acceptance; the marker became registered with that id. ① 5-6 and ② above.
  2. Should-fix (the "three doors" docblock): closed. The predicate module's docblock counts five doors, names action:icon and each action:group / action:menu member, and the predicate and message are byte-identical to main (① 7).
  3. Optional (an icon-describe note on the tooltip): left out, as allowed. The dev report says so. The tooltip side effect is stated in the changeset's fix line and in the entry's reason, where an upgrader reads it; the describe is not required to repeat it. Answered.

Dev flags, both reports:

  1. Patch-round report: dogfood battery not rerun (the patch adds a D3 entry, a docblock and changeset prose). Answered by the head's own check-runs: Dogfood Regression Gate concluded success on 9d69f747, as did Test Core 1-8 and Test UI 1-16; the schema behaviour is unchanged since 34eb15a15d.
  2. Patch-round report: check:type-check-debt first exited 3 on a lock queue-timeout, rerun 0. A local lock contention; the head's Type Check · debt ledger concluded success. Answered.
  3. Patch-round report: open_questions none, security_findings none, out_of_scope_findings none. Answered.
  4. First report: the docblock stop. Closed in 2. First report: the label-less icon tooltip. Confirmed again in action-icon.tsx L321-334 (schema.label || schema.description); now written into the entry's reason. Answered. First report: check:dev-prereqs exit 1 (35 ui/ packages without dist). Machine state of that worktree, no check-run carries it. Answered.
  5. Docs-drift comment 6665974405. Advisory; it lists hand-written pages naming confirmText / actionType and notes that action-confirm-beside-params.ts yielded no anchor. The module's change is comment-only, so no page documents what moved; Flag docs affected by code changes and Lint · docs & code guards (which carries check-drift-comment) concluded success. Answered, no change asked.
  6. Skills-docs claim. skills/objectstack-ui/rules/actions.md L22 stays a true sentence naming this refusal's remedy; a Tier H path outside this PR. Answered.
  7. Check-runs on 9d69f747, read at the time above. Every run is completed; none concluded failure. success: Auto Label, Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset (twice), Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint · CI/release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped (query-options erasure; slot lookup & corpus; tooling self-tests), Live E2E UI, PR-body guards (twice), Plan Test Core shards, Spec property liveness, Test Core and its 8 shards, Test UI and its 16 shards, Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter. skipped: 18.x upgrade harness, Dogfood shard matrix, Dogfood Verify CLI, Lint UI, Packed-tarball smoke, Schedule gate, Temporal Conformance, UI Published Artifacts, and the re-triggered Auto Label / Check PR Size. The PR body's local readings (104 derived families at exit 0, the ablation legs, the door probe) are the dev's account and are not adopted here.
  8. Governed surfaces. No path under docs/adr/**, skills/**, .claude/**, AGENTS.md or .github/workflows/**; packages/spec/src/migrations/entries/semantic/ is a ledger directory, not a governed surface; the Governed Surface Queue Guard concluded success.

④ Design judgment

packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-confirm-text-beside-params-refused.ts — holds. Judged against ADR-0087 D3 ("a migration that cannot be expressed declaratively gets a structured TODO entry (surface, reason, acceptance criteria) rather than silence"), the SemanticMigration contract, its two siblings on these rows, and its first consumer, os migrate meta --from 17, which lists it on the step-18 hop.

  • Surface against the shipped refinements. The entry claims the pair is refused on four rows; the tree refuses it on exactly those four (component.zod.ts L4083, L4189, L4430, L4461), and on no other page row. The three exclusions it states are the three the predicate's Array.isArray + positive-length read leaves alone, and the entry attributes the object params correctly per row (static values on the two node rows, action-button.tsx L257 and action-icon.tsx L153-156; the api member's payload, actionContainerMemberParamsFitType). An upgrader who greps the entry finds every refused shape and no shape that still parses.
  • Replacement and acceptance against the props gate. The gate reports each row issue at properties + the issue path under component-props-invalid, so the two paths the acceptance names are the two the gate emits (① 4), and "every row with no params, params: [] or an object params parses as before" is the pinned preservation set. The replacement's key, description, is declared on all four rows and read by the runner's param dialog (ActionRunner.ts L722, L2056-2077), so the prescription is actionable on every row it names.
  • Reason against the runner. ActionRunner.execute awaits the confirm (L1659-1684) and then opens the param dialog on a positive-length actionParams (L1688-1693); all four forwarders hand an array params across as actionParams and confirmText as itself. The tooltip note is true at action-icon.tsx L321-334. The load-path / save-door sentence is the sibling entries' own, unchanged. "Measured producers: none shipped in this repository; the one carrier was a spec test fixture, respelled" matches the pin-test diff.
  • Attempts to break it. (a) A hop printer could try to prove it absent and hide it: no relevantWhen, so it is always listed, which is right for a surface the metadata API can store (① 6). (b) The id could collide with the sibling family: …-confirm-text-beside-params-refused is distinct from …-on-success-beside-executor-refused and the generator sorts by id, no index to conflict on. (c) The registration could be claimed without the entry: the gate resolves the id at HEAD and requires it new in the diff, and both held. (d) The action:button half could be read as retroactive: a consumer replaying 17 → 18 meets the whole refusal family in one hop, which is what D3's "one entry per retirement family" asks; a consumer already on an 18.x that refused action:button loses nothing by seeing the row named again.
  • Must-fix: none. Should-fix: none.

packages/spec/src/ui/action-confirm-beside-params.ts — holds. Comment-only (① 7). Each sentence the docblock adds is true in the cited renderers and the runner; no behaviour rides on it; the test the docblock cites pins the equality it describes. Must-fix: none. Should-fix: none.

packages/spec/src/ui/component.zod.ts — holds, unchanged since the first head. Judged against ruling 6624208841 letter A and its first consumers (the lint door and the three renderers through ActionRunner). The refine makes the spec's own two-dialog refusal real at the save door on the two rows that still let the pair through, with the predicate and wording the three released doors carry; it takes no declared capability away beyond the ruling (the four kept shapes per door are pinned); the alias table never renames params or confirmText, so the predicate sees the authored pair; each preceding superRefine adds issues without fatal, so the pair verdict is reported beside any other and the pair-only fixtures assert exactly one issue; a non-array params on a non-api member draws the fit-type verdict alone and the pair verdict only once params is an array, each verdict true when given. The narrowing against the published spec on main now carries the one thing it owed, its step-18 ledger entry. Must-fix: none. Should-fix: none.

Implemented-by: claude/issue-30017-confirm-beside-params-icon-members
Reviewed-by: session_01L6bhgVugtCCwBs98FeR27P

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P

维护者速读

  • 改了什么:页面上的 action:icon,以及 action:group / action:menu 里的每个成员,现在不能再同时写 confirmText 和一个非空的 params 列表,和 action:button、注册动作、内联动作一样会被拒绝,报同一句话:改法是把确认的问题写进 description,去掉 confirmText。另外登记了一条升级指南条目,四种页面动作行都覆盖。
  • 为什么改:这两样同时写,界面会先弹一个确认框(看起来像动作已经执行了),然后再弹参数框,一个决定弹两次。其他三扇门早已拒绝,这两处漏了。
  • 风险与回滚:收窄了已发布的接受集,changeset 标了 **BREAKING**。没有 params、params: []、对象形式的 params 都照旧接受。已存的页面照常加载,下次保存时才报。仓库里没有在用的写法,唯一一处是测试夹具,已改。回滚即 revert 本 PR。
  • 先看:packages/spec/src/ui/component.zod.ts(三处 .refine,复用 declaresConfirmBesideParams)→ packages/spec/src/ui/action-confirm-params-guard.test.ts 新增的 describe → packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-confirm-text-beside-params-refused.ts。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/ui/action-confirm-params-guard.test.ts,预期全过;去掉 icon 的 refine 后 1 条转红,去掉 group 或 menu 的各 2 条转红。
  • 需要你做的:无,席位落地。
  • 席位意见:接受。第一轮契约复核 FAIL(6666209629,Head-sha 0446795a),唯一的 must-fix 是 ADR-0087 处置不诚实;补丁轮后再复核 PASS(6667945270,Head-sha 9d69f747),三个 hits 文件都没有 must-fix 或 should-fix。

席位核对

ACCEPT · head 9d69f747cd545a5e11690329ea16436a77d8a49a · 2026-11-05T20:02Z

  1. 形态:draft,base main,正文 Fixes #30017,全文只有这一个闭合关键词。7 个文件都在认领文件面内:新的语义条目和 action-confirm-beside-params.ts 的文档注释由修订 6666273551 补进文件面。ui/**、skills/**、docs/adr/**、.claude/**、AGENTS.md 都没碰,没有 content/docs/releases/。
  2. 前提实测:在 origin/main 2ffdb649 上,action:button 已用 declaresConfirmBesideParams 拒绝这一对;ActionIconPropsSchema 和 actionContainerMemberShape() 的两个消费者带着 params 与 confirmText,却没有这条 refine。裁决 6624208841 选 A。前提成立。
  3. 门禁 check-runs:
    • head 9d69f747 上 59 success、10 skipped、0 failure,全部跑完,Dogfood Regression Gate、Test Core 1-8、Test UI 1-16 都在其中。
    • 契约再复核 6667945270 在 Served-tier: CONTRACT_REVIEW_TIER 下 PASS,Scope 覆盖三个 hits(component.zod.ts、action-confirm-beside-params.ts、新语义条目)以及门禁、渲染器和 ActionRunner 的所引代码。
    • dev 补丁轮本地 104 条派生门禁 --ran 0 NOT-MEASURED,只证本地子集;check-adr-0087-registration --base origin/main exit 0,读到 registered 且条目在本 diff 中新增。
    • (narrowing) 要求的 dogfood_battery:首轮在 34eb15a1 上 command-exit 0(420 个文件通过、3 个跳过)。补丁轮只加了一条条目、一段注释和 changeset 文字,schema 行为不变,head 上的 Dogfood Regression Gate 也是 success。
    • 消融(首轮):分别删掉三处 refine,icon 1 条、group 2 条、menu 2 条转红;每次还原后 blob 等于 HEAD。
  4. 席位自读(TIER_DEFAULT,hits 之外的部分):
    • 守卫测试新 describe:icon 和两种容器成员的拒绝都断言 custom、具体路径(confirmText / actions.1.confirmText)、只有一条 issue,且文案与 action:button 的那条逐字相等;容器成员还覆盖模板信封写法。接受集覆盖无 params、params: []、对象 params 和改法本身。
    • pin 测试里唯一的旧夹具改成新用例,问题挪到 description。
    • component.mdx 三行是生成产物,与三处 describe 一致,check:generated 绿。
    • changeset 逐句核过:
      • 声明行 Clause-②: no (narrowing) / Metadata-protocol: narrowed 与认领 6663933383 一致;@objectstack/spec minor,没有 major。
      • 处置标记改为 registered ui-page-action-row-confirm-text-beside-params-refused,原来与改法自相矛盾的 “no rewrite” 已删。
      • “A stored page keeps loading and serving; its next save is refused” 与新条目的 reason 一致。
      • 改法一行点名了条目和 os migrate meta --from 17,并说明无 label 的 icon 上 description 也是 tooltip。
  5. 接受的偏差:
  6. 范围外去向和 Skills-docs::
    • Skills-docs: none — … 行在,属实:skills/objectstack-ui/rules/actions.md L22 那句仍然成立,并且给的就是本拒绝的改法。
    • docs-drift 评论 6665974405 是提示性的;改动的模块只动了注释,没有页面记录的内容变了,漂移门禁绿。不立卡。
  7. 落地条款:check-governed-merges.mjs --pr 30624 已在本 head 读过:7 个路径 0 个命中 governed 登记,253 行(+234 / -19)≤ 3000,属于 Tier S;落地前再读一次。经 relay pr_ready + automerge_enable,跟到 MERGED,同一动作里摘 pm:dispatched。

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 20:05
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 20:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit f3a52d3 Nov 5, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-30017-confirm-beside-params-icon-members branch November 5, 2026 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(ui): action:icon and action:group/menu members refuse confirmText beside an array params (ruling A)

1 participant