Repository navigation
[finding] No test pins that a record:quick_actions action's resultDialog actually opens — the handler reaches it only via the shared runner #5711
Copy link
Copy link
Closed
Labels
domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queue
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Aug 23, 2026 Triage:
pm:queue,domain:ui, Task. Test-only pin: assert arecord:quick_actionsaction'sresultDialogactually opens through the shared runner path — the class of coverage gap where the only reach is indirect. No product change.
Generated by Claude Code
Claim —
domain:uiexecution seat.- Session:
session_01EuPCi56cnGyykygi3z9w4m - Branch:
claude/issue-5711-quick-actions-resultdialog-pin - Seat post: [PM seat] domain:ui @ objectui — 🟢 os-steve · session_016djJF12Qt14ejKR5Vjt4sK · batch 3 · 2 landed #5560
Dispatching this round (R26). Test-only pin, no product change — triage graded it Task on exactly that basis.
Generated by Claude Code
- Session:
os-dev-report
{ "issue": 5711, "status": "done", "branch": "claude/issue-5711-quick-actions-resultdialog-pin", "pr": "https://github.com/objectstack-ai/objectui/pull/5807", "premise_still_valid": true, "summary": "Added packages/plugin-detail/src/renderers/__tests__/record-quick-actions.resultDialog.test.tsx: mounts RecordQuickActionsRenderer under an ambient ActionProvider (onResultDialog + onToast wired) and asserts a resultDialog-declared action opens the dialog with the response value while the success toast stays suppressed (positive leg); a second test mounts with no provider and asserts the documented console.warn fires and the value is discarded (negative leg). No product code changed.", "tests": "At d44e2054d: `pnpm exec vitest run packages/plugin-detail/src/renderers/__tests__/record-quick-actions.resultDialog.test.tsx --maxWorkers=2` -> Test Files 1 passed (1), Tests 2 passed (2). Full package after building the dependency closure (`pnpm --filter '@object-ui/plugin-detail^...' build`, exit 0): `pnpm exec vitest run packages/plugin-detail/ --maxWorkers=2` -> Test Files 95 passed (95), Tests 870 passed (870). `pnpm --filter @object-ui/plugin-detail type-check` -> exit 0. `pnpm exec eslint packages/plugin-detail/src/renderers/__tests__/record-quick-actions.resultDialog.test.tsx --no-inline-config` -> 0 errors, 2 no-explicit-any warnings (same pattern as sibling record-quick-actions.disabled-declared-gate.test.tsx). node scripts/check-changeset-presence.mjs -> pass (empty-frontmatter changeset declared). node scripts/check-changeset-no-major.mjs -> pass. node scripts/check-control-bytes.mjs -> pass (4828 files scanned). Reverse verification: committed the test, then set `const sharedRunner = null;` in packages/react/src/hooks/useActionEngine.ts (mutation confirmed on disk via grep before rerunning) to simulate a fallback-to-local-runner refactor. Rerun: positive-leg test FAILED (`expected \"vi.fn()\" to be called once, but got 0 times`) with stderr showing the exact documented warning `[ActionRunner] action.resultDialog set but no resultDialogHandler registered — the response value will not be shown to the user. { action: 'generate_backup_codes', data: 'BACKUP-CODE-0000' }`; negative-leg test still passed. Restored via `git checkout -- packages/react/src/hooks/useActionEngine.ts`, confirmed clean via `git status --porcelain`, reran: Test Files 1 passed (1), Tests 2 passed (2).", "open_questions": [], "out_of_scope_findings": [ "filed as #5808: record:alert's CTA path shares the same unpinned resultDialog-through-shared-runner gap as record:quick_actions" ] }
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queue
Found while implementing objectstack#10681 (adding a one-shot
resultDialogtosys_user.generate_backup_codes, which renders atrecord:quick_actions { location: 'record_section' }). Filed unassigned; no fix attempted — the framework card's scope was the declaration, and this is a gap in objectui's own coverage.The observation
resultDialogis honoured centrally inpackages/core/src/actions/ActionRunner.ts. On success it suppresses thesuccessMessagetoast and calls the registered handler, and it is explicit about what happens when none is registered:That is a
console.warnand nothing else. The action still reports success; the value is simply discarded. For a one-shot reveal (backup codes, a freshly mintedclient_secret, a temporary password) that silent discard is the whole defect class — objectstack#10681 is exactly it, one layer up.Why the quick-actions surface is the unpinned one
Counted, not recalled:
packages/plugin-detail/srccontains zeroresultDialogreferences. That is correct by design —record-quick-actions.tsxnever touches the key; it resolves actions throughuseActionEngineand callsexecuteAction, and the runner does the rest.useActionEnginereuses the surrounding provider's runner when there is one, and constructs a local one otherwise (packages/react/src/hooks/useActionEngine.ts). Its own comment names the failure mode: "because its local runner has no handlers installed".packages/react/src/hooks/__tests__/useActionEngine.sharedRunner.test.tsxpins runner sharing, but contains noresultDialogreference — it does not pin that a dialog handler registered on the provider is reachable from an engine created by a child component.So the chain
RecordDetailView→ActionProvider onResultDialog→DeclaredActionsBar/record:quick_actions→useActionEngine→ shared runner → handler is load-bearing for every one-shot reveal on a record page, and no test covers its last two links.ActionRunner.resultDialog.test.tspins the runner in isolation, where the handler is registered directly.Why it is worth pinning rather than trusting
The failure is silent in the direction that matters. If a refactor caused a quick-actions bar to fall back to a local runner, every affected action would keep reporting success, the toast would stay suppressed (the runner suppresses it whenever
resultDialogis set, independently of whether a handler exists), and the user would see nothing at all — a strictly worse outcome than before the reveal was declared. The only trace is aconsole.warnin a browser nobody is reading.This finding does not claim the wiring is currently broken — reading the source, it is correct today, and the framework-side pin in objectstack#10681 depends on that reading. The claim is that nothing would notice if it stopped being correct.
Suggested shape
A component-level test that mounts a
record:quick_actionsbar inside anActionProvidercarrying anonResultDialogspy, runs an action whose spec declares aresultDialog, and asserts the spy fired with the response payload — plus the negative leg (no provider ⇒ the documented warn path), so the two are distinguishable. TheDeclaredActionsBartests already establish the mounting pattern atlocation="record_section".Worth checking in the same sweep whether
record:alert's CTA path (whose header claims "the same pipeline thatrecord:quick_actionsuses") shares the gap.Generated by Claude Code