Summary
Several tool actions still wrap async work with fire-and-forget handlers such as onClick: () => { void verify() } or onClick: () => void copyOutput(). This bypasses the shared ToolActionBar pending/success/failure semantics.
ToolActionBar only treats an action as pending when action.onClick() returns a Promise. If the handler starts async work and returns undefined, the toolbar immediately marks the action as successful and announces success to assistive technology, even if the actual async operation is still running, fails later, or does nothing because required input is missing.
Why this matters
The recent action-state audit standardized pending/success/failure feedback. Fire-and-forget handlers undermine that standardization:
- screen-reader users can hear a success announcement before verification/copy work completes;
- pending state is skipped;
- failures are not reflected in the shared toolbar state;
- analytics/action feedback can report a run even when validation short-circuits;
- disabled/empty-input states become inconsistent across tools.
Current behavior
ToolActionBar does this:
constmaybeResult=action.onClick()if(!isPromiseLike(maybeResult)){setLastActionStatus(... "success")setActionAnnouncement(getResultAnnouncement(...))return}So any action that returns undefined is treated as a completed success.
Known examples:
src/features/tools/jwt-verifier/page.tsxonClick: () => { void verify() }verify() is async and may perform Web Crypto work.
src/features/tools/oauth-jwks-workbench/page.tsx- copy action uses
onClick: () => void copyOutput(). - copy may fail, but the toolbar receives
undefined immediately.
There may be additional occurrences; search for fire-and-forget patterns around ToolAction definitions.
Expected behavior
Tool actions should return one of:
void only for genuinely synchronous state updates that cannot fail;ToolActionResult for synchronous success/failure;Promise<void | ToolActionResult> for async actions.
Async tool work should not be hidden behind void wrappers.
Suggested implementation plan
- Update known actions:
onClick: verify
onClick: copyOutput
- Where an async function currently only shows toast, make it return
ToolActionResult or use shared helpers such as copyTextWithToolFeedback(). - Add disabled conditions and disabled reasons for required input:
- JWT verifier Verify should be disabled when token is empty.
- HMAC verification should guide the user when secret is empty for HS algorithms.
- Add/extend a guard to detect
onClick: () => void ... inside ToolAction arrays unless explicitly allowlisted. - Add component tests:
- JWT verifier Verify enters pending state or at least does not announce success before async verification resolves.
- Copy failure returns failed state through the shared toolbar.
- Empty required input does not produce a false success announcement.
Acceptance criteria
Related code pointers
src/features/tool-shell/tool-action-bar.tsxsrc/features/tools/jwt-verifier/page.tsxsrc/features/tools/oauth-jwks-workbench/page.tsxsrc/features/tool-shell/tool-action-feedback.ts
Summary
Several tool actions still wrap async work with fire-and-forget handlers such as
onClick: () => { void verify() }oronClick: () => void copyOutput(). This bypasses the sharedToolActionBarpending/success/failure semantics.ToolActionBaronly treats an action as pending whenaction.onClick()returns a Promise. If the handler starts async work and returnsundefined, the toolbar immediately marks the action as successful and announces success to assistive technology, even if the actual async operation is still running, fails later, or does nothing because required input is missing.Why this matters
The recent action-state audit standardized pending/success/failure feedback. Fire-and-forget handlers undermine that standardization:
Current behavior
ToolActionBardoes this:So any action that returns
undefinedis treated as a completed success.Known examples:
src/features/tools/jwt-verifier/page.tsxonClick: () => { void verify() }verify()is async and may perform Web Crypto work.src/features/tools/oauth-jwks-workbench/page.tsxonClick: () => void copyOutput().undefinedimmediately.There may be additional occurrences; search for fire-and-forget patterns around
ToolActiondefinitions.Expected behavior
Tool actions should return one of:
voidonly for genuinely synchronous state updates that cannot fail;ToolActionResultfor synchronous success/failure;Promise<void | ToolActionResult>for async actions.Async tool work should not be hidden behind
voidwrappers.Suggested implementation plan
ToolActionResultor use shared helpers such ascopyTextWithToolFeedback().onClick: () => void ...inside ToolAction arrays unless explicitly allowlisted.Acceptance criteria
voidwrappers without an explicit allowlist and justification.npm run test -- tests/component/tool-shell-status-output.test.tsx tests/component/jwt-verifier-page.test.tsx tests/component/oauth-jwks-workbench-page.test.tsxpasses if updated.npm run check:typesandnpm run lintpass.Related code pointers
src/features/tool-shell/tool-action-bar.tsxsrc/features/tools/jwt-verifier/page.tsxsrc/features/tools/oauth-jwks-workbench/page.tsxsrc/features/tool-shell/tool-action-feedback.ts