Skip to content

fix(web): focus the primary action when confirmation dialogs open - #18187

Open
rgrvlsk wants to merge 2 commits into
pingdotgg:mainfrom
rgrvlsk:fix/dialog-initial-focus
Open

rgrvlsk wants to merge 2 commits into
pingdotgg:mainfrom
rgrvlsk:fix/dialog-initial-focus

Conversation

@rgrvlsk

@rgrvlsk rgrvlsk commented Oct 11, 2026 •

Copy link
Copy Markdown

Replaces #18176, which was closed for missing evidence. The before/after screenshots and the recording are now included below; the code is unchanged.

Problem

Confirmation dialogs focus Cancel when they open, so Enter cancels even though Confirm is the highlighted action. The desktop update prompt is the clearest case. Fixes #18167.

Repro: wait for the desktop update prompt ("Install update … and restart T3 Code?") and press Enter. The dialog closes as if Cancel was pressed.

Change

  • New DialogAction (also exported as AlertDialogAction) in components/ui: a Button that marks itself as the dialog's primary action. Its variant prop excludes the destructive Button variants at compile time, so a destructive action can't take focus by mistake; destructive confirms keep a plain Button.
  • DialogPopup and AlertDialogPopup pass Base UI's initialFocus and focus that action when they open. Enter and Space then confirm it with native button behavior, Esc cancels, and Tab reaches Cancel.
  • If there is no enabled primary action, Base UI's default focus is unchanged: destructive dialogs keep focus on Cancel, and so do dialogs whose action is disabled. Dialogs that don't use the new component behave exactly as today.
  • Touch opens still focus the popup itself, as Base UI does, so no virtual keyboard opens. A caller's own initialFocus still wins.
  • ConfirmDialogHost is shared by every requestConfirmDialog / dialogs.confirm caller, so it stays Cancel-first by default. A new optional initialFocus: "confirm" | "cancel" on ConfirmDialogOptions (packages/contracts, a local-shell TS type; nothing crosses the wire) lets a caller opt in, and destructive confirms keep Cancel even then. Only the desktop and server update prompts opt in. Every other host confirm (MCP tool approvals, checkpoint rollback, provider sign-out, archive, …) behaves exactly as today.
  • Other call sites: relay client install, and the browser import "quit" and "try again" steps. One-line Button to action swaps, no visual change. None of these dialogs has inputs before the footer that the new focus would skip.
  • Surfaces: web and desktop share these components. Mobile (apps/mobile) is React Native and doesn't use them.

Scope

