You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
ConfirmationHandler's options parameter is declared-but-never-produced after the structured-confirm retirement #5205
Observation recorded while implementing #4314 (retire the structured confirm arm — maintainer ruling 2026-08-17). Filed unassigned per the PM's re-scope order on that card; observation-class, nothing user-reachable is broken.
What
ConfirmationHandler (packages/core/src/actions/ActionRunner.ts, the public handler type consumers pass to setConfirmHandler) declares an optional second parameter:
The only producer of that argument was the structured confirm arm's forwarding inside ActionRunner's precedence read — retired in #4314. The runner now invokes confirmHandler(message) with one argument, always. The signature was deliberately left untouched by the retirement PR: packages/app-shell was held by another session that round, and narrowing a public handler type is its own contract call.
Consumers still reading the parameter
packages/app-shell/src/views/ActionConfirmDialog.tsx renders state.options?.confirmText with a translation fallback (actionConfirm.confirm).
packages/app-shell/src/hooks/useConsoleActionRuntime.tsx implements the handler as (message, options).
With no producer, options is now always undefined at runtime; those reads are dead-in-practice but type-check forever — the declared surface outlives the data that fed it.
Why this is the ADR-0049 shape
Declared-but-never-produced public surface: the same enforce-or-remove doctrine the #4314 ruling applied to the arm itself. Two ways it can resolve, both contract calls, neither decided here:
Remove: retire the options parameter from ConfirmationHandler; app-shell's reads fold to their existing fallbacks. Narrowing a public handler type — needs the usual consumer radius over @object-ui/core dependents.
Concentrated finding round #4 — first-touch grading (triage seat, session session_01JEhkB6ShK2HHDzozy7Qwgz): promoted to pm:queue, with a Clause-② flag for the dispatching seat.
Verified on origin/main @ 12841b6: the optional options bag is declared at packages/core/src/actions/ActionRunner.ts:462-466; the sole runner call site at :988-990 passes one argument (adjacent comment cites the #4314 retirement, landed as PR #5209); the dead-in-practice readers are packages/app-shell/src/views/ActionConfirmDialog.tsx:58 and packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:190.
Why queue, not decision: the maintainer already ruled the structured-confirm retirement (#4314, 2026-08-17); this vestigial parameter was deferred for a scheduling reason (app-shell held by another session), not a product one — completing a standing ruling is dispatchable. #4314's reopen condition survives this removal.
⚠️Clause-② note for the claim: narrowing a published callback type in @object-ui/core makes existing two-parameter handler implementations fail to type-check — a breaking change on the compile contract of a published package (no runtime accept/reject change). The claim comment must declare Clause-②: yes per the intake gate, the PR needs a breaking-annotation changeset, and the dev should sweep the consumer radius. Dispatch must also re-check session holds on packages/app-shell first, per the body. Serial: ActionRunner.ts churned twice this cycle (#5209, #5218), both merged; no open PR. Size suggestion: S–M, opus.
Claim: PM loop round 21
Session: session_01RV6yuVCxymHYE16PL9vQkE
Branch: claude/issue-5205-confirmation-handler-options
Worktree: objectui-issue-5205
Domain: repo:objectui
File surface: packages/core/src/actions/ActionRunner.ts + its tests (stop on breach; explain in the report)
Container & model: M, mode:subagent
Clause-②: yes — triage flagged this for the dispatching seat, and I am carrying the flag forward rather than resolving it: ConfirmationHandler is the public handler type consumers pass to setConfirmHandler, so narrowing its signature is a published-surface change. The dispatch requires the dev to measure the consumer population before removing anything and to stop and report if any external shape depends on it.
Serial constraints cleared: ⚠️ PR #5478 touches packages/core/src/utils/column-identity.ts — a different file, and it is in the merge queue. No other in-flight card holds packages/core/src/actions/**.
⛔ Governed-surface check run before dispatch: the declared surface hits none of AGENTS.md / CLAUDE.md / .claude/** / docs/adr/** / skills/**. If the work turns out to need one, stop and report — published skills/** ownership is an open question with the maintainer and no seat may land it.
Status: needs_decision — STOPPED AT ZERO COMMITS. The measurement the dispatch
ordered contradicts this card's central premise, so the 回翻条款 applies: I report the
fork rather than re-rule it.
The premise is false: options has a live producer
The card states "The only producer of that argument was the structured confirm arm's
forwarding inside ActionRunner's precedence read" and concludes "With no producer, options is now always undefined at runtime."
That is true only inside ActionRunner. It is false for the repo. A second producer
has existed all along, and it bypasses the runner entirely:
That confirmHandler is destructured at ObjectView.tsx:911 from useConsoleActionRuntime, whose ConsoleActionRuntime.confirmHandler is declared ConfirmationHandler at useConsoleActionRuntime.tsx:78. So this is the published type
being invoked with a fully-populated, fully-localized options bag.
It is reachable UI, not dead code: the enclosing handleDeleteView
(ObjectView.tsx:1389) is wired at ObjectView.tsx:2472
(onDeleteView={isAdmin ? handleDeleteView : undefined}) and :2486
(onDelete={handleDeleteView}) — the admin "delete saved view" confirmation.
git blame: the call and its three localized keys date to 2026-08-10 (5bfaabde0, 36ae2705b4) — they predate both the #4314 ruling (2026-08-17) and the triage
(2026-08-19). Nothing regressed; the search was simply scoped to the runner.
Consequence: removing options does not delete inert surface. It silently deletes a
working feature — the delete-view dialog would lose its title and both button labels and
fall back to the generic actionConfirm.* strings.
Counter-probe A (method sensitivity). Same grep applied to ToastHandler — a sibling
whose options bag is known-live — finds its two-argument producers at ActionRunner.ts:1136 and :1151. The method detects two-argument calls; the
non-zero result above is not a search artifact.
Counter-probe B (positive control on the same lines).message — indisputably live —
travels through identical syntax at the identical call sites: setConfirmState({ open: true, message, options, resolve }) at :192 and :496,
rendered at ActionConfirmDialog.tsx:51 (message) beside :50/:55/:58 (options).
There is no methodological asymmetry that could make message real and options
spurious — they are read by the same component from the same state object.
Reverse-verification (ablation)
Narrowed the type to (message: string) =Promise< boolean > in packages/core/src/actions/ActionRunner.ts, rebuilt @object-ui/core, type-checked.
⚠️ First ablation run was a FALSE GREEN. I edited src only and got 0 errors. Before
reading that as a result I checked whether the mutation had reached the consumer: packages/core/dist/actions/ActionRunner.d.ts:393 still carried the two-parameter form. app-shell resolves @object-ui/core through dist/index.d.ts → dist/actions/index.d.ts:8 → ActionRunner.js, so the un-rebuilt leg measured the unmutated tree. Rebuilding core and proving the narrowed form present at dist/actions/ActionRunner.d.ts:393 produced the real result.
Predictions were recorded before the run:
#
Predicted
Observed
1
ObjectView.tsx TS2554 "Expected 1 arguments, but got 2"
✅ ObjectView.tsx(1399,13): error TS2554: Expected 1 arguments, but got 2.
2
useConsoleActionRuntime.tsx:190 fails — 2 required params vs 1-param target
✅ TS2345 + TS7006 ×2 (richer than predicted)
3
RecordDetailView.tsx:494no error (its options?: is optional)
✅ absent from output
4
packages/core stays clean
✅ 0 errors, exit 0
Baseline (closure built, unmutated): 0 errors, exit 0.
Ablated: 4 errors, exit 2 — all in packages/app-shell, none in packages/core.
src/hooks/useConsoleActionRuntime.tsx(190,59): error TS2345: Argument of type '(message: any, options: any) =' Promise< boolean > 'is not assignable to parameter of type' ConfirmationHandler'.
src/hooks/useConsoleActionRuntime.tsx(190,60): error TS7006: Parameter 'message' implicitly has an 'any' type.
src/hooks/useConsoleActionRuntime.tsx(190,69): error TS7006: Parameter 'options' implicitly has an 'any' type.
src/views/ObjectView.tsx(1399,13): error TS2554: Expected 1 arguments, but got 2.
Restored: source reverted, core rebuilt on the restore leg too, narrowed form proven absent from dist, app-shell type-check back to 0 errors / exit 0, git status --porcelain empty, git diff origin/main...HEAD empty.
The published-surface question the dispatch asked me to answer
The dispatch asked whether "an implementer who wrote (msg, opts) = … still type-checks
against a one-parameter type" makes removal safe. Measured answer: no — the premise is
half-true, and the half that is false is the common case.
Bare form (message, options) = … (contextually typed, both params required): rejected. A source signature may not have more required parameters than its
target. Observed as TS2345 plus two TS7006 implicit-any errors under noImplicitAny. This is the natural way to write it, and it is what this repo's own useConsoleActionRuntime.tsx:190 does.
Explicitly-optional form (message: string, options?: { … }) = …: accepted —
optional params do not count toward required arity. RecordDetailView.tsx:494 survives.
So the escape hatch exists but is not the default; an external implementer who wrote the
obvious two-parameter handler gets a compile break. @object-ui/core is public
(private unset, v17.6.0, files: ["dist", …]) and the type is reachable from the
package entry through the re-export chain above — so this is a genuine narrowing of a
published .d.ts.
Precedent in this exact file: useConsoleActionRuntime.tsx:82-87 documents
objectui#4040, where a one-parameter declaration was a narrower restatement of a
two-parameter implementation and nothing caught it. Removing options would reintroduce
that same shape deliberately.
Why I stopped
Three independent stop conditions from the dispatch all fired:
Premise falsified (回翻条款) — a live producer exists; stop at zero commits.
"If any consumer reads options … STOP and report" — three do, and one is a
terminal render of localized strings.
File-surface breach — my declared surface is packages/core/src/actions/ActionRunner.ts + its tests. Every compile break lands in packages/app-shell (4 errors, 2 files) and the feature repair would be a third file. packages/core alone stays green, which is exactly why a runner-scoped search read
this as a clean removal.
I did not add a pin test or a "keep and document" comment either — choosing that
resolution would itself be re-ruling the card, which the 回翻条款 forbids.
For the maintainer
The card's option 2 ("a producer returns") is not a future condition — a producer never
left. Suggested re-triage:
Recommended — close as not-a-defect / re-scope to documentation.options is live,
localized and user-reachable. The correct minimal follow-up, if any, is a comment at ActionRunner.ts:462 naming the out-of-runner producer so the next liveness sweep does
not re-derive "no producers" from the runner alone.
Not recommended — remove. Breaks a shipped admin feature's localization, breaks app-shell's compile, and narrows a published .d.ts against implementers who wrote
the natural two-parameter form.
Two secondary observations, recorded rather than filed (both are facets of this card's own
subject, and filing them while the card is in decision would just be noise):
RecordDetailView.tsx:494's handler is fed only by the runner, so itsoptions
is in fact always undefined — the parameter is live on the ObjectView path and
inert on the RecordDetailView path. The card's "dead-in-practice" reading is correct
for exactly one of the two implementations.
useConsoleActionRuntime.tsx:190 and RecordDetailView.tsx:494 are near-duplicate
confirm-runtime implementations feeding the same ActionConfirmDialog.
Branch claude/issue-5205-confirmation-handler-options was pushed as an empty write-route
probe and carries zero commits; no PR opened.
回翻条款 — premise falsified. Moving to needs-user-decision.
PM round 21, session session_01RV6yuVCxymHYE16PL9vQkE. ⛔ Not a ruling — reporting the fork. Zero commits; nothing was changed.
options is not declared-but-never-produced. It is produced outside ActionRunner: packages/app-shell/src/views/ObjectView.tsx:1397 calls confirmHandler(message, {title, confirmText, cancelText}) with all three fields localized through t(), and ActionConfirmDialog.tsx:50/55/58 renders them. The handler is typed ConfirmationHandler at useConsoleActionRuntime.tsx:78, so the published type is invoked with a populated bag. It is reachable UI, not dead code — handleDeleteView is wired at ObjectView.tsx:2472 (onDeleteView={isAdmin ? handleDeleteView : undefined}) and :2486.
Nothing regressed.git blame dates the call and its three i18n keys to 2026-08-10 (5bfaabde0, 36ae2705b4) — before the #4314 ruling (2026-08-17) and before the triage (2026-08-19). The card's and the grading's shared error was scope: the search was runner-scoped, and the producer is one package over. Worth keeping as a method note — "no producer" is only as wide as where you looked.
Removing it would delete a working feature, not inert surface: the delete-view dialog loses its title and both button labels, falling back to generic actionConfirm.* strings; app-shell fails to compile in two files outside the declared surface; and a published .d.ts narrows.
The published-surface question I put to the dev, answered
My dispatch asked whether TypeScript's tolerance of extra parameters makes the removal safe. Half-true, and the false half is the common case: a bare (msg, opts) => … is rejected — two required parameters cannot satisfy a one-parameter target — while only the explicitly-optional (msg, opts?: {…}) form survives. So removal is a genuine breaking narrowing for the natural spelling.
In-file precedent the dev found: useConsoleActionRuntime.tsx:82-87 carries a comment about objectui#4040, where a one-parameter declaration was a narrower restatement of a two-parameter implementation and nothing caught it. Removing options would deliberately reintroduce that shape.
Method note worth keeping — a false green, caught
The dev's first ablation leg edited src only and produced 0 errors. Rather than reading that as a result, it checked reachability and found packages/core/dist/actions/ActionRunner.d.ts:393 still carrying the two-parameter form: app-shell resolves @object-ui/core through dist, so the un-rebuilt leg had measured the unmutated tree. It rebuilt, proved the narrowed form present in dist, and only then re-ran — 4 errors, all in app-shell, none in packages/core. That distribution is exactly why a runner-scoped search reads this as a clean removal.
The fork
The card's two options no longer partition the space: its option 2 ("a producer returns") is not a future condition — a producer never left.
A (dev's recommendation) — keep the parameter, and add a comment at ActionRunner.ts:462 naming the out-of-runner producer so the next liveness sweep does not re-derive "no producers" from the runner alone.
B — remove it anyway: needs a new dispatch covering packages/app-shell, replacement for three localized strings, and a breaking-change changeset. Deletes working localization for no measured benefit.
C — split: RecordDetailView.tsx:494's handler is fed only by the runner, so itsoptions really is always undefined. That half is genuinely inert.
⛔ I am not choosing. Returning unassigned.
Two things the dev deliberately did not file, recording them here instead because both are facets of this card while it is in decision: the RecordDetailView.tsx:494 inert half above, and the fact that useConsoleActionRuntime.tsx:190 and RecordDetailView.tsx:494 are near-duplicate confirm runtimes feeding one dialog — a deduplication candidate whose right shape depends on how you rule here.
Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat, session_01ApyDuQY2fkunMCqXiqvBhR)
The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.
Ruled: Option A — keep the options parameter, with an explanatory comment. The parameter is not declared-but-never-produced: a live, localized producer exists outside the runner (ObjectView.tsx:1397, the admin delete-view confirmation), so removal would delete a working feature and narrow a published .d.ts against the natural two-parameter handler spelling. The deliverable is the comment at ActionRunner.ts:462 naming the out-of-runner producer, so the next liveness sweep does not re-derive "no producers" from a runner-scoped search alone. Option B (remove anyway) is rejected.
State: needs-user-decision removed → pm:queue (ready for dispatch).
Triage: domain:ui. The fix lands in packages/app-shell — ConfirmationHandler's options parameter, declared-but-never-produced after the structured-confirm retirement. Published library source.
Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer of domain:* and may re-grade.
Claim — domain:ui execution seat. PM round 29, session session_01EuPCi56cnGyykygi3z9w4m, branch claude/issue-5205-confirmation-handler-options-comment. Default tier — the ruling is KEEP, so nothing narrows.
Executing the 2026-08-22 maintainer ruling (comment on this card): Option A — keep the options parameter and add the explanatory comment. Option B (remove it) is rejected and ⛔ is not re-opened.
The reason is the whole deliverable, so it must survive into the comment: the parameter is not declared-but-never-produced. A live, localized producer exists outside the runner — ObjectView.tsx:1397, the admin delete-view confirmation. Removing it would delete a working feature and narrow a published .d.ts against the natural two-parameter handler spelling.
The deliverable is the comment at ActionRunner.ts:462naming that out-of-runner producer, precisely so the next liveness sweep does not re-derive "no producers" from a runner-scoped search alone. ⚠️ That is the failure this card exists to prevent — a comment that merely says "keep this" would not prevent it.
{
"issue": 5205,
"status": "done",
"branch": "claude/issue-5205-confirmation-handler-options-comment",
"pr": "https://github.com/objectstack-ai/objectui/pull/5833",
"premise_still_valid": false,
"summary": "The card's own premise is false and that is by design here: the 2026-08-22 maintainer ruling already established it, and executing the ruling was the task. Re-verified the ruling's premise on origin/main @ 5da008463 before writing anything: the live producer exists (handleDeleteView in packages/app-shell/src/views/ObjectView.tsx invokes a ConfirmationHandler with title/confirmText/cancelText all populated through t(), the three keys are present in all 8 packages/i18n/src/locales bundles, ActionConfirmDialog.tsx renders them over its actionConfirm.* fallbacks, and the path is reachable admin UI via onDeleteView={isAdmin ? handleDeleteView : undefined} and onDelete={handleDeleteView}). Added a doc comment above the ConfirmationHandler declaration at ActionRunner.ts:462 that names that producer, says why a runner-scoped sweep sees zero producers, states what removal would cost (the dialog's title and both button labels, plus narrowing a published .d.ts against the natural two-parameter spelling), and tells the next sweep to grep the repo rather than this package. The parameter, the handler type, ActionConfirmDialog.tsx and ObjectView.tsx are untouched. NOTE for the next reader: the card's pointer had already drifted -- ObjectView.tsx:1397 is :1520 on today's main -- so the comment deliberately carries NO line numbers and anchors on the symbol name, the file and the i18n keys instead.",
"tests": "Gate union re-run after the final commit, at HEAD 5af910c80, from the repo root under the shared verify lock, with `set -o pipefail`; verdict lines quoted from the gates themselves, not from a bare $?. (1) node scripts/check-changeset-presence.mjs -> '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/tidy-jars-confirm.md.' Empty frontmatter, i.e. declared as releasing nothing -- the gate's own explicit exemption; objectui has no skip-changeset label, so this is the declaration route. First run of this gate was RED ('adds no changeset') before the changeset was written. (2) node scripts/check-changeset-no-major.mjs -> 'No changeset declares a `major` bump.' (3) node scripts/check-control-bytes.mjs -> 'check-control-bytes: OK (scanned 4856 tracked text file(s); skipped 85 binary).' plus a manual grep -naP for control bytes over the edited file: none. (4) pnpm --workspace-concurrency=2 --filter @object-ui/core type-check -> clean, exit 0, with the script echoed ('tsc --noEmit && tsc -p tsconfig.test.json') so this is not a zero-match silent pass. The FIRST run was red with 41 errors, all TS6305 'packages/types/dist/index.d.ts has not been built' plus implicit-any fallout -- an unbuilt dependency closure, not the diff; re-run green after pnpm --workspace-concurrency=2 --filter '@object-ui/core^...' build. (5) pnpm --workspace-concurrency=2 --filter @object-ui/core lint -> '510 problems (0 errors, 510 warnings)', exit 0; all warnings are pre-existing no-explicit-any. This is the package's own eslint task, i.e. exactly what `turbo run lint` (= pnpm lint) schedules for the one package this diff touches, and the repo's eslint.config.js enables no type-aware linting (no `project:`/`projectService`), so a comment-only diff cannot move any judgment in a package it does not touch. (6) pnpm exec vitest run packages/core/src/actions/__tests__/ActionRunner.test.ts packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.test.tsx --maxWorkers=2 -> 'Test Files 2 passed (2)' / 'Tests 129 passed (129)'. Run from the repo root per the dispatch, never via pnpm --filter. NO ABLATION: the change is a comment with no runtime or type behaviour to mutate, so there is nothing an ablation could turn red -- reporting that rather than staging a decorative one. The producer claim was instead verified directly against origin/main source (greps and call-site reads listed in the summary), and the ruling's own reverse-verification from round 21 already stands on the record.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #5835: RecordDetailView carries a second confirm runtime (inline options shape rather than the published ConfirmationHandler type) that duplicates useConsoleActionRuntime's, and on that path options really is always undefined -- both facts were noted in this card's thread and would have left the tracker when it closes; labelled `finding`, unassigned, no pm:queue"
]
}
Activity
os-warren commentedon Aug 19, 2026
Concentrated finding round #4 — first-touch grading (triage seat, session
session_01JEhkB6ShK2HHDzozy7Qwgz): promoted topm:queue, with a Clause-② flag for the dispatching seat.Verified on
origin/main@12841b6: the optionaloptionsbag is declared atpackages/core/src/actions/ActionRunner.ts:462-466; the sole runner call site at:988-990passes one argument (adjacent comment cites the #4314 retirement, landed as PR #5209); the dead-in-practice readers arepackages/app-shell/src/views/ActionConfirmDialog.tsx:58andpackages/app-shell/src/hooks/useConsoleActionRuntime.tsx:190.Why queue, not decision: the maintainer already ruled the structured-confirm retirement (#4314, 2026-08-17); this vestigial parameter was deferred for a scheduling reason (app-shell held by another session), not a product one — completing a standing ruling is dispatchable. #4314's reopen condition survives this removal.
@object-ui/coremakes existing two-parameter handler implementations fail to type-check — a breaking change on the compile contract of a published package (no runtime accept/reject change). The claim comment must declareClause-②: yesper the intake gate, the PR needs a breaking-annotation changeset, and the dev should sweep the consumer radius. Dispatch must also re-check session holds onpackages/app-shellfirst, per the body. Serial: ActionRunner.ts churned twice this cycle (#5209, #5218), both merged; no open PR. Size suggestion: S–M, opus.Generated by Claude Code
claude commentedon Aug 21, 2026
Claim: PM loop round 21
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-5205-confirmation-handler-optionsWorktree:
objectui-issue-5205Domain:
repo:objectuiFile surface:
packages/core/src/actions/ActionRunner.ts+ its tests (stop on breach; explain in the report)Container & model:
M,mode:subagentClause-②: yes — triage flagged this for the dispatching seat, and I am carrying the flag forward rather than resolving it:
ConfirmationHandleris the public handler type consumers pass tosetConfirmHandler, so narrowing its signature is a published-surface change. The dispatch requires the dev to measure the consumer population before removing anything and to stop and report if any external shape depends on it.Serial constraints cleared:⚠️ PR #5478 touches
packages/core/src/utils/column-identity.ts— a different file, and it is in the merge queue. No other in-flight card holdspackages/core/src/actions/**.⛔ Governed-surface check run before dispatch: the declared surface hits none of
AGENTS.md/CLAUDE.md/.claude/**/docs/adr/**/skills/**. If the work turns out to need one, stop and report — publishedskills/**ownership is an open question with the maintainer and no seat may land it.Generated by Claude Code
claude commentedon Aug 21, 2026
os-dev-report
Status:
needs_decision— STOPPED AT ZERO COMMITS. The measurement the dispatchordered contradicts this card's central premise, so the 回翻条款 applies: I report the
fork rather than re-rule it.
The premise is false:
optionshas a live producerThe card states "The only producer of that argument was the structured
confirmarm'sforwarding inside ActionRunner's precedence read" and concludes "With no producer,
optionsis now alwaysundefinedat runtime."That is true only inside
ActionRunner. It is false for the repo. A second producerhas existed all along, and it bypasses the runner entirely:
packages/app-shell/src/views/ObjectView.tsx:1397That
confirmHandleris destructured atObjectView.tsx:911fromuseConsoleActionRuntime, whoseConsoleActionRuntime.confirmHandleris declaredConfirmationHandleratuseConsoleActionRuntime.tsx:78. So this is the published typebeing invoked with a fully-populated, fully-localized options bag.
It is reachable UI, not dead code: the enclosing
handleDeleteView(
ObjectView.tsx:1389) is wired atObjectView.tsx:2472(
onDeleteView={isAdmin ? handleDeleteView : undefined}) and:2486(
onDelete={handleDeleteView}) — the admin "delete saved view" confirmation.git blame: the call and its three localized keys date to 2026-08-10 (5bfaabde0,36ae2705b4) — they predate both the #4314 ruling (2026-08-17) and the triage(2026-08-19). Nothing regressed; the search was simply scoped to the runner.
Consequence: removing
optionsdoes not delete inert surface. It silently deletes aworking feature — the delete-view dialog would lose its title and both button labels and
fall back to the generic
actionConfirm.*strings.Consumer census (with counter-probes)
optionscore/.../ActionRunner.ts:988app-shell/.../ObjectView.tsx:1397app-shell/.../useConsoleActionRuntime.tsx:190(message, options)setConfirmState:192app-shell/.../RecordDetailView.tsx:494(message, options?: …)setConfirmState:496app-shell/.../ActionConfirmDialog.tsx:50,55,58title,cancelText,confirmTextreact/.../useActionRunner.ts:26,react/.../ActionContext.tsx:35onConfirm?: ConfirmationHandlercomponents/.../page-header-action-i18n.test.tsx:83Counter-probe A (method sensitivity). Same grep applied to
ToastHandler— a siblingwhose options bag is known-live — finds its two-argument producers at
ActionRunner.ts:1136and:1151. The method detects two-argument calls; thenon-zero result above is not a search artifact.
Counter-probe B (positive control on the same lines).
message— indisputably live —travels through identical syntax at the identical call sites:
setConfirmState({ open: true, message, options, resolve })at:192and:496,rendered at
ActionConfirmDialog.tsx:51(message) beside:50/:55/:58(options).There is no methodological asymmetry that could make
messagereal andoptionsspurious — they are read by the same component from the same state object.
Reverse-verification (ablation)
Narrowed the type to
(message: string) =Promise< boolean >inpackages/core/src/actions/ActionRunner.ts, rebuilt@object-ui/core, type-checked.srconly and got 0 errors. Beforereading that as a result I checked whether the mutation had reached the consumer:
packages/core/dist/actions/ActionRunner.d.ts:393still carried the two-parameter form.app-shellresolves@object-ui/corethroughdist/index.d.ts→dist/actions/index.d.ts:8→ActionRunner.js, so the un-rebuilt leg measured theunmutated tree. Rebuilding
coreand proving the narrowed form present atdist/actions/ActionRunner.d.ts:393produced the real result.Predictions were recorded before the run:
ObjectView.tsxTS2554 "Expected 1 arguments, but got 2"ObjectView.tsx(1399,13): error TS2554: Expected 1 arguments, but got 2.useConsoleActionRuntime.tsx:190fails — 2 required params vs 1-param targetTS2345+TS7006×2 (richer than predicted)RecordDetailView.tsx:494no error (itsoptions?:is optional)packages/corestays cleanBaseline (closure built, unmutated): 0 errors, exit 0.
Ablated: 4 errors, exit 2 — all in
packages/app-shell, none inpackages/core.Restored: source reverted,
corerebuilt on the restore leg too, narrowed form provenabsent from
dist,app-shelltype-check back to 0 errors / exit 0,git status --porcelainempty,git diff origin/main...HEADempty.The published-surface question the dispatch asked me to answer
The dispatch asked whether "an implementer who wrote
(msg, opts) =… still type-checksagainst a one-parameter type" makes removal safe. Measured answer: no — the premise is
half-true, and the half that is false is the common case.
(message, options) =… (contextually typed, both params required):rejected. A source signature may not have more required parameters than its
target. Observed as
TS2345plus twoTS7006implicit-any errors undernoImplicitAny. This is the natural way to write it, and it is what this repo's ownuseConsoleActionRuntime.tsx:190does.(message: string, options?: { … }) =…: accepted —optional params do not count toward required arity.
RecordDetailView.tsx:494survives.So the escape hatch exists but is not the default; an external implementer who wrote the
obvious two-parameter handler gets a compile break.
@object-ui/coreis public(
privateunset,v17.6.0,files: ["dist", …]) and the type is reachable from thepackage entry through the re-export chain above — so this is a genuine narrowing of a
published
.d.ts.Precedent in this exact file:
useConsoleActionRuntime.tsx:82-87documentsobjectui#4040, where a one-parameter declaration was a narrower restatement of a
two-parameter implementation and nothing caught it. Removing
optionswould reintroducethat same shape deliberately.
Why I stopped
Three independent stop conditions from the dispatch all fired:
options… STOP and report" — three do, and one is aterminal render of localized strings.
packages/core/src/actions/ActionRunner.ts+ its tests. Every compile break lands inpackages/app-shell(4 errors, 2 files) and the feature repair would be a third file.packages/corealone stays green, which is exactly why a runner-scoped search readthis as a clean removal.
I did not add a pin test or a "keep and document" comment either — choosing that
resolution would itself be re-ruling the card, which the 回翻条款 forbids.
For the maintainer
The card's option 2 ("a producer returns") is not a future condition — a producer never
left. Suggested re-triage:
optionsis live,localized and user-reachable. The correct minimal follow-up, if any, is a comment at
ActionRunner.ts:462naming the out-of-runner producer so the next liveness sweep doesnot re-derive "no producers" from the runner alone.
app-shell's compile, and narrows a published.d.tsagainst implementers who wrotethe natural two-parameter form.
Two secondary observations, recorded rather than filed (both are facets of this card's own
subject, and filing them while the card is in decision would just be noise):
RecordDetailView.tsx:494's handler is fed only by the runner, so itsoptionsis in fact always
undefined— the parameter is live on theObjectViewpath andinert on the
RecordDetailViewpath. The card's "dead-in-practice" reading is correctfor exactly one of the two implementations.
useConsoleActionRuntime.tsx:190andRecordDetailView.tsx:494are near-duplicateconfirm-runtime implementations feeding the same
ActionConfirmDialog.Branch
claude/issue-5205-confirmation-handler-optionswas pushed as an empty write-routeprobe and carries zero commits; no PR opened.
Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 21, 2026
回翻条款 — premise falsified. Moving to
needs-user-decision.PM round 21, session
session_01RV6yuVCxymHYE16PL9vQkE. ⛔ Not a ruling — reporting the fork. Zero commits; nothing was changed.optionsis not declared-but-never-produced. It is produced outside ActionRunner:packages/app-shell/src/views/ObjectView.tsx:1397callsconfirmHandler(message, {title, confirmText, cancelText})with all three fields localized throught(), andActionConfirmDialog.tsx:50/55/58renders them. The handler is typedConfirmationHandleratuseConsoleActionRuntime.tsx:78, so the published type is invoked with a populated bag. It is reachable UI, not dead code —handleDeleteViewis wired atObjectView.tsx:2472(onDeleteView={isAdmin ? handleDeleteView : undefined}) and:2486.Nothing regressed.
git blamedates the call and its three i18n keys to 2026-08-10 (5bfaabde0,36ae2705b4) — before the #4314 ruling (2026-08-17) and before the triage (2026-08-19). The card's and the grading's shared error was scope: the search was runner-scoped, and the producer is one package over. Worth keeping as a method note — "no producer" is only as wide as where you looked.Removing it would delete a working feature, not inert surface: the delete-view dialog loses its title and both button labels, falling back to generic
actionConfirm.*strings; app-shell fails to compile in two files outside the declared surface; and a published.d.tsnarrows.The published-surface question I put to the dev, answered
My dispatch asked whether TypeScript's tolerance of extra parameters makes the removal safe. Half-true, and the false half is the common case: a bare
(msg, opts) => …is rejected — two required parameters cannot satisfy a one-parameter target — while only the explicitly-optional(msg, opts?: {…})form survives. So removal is a genuine breaking narrowing for the natural spelling.In-file precedent the dev found:
useConsoleActionRuntime.tsx:82-87carries a comment about objectui#4040, where a one-parameter declaration was a narrower restatement of a two-parameter implementation and nothing caught it. Removingoptionswould deliberately reintroduce that shape.Method note worth keeping — a false green, caught
The dev's first ablation leg edited
srconly and produced 0 errors. Rather than reading that as a result, it checked reachability and foundpackages/core/dist/actions/ActionRunner.d.ts:393still carrying the two-parameter form: app-shell resolves@object-ui/corethroughdist, so the un-rebuilt leg had measured the unmutated tree. It rebuilt, proved the narrowed form present indist, and only then re-ran — 4 errors, all in app-shell, none inpackages/core. That distribution is exactly why a runner-scoped search reads this as a clean removal.The fork
The card's two options no longer partition the space: its option 2 ("a producer returns") is not a future condition — a producer never left.
ActionRunner.ts:462naming the out-of-runner producer so the next liveness sweep does not re-derive "no producers" from the runner alone.packages/app-shell, replacement for three localized strings, and a breaking-change changeset. Deletes working localization for no measured benefit.RecordDetailView.tsx:494's handler is fed only by the runner, so itsoptionsreally is always undefined. That half is genuinely inert.⛔ I am not choosing. Returning unassigned.
Two things the dev deliberately did not file, recording them here instead because both are facets of this card while it is in decision: the
RecordDetailView.tsx:494inert half above, and the fact thatuseConsoleActionRuntime.tsx:190andRecordDetailView.tsx:494are near-duplicate confirm runtimes feeding one dialog — a deduplication candidate whose right shape depends on how you rule here.Generated by Claude Code
huangyiirene commentedon Aug 22, 2026
Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat,
session_01ApyDuQY2fkunMCqXiqvBhR)The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.
Ruled: Option A — keep the
optionsparameter, with an explanatory comment. The parameter is not declared-but-never-produced: a live, localized producer exists outside the runner (ObjectView.tsx:1397, the admin delete-view confirmation), so removal would delete a working feature and narrow a published.d.tsagainst the natural two-parameter handler spelling. The deliverable is the comment atActionRunner.ts:462naming the out-of-runner producer, so the next liveness sweep does not re-derive "no producers" from a runner-scoped search alone. Option B (remove anyway) is rejected.State:
needs-user-decisionremoved →pm:queue(ready for dispatch).Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
Triage:
domain:ui. The fix lands inpackages/app-shell—ConfirmationHandler'soptionsparameter, declared-but-never-produced after the structured-confirm retirement. Published library source.objectui three-stream split (maintainer 2026-08-21):
domain:devxengineering surface ·domain:speccontract surface ·domain:uieverything else (published libraries and apps).Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session
session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer ofdomain:*and may re-grade.⛔ pm-state, assignee and scope untouched.
Generated by Claude Code
claude commentedon Aug 23, 2026
Claim —
domain:uiexecution seat. PM round 29, sessionsession_01EuPCi56cnGyykygi3z9w4m, branchclaude/issue-5205-confirmation-handler-options-comment. Default tier — the ruling is KEEP, so nothing narrows.Executing the 2026-08-22 maintainer ruling (comment on this card): Option A — keep the
optionsparameter and add the explanatory comment. Option B (remove it) is rejected and ⛔ is not re-opened.The reason is the whole deliverable, so it must survive into the comment: the parameter is not declared-but-never-produced. A live, localized producer exists outside the runner —
ObjectView.tsx:1397, the admin delete-view confirmation. Removing it would delete a working feature and narrow a published.d.tsagainst the natural two-parameter handler spelling.The deliverable is the comment at⚠️ That is the failure this card exists to prevent — a comment that merely says "keep this" would not prevent it.
ActionRunner.ts:462naming that out-of-runner producer, precisely so the next liveness sweep does not re-derive "no producers" from a runner-scoped search alone.Generated by Claude Code
docs(core): name the out-of-runner producer of ConfirmationHandler's …
os-zhuang commentedon Aug 23, 2026
{ "issue": 5205, "status": "done", "branch": "claude/issue-5205-confirmation-handler-options-comment", "pr": "https://github.com/objectstack-ai/objectui/pull/5833", "premise_still_valid": false, "summary": "The card's own premise is false and that is by design here: the 2026-08-22 maintainer ruling already established it, and executing the ruling was the task. Re-verified the ruling's premise on origin/main @ 5da008463 before writing anything: the live producer exists (handleDeleteView in packages/app-shell/src/views/ObjectView.tsx invokes a ConfirmationHandler with title/confirmText/cancelText all populated through t(), the three keys are present in all 8 packages/i18n/src/locales bundles, ActionConfirmDialog.tsx renders them over its actionConfirm.* fallbacks, and the path is reachable admin UI via onDeleteView={isAdmin ? handleDeleteView : undefined} and onDelete={handleDeleteView}). Added a doc comment above the ConfirmationHandler declaration at ActionRunner.ts:462 that names that producer, says why a runner-scoped sweep sees zero producers, states what removal would cost (the dialog's title and both button labels, plus narrowing a published .d.ts against the natural two-parameter spelling), and tells the next sweep to grep the repo rather than this package. The parameter, the handler type, ActionConfirmDialog.tsx and ObjectView.tsx are untouched. NOTE for the next reader: the card's pointer had already drifted -- ObjectView.tsx:1397 is :1520 on today's main -- so the comment deliberately carries NO line numbers and anchors on the symbol name, the file and the i18n keys instead.", "tests": "Gate union re-run after the final commit, at HEAD 5af910c80, from the repo root under the shared verify lock, with `set -o pipefail`; verdict lines quoted from the gates themselves, not from a bare $?. (1) node scripts/check-changeset-presence.mjs -> '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/tidy-jars-confirm.md.' Empty frontmatter, i.e. declared as releasing nothing -- the gate's own explicit exemption; objectui has no skip-changeset label, so this is the declaration route. First run of this gate was RED ('adds no changeset') before the changeset was written. (2) node scripts/check-changeset-no-major.mjs -> 'No changeset declares a `major` bump.' (3) node scripts/check-control-bytes.mjs -> 'check-control-bytes: OK (scanned 4856 tracked text file(s); skipped 85 binary).' plus a manual grep -naP for control bytes over the edited file: none. (4) pnpm --workspace-concurrency=2 --filter @object-ui/core type-check -> clean, exit 0, with the script echoed ('tsc --noEmit && tsc -p tsconfig.test.json') so this is not a zero-match silent pass. The FIRST run was red with 41 errors, all TS6305 'packages/types/dist/index.d.ts has not been built' plus implicit-any fallout -- an unbuilt dependency closure, not the diff; re-run green after pnpm --workspace-concurrency=2 --filter '@object-ui/core^...' build. (5) pnpm --workspace-concurrency=2 --filter @object-ui/core lint -> '510 problems (0 errors, 510 warnings)', exit 0; all warnings are pre-existing no-explicit-any. This is the package's own eslint task, i.e. exactly what `turbo run lint` (= pnpm lint) schedules for the one package this diff touches, and the repo's eslint.config.js enables no type-aware linting (no `project:`/`projectService`), so a comment-only diff cannot move any judgment in a package it does not touch. (6) pnpm exec vitest run packages/core/src/actions/__tests__/ActionRunner.test.ts packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.test.tsx --maxWorkers=2 -> 'Test Files 2 passed (2)' / 'Tests 129 passed (129)'. Run from the repo root per the dispatch, never via pnpm --filter. NO ABLATION: the change is a comment with no runtime or type behaviour to mutate, so there is nothing an ablation could turn red -- reporting that rather than staging a decorative one. The producer claim was instead verified directly against origin/main source (greps and call-site reads listed in the summary), and the ruling's own reverse-verification from round 21 already stands on the record.", "open_questions": [], "out_of_scope_findings": [ "filed as #5835: RecordDetailView carries a second confirm runtime (inline options shape rather than the published ConfirmationHandler type) that duplicates useConsoleActionRuntime's, and on that path options really is always undefined -- both facts were noted in this card's thread and would have left the tracker when it closes; labelled `finding`, unassigned, no pm:queue" ] }Generated by Claude Code
Generated by Claude Code
docs(core): name the out-of-runner producer of ConfirmationHandler's …