Follows the direction approved in #18167 (comment): Base UI initialFocus in the two dialog primitives plus targeted call sites, no Enter interceptor.

  • Destructive confirms are untouched and keep focus on Cancel. That includes "remove a hub", which the issue used as an example but is variant="destructive".
  • Focus on the shared confirm host is opt-in rather than inferred from the variant, so consent prompts such as MCP tool approvals never become Enter-to-approve as a side effect (raised by CodeRabbit's security review).
  • PR stack merge/rebase and "Use a reset credit" are left out on purpose: both are hard to undo, so they keep today's behavior (Cancel focused). Whether they should be marked destructive, or let the action take focus, is a product call; happy to follow up either way.
  • Other non-destructive confirms (branch switch/revert, connections, theme) are also left for a follow-up if you want them, as is the macOS "Return always runs the default button" layer.

Verification

  • Automated: vp test run on ui/dialog.test.tsx, the new ConfirmDialogHost.test.tsx, and the existing tests for the touched flows (confirmDialog, localApi, ServerUpdateAction, relayClientInstallDialog, SidebarUpdatePill, SettingsPanels.restore): 54 tests pass. The new tests cover:

    • focus on the primary action in AlertDialog and Dialog, Cancel when it's disabled, and a caller's initialFocus winning;
    • the shared host: Cancel by default, Confirm with initialFocus: "confirm", and Cancel for a destructive confirm even when asked for Confirm.
  • Static checks: tsc --noEmit in apps/web and packages/contracts is clean. vp lint and vp fmt --check on the changed files are clean.

  • Manual: I ran vp run dev with an isolated --home-dir and opened the confirm dialog with the real update message, driving the keys in headless Chrome on main and on this branch. The table is from the first run, on 98beed1a22. The screenshots and recording below repeat the key rows on the current base, 8d199b4f7b. The rebase didn't touch the dialog primitives. The recording predates the opt-in commit; the update prompts now pass initialFocus: "confirm", so what they show is unchanged.

    Dialog Keys main This PR
    Update focus on open Cancel Confirm
    Update Enter Cancel Confirm
    Update Space Cancel Confirm
    Update Esc Cancel Cancel
    Update Shift+Tab, Enter Confirm (focus wraps) Cancel
    Destructive focus on open Cancel Cancel
    Destructive Enter Cancel Cancel
    Destructive Tab, Enter Confirm Confirm
  • Not checked: the packaged Electron app (same web component), and real screen readers.

Evidence

Initial focus, update prompt (before → after):
before-default
after-default

Destructive confirm keeps Cancel focused (before → after):
before-destructive
after-destructive

Recording: main first, then this PR. The caption bar is an overlay added by the recording script to show the key pressed and the result. For the main pass, only the web files were switched while the same dev server kept running, so the footer still shows the branch name.

dialog-focus-before-after.mp4

Model and harness: Claude Opus 5.5 in Claude Code (T3 Code).

🤖 Generated with Claude Code

Dialogs focused Cancel on open, so pressing Enter cancelled even though
Confirm is the highlighted action. Popups now focus the AlertDialogAction
or DialogAction when they open, so Enter and Space confirm and Tab reaches
Cancel, with native button semantics. Destructive actions aren't marked,
so those dialogs keep focus on Cancel. Touch opens still focus the popup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 11, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR changes keyboard focus and Enter/Space behavior in shared web dialog primitives and several existing update, installation, and import flows. The resulting product interaction defaults warrant human review.

You can add or adjust custom eligibility rules. Learn more.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 11, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 11, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 11, 2026 10:45

Dismissing prior approval to re-evaluate 894463f

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 11, 2026
@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eb2d1a97-60ea-4138-a22a-52ba5f8342c0



📥 Commits

Reviewing files that changed from the base of the PR and between 894463f and 6f46e56.




📒 Files selected for processing (10)
  • apps/web/src/components/ConfirmDialogHost.test.tsx
  • apps/web/src/components/ConfirmDialogHost.tsx
  • apps/web/src/components/LegacySidebar.tsx
  • apps/web/src/components/ServerUpdateAction.tsx
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/sidebar/SidebarUpdatePill.tsx
  • apps/web/src/components/ui/dialog.test.tsx
  • apps/web/src/components/ui/dialog.tsx
  • apps/web/src/confirmDialog.ts
  • packages/contracts/src/ipc.ts



Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.





📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

Dialog components now support initial-focus options for confirmation dialogs. They focus eligible actions when a dialog opens. Several confirmation callers request focus on Confirm.

Changes

Dialog Initial Focus

Layer / File(s) Summary
Carry confirmation focus options
packages/contracts/src/ipc.ts, apps/web/src/confirmDialog.ts
ConfirmDialogOptions adds "confirm" and "cancel" focus choices. Active, queued, and closing confirmation states preserve the selected option.
Select dialog initial focus
apps/web/src/components/ui/dialog.tsx, apps/web/src/components/ui/alert-dialog.tsx, apps/web/src/components/ui/dialog.test.tsx
Dialog popups select an enabled marked action for initial focus, or the popup on touch opens. Alert dialogs connect the focus hook and export AlertDialogAction. Tests cover default, disabled, and caller-provided focus targets.
Apply focus-aware confirmation actions
apps/web/src/components/ConfirmDialogHost.tsx, apps/web/src/components/ConfirmDialogHost.test.tsx, apps/web/src/components/LegacySidebar.tsx, apps/web/src/components/ServerUpdateAction.tsx, apps/web/src/components/settings/SettingsPanels.tsx, apps/web/src/components/sidebar/SidebarUpdatePill.tsx, apps/web/src/components/cloud/RelayClientInstallDialog.tsx, apps/web/src/components/settings/BrowserImportWizard.tsx
Update confirmation callers request focus on Confirm. ConfirmDialogHost uses AlertDialogAction for eligible confirmations. Relay install and browser import actions use DialogAction. Tests cover default, requested, and destructive confirmation focus.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant ConfirmCaller
  participant ConfirmDialogState
  participant ConfirmDialogHost
  participant DialogPopup
  ConfirmCaller->>ConfirmDialogState: Set initialFocus option
  ConfirmDialogState->>ConfirmDialogHost: Publish confirmation state
  ConfirmDialogHost->>DialogPopup: Render focus-aware action
  DialogPopup->>DialogPopup: Focus eligible action on open
Loading

Suggested reviewers: juliusmarminge





Merge Risk: ⚪ Minimal · up to 6f46e

This change focuses Confirm when update-confirmation dialogs open, and other confirmations keep Cancel focused by default. No concrete merge-blocking issue was found, and the worst case is a minor focus inconsistency.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 6f46e

The change selects an initial focus target rather than granting new permissions or removing confirmation. Ordinary and destructive confirmations retain Cancel unless an eligible action explicitly opts in. No security bypass was established in the inspected flows, but external-caller and runtime coverage remains incomplete.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The most consequential inspected focus change reaches confirmations for selected remote desktop machines, including a batch-update path. Their existing target selection and confirmation remain in place; the PR changes the keyboard-default choice rather than introducing additional execution targets. Broader tenant or deployment exposure was not established.

Trust Boundaries and Controls

  • observed — The inspected MCP approval bridge passes only a message to requestConfirmDialog, not focus options. ConfirmDialogHost renders message-derived content as text and marks Confirm only when the state explicitly requests it and the variant is default. This path therefore retains its previous Cancel-first approval behavior.
  • observed — Shared action selection excludes natively disabled elements. Destructive-action exclusion in DialogAction is a compile-time contract, whereas ConfirmDialogHost also enforces a runtime default-variant condition. These are focus-selection safeguards, not substitutes for operation authorization.

Resilience and Maintainability Implications

  • observed — The confirmation transition assessment found focus metadata preserved across active, closing, and queued states without changing response ownership or cleanup. The inspected relay confirmation handler requires a confirming state and a live resolver, clears that resolver before resolution, and rejects overlapping installation requests.



Pre-merge checks | Passed 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: focusing the primary action when confirmation dialogs open.
Description check Passed The description includes the problem, change, scope, approval reference, verification results, limitations, screenshots, recording, and agent details. It provides sufficient evidence for the UI change…
Linked Issues check Passed Issue [#18167] now defines targeted scope for desktop and server update prompts, relay client install, and browser import "quit" and "try again" steps. The PR applies initialFocus to those call site…
Out of Scope Changes check Passed The dialog focus hook, DialogAction, ConfirmDialogHost option, contract plumbing, targeted call-site changes, and tests support [#18167]. The default Cancel focus for shared confirmations and dest…

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR





  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

ConfirmDialogHost serves every requestConfirmDialog and dialogs.confirm
caller, so focusing Confirm there also made Enter approve MCP tool
requests, roll back checkpoints and sign out providers. The host now
keeps Cancel focused unless the caller passes initialFocus: "confirm",
and only the desktop and server update prompts do.

DialogAction now rejects destructive Button variants at compile time
instead of matching the variant name, which missed ghost-destructive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 11, 2026 11:15

Dismissing prior approval to re-evaluate 6f46e56

@rgrvlsk

rgrvlsk commented Oct 11, 2026

Copy link
Copy Markdown
Author

Re CodeRabbit's security note: good catch. ConfirmDialogHost serves every requestConfirmDialog / dialogs.confirm caller, and MCP tool approvals, checkpoint rollback and provider sign-out all use the default variant, so they would have become Enter-to-confirm too. Fixed in 6f46e56: the host now keeps Cancel focused unless the caller passes initialFocus: "confirm", and only the update prompts opt in. Focus preference is now separate from destructive styling, and DialogAction rejects destructive Button variants at compile time. Added host tests for the default, the opt-in, and destructive + opt-in.

@rgrvlsk

rgrvlsk commented Oct 11, 2026

Copy link
Copy Markdown
Author

@coderabbitai run pre-merge checks

@coderabbitai

coderabbitai Bot commented Oct 11, 2026

Copy link
Copy Markdown
✅ Actions performed

Running Pre-merge checks. Results will be posted in the summary comment once complete.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pressing Enter in a confirmation dialog cancels instead of confirming

2 participants