Skip to content

feat(web): setting to keep the terminal from taking focus automatically - #8372

Open
darahaas15 wants to merge 4 commits into
pingdotgg:mainfrom
darahaas15:feat/terminal-auto-focus-setting
Open

feat(web): setting to keep the terminal from taking focus automatically#8372
darahaas15 wants to merge 4 commits into
pingdotgg:mainfrom
darahaas15:feat/terminal-auto-focus-setting

Conversation

@darahaas15

@darahaas15 darahaas15 commented Aug 27, 2026

Copy link
Copy Markdown

What Changed

A new client setting: Settings → General → "Terminal auto-focus" (terminalAutoFocus, default on, so behavior is unchanged for existing users). When off, the embedded terminal never takes keyboard focus on its own: not when you switch to a thread whose terminal is open, and not when the drawer opens. Direct interaction still focuses it: clicking into the terminal, and terminal actions such as creating, splitting, closing, or activating one.

The gating splits automatic focus from explicit focus requests (refined per the review feedback below). Automatic paths honor the setting: the surface-ready and first-output focus in TerminalViewport, its reveal effect on the hidden-to-visible transition (thread activation, drawer open), and the drawer-open request bump in ChatView. The focus-request id carries only explicit actions - encoding visibility or the setting into it would turn their transitions, including async settings hydration, into spoofed requests. Action-driven focus requests (terminal create/split/close/tab activation) always focus, so terminal actions cannot drop keyboard focus on document.body. Two supporting changes make the split sound: the focus-request id is now passed to persistent drawers unconditionally (the previous swap-to-0 made thread activation itself look like a request), and a viewport mounted by the same render as a request (the single-terminal view remounts on create/close/activate) carries it across the remount via a mount-captured flag. Focus permission is checked at frame time through an effect event, so a setting that hydrates or toggles after scheduling wins over the captured render. Both terminal surfaces (bottom drawer and right-panel terminal) render through TerminalViewport, so both respect the setting. The row is searchable, has a reset button, and participates in restore-defaults.

Why

Switching threads with thread.next / thread.previous (⌘⇧] / ⌘⇧[) or thread.jump.N drops focus into the terminal whenever the target thread has one open. Once the Ghostty surface has focus it owns keystrokes: keybindings.ts already documents that the jump commands cannot run there regardless of when clauses, and the jump hints are deliberately hidden while terminalFocus for the same reason. So keyboard-driven thread navigation dead-ends on the first thread with an open terminal, and every jump costs an extra ⌘J or a click to get focus back.

This is a setting rather than a behavior change because auto-focus is the right default for people who open a terminal to type into it. Keyboard-heavy users who mostly watch agent terminals can opt out and keep focus in the composer.

UI Changes

One standard SettingsRow + Switch in Settings → General, placed with the other interaction-behavior rows and reusing the archive/delete-confirmation row pattern verbatim. Happy to attach before/after screenshots if wanted.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (single stock SettingsRow/Switch; can add on request)
  • I included a video for animation/interaction changes (no motion involved)

Note

Medium Risk
Terminal focus is intricate keyboard UX; regressions could break thread navigation or terminal shortcuts, though the default preserves prior auto-focus behavior.

Overview
Adds a Terminal auto-focus client setting (terminalAutoFocus, default on) so existing behavior stays the same until users turn it off in Settings → General.

When off, the embedded terminal no longer steals keyboard focus on automatic reveals—opening the drawer, switching to a thread with an open terminal, surface-ready mount, or first output. Explicit terminal actions (create, split, close, tab activate) still focus the terminal so focus does not fall to document.body when a viewport remounts.

The focus model is split so focusRequestId carries only action-driven requests (visibility and settings are no longer mixed into that counter). TerminalViewport gates automatic paths via frame-time permission checks and a one-shot mountFocusPending flag for remounts tied to explicit requests; ChatView only bumps the focus request when the drawer opens if auto-focus is enabled.

Reviewed by Cursor Bugbot for commit 8f49ff3. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add terminalAutoFocus setting to control terminal drawer auto-focus

  • Adds a new boolean terminalAutoFocus field (default true) to ClientSettingsSchema and the patch schema in settings.ts
  • Adds a toggle in the General settings panel with reset-to-default support and a searchable entry in SettingsPanels.tsx and settingsSearch.ts
  • Reworks focus logic in ChatView.tsx and ThreadTerminalDrawer.tsx so reveal and readiness events respect the setting, while explicit focus requests (create/split/activate) still focus when autoFocus is true
  • focusRequestId no longer zeroes for inactive threads and no longer changes on visibility transitions, preventing spurious focus on activation
  • Behavioral Change: focusRequestId is now always the current terminalFocusRequestId instead of zeroed for inactive threads; hidden drawers ignore requests. Reviewers should verify inactive-thread terminal activation still behaves correctly.

Macroscope summarized 8f49ff3.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ba98415-338a-459a-808f-79d66911cba4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@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 Aug 27, 2026
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: gating the focusRequestId effect on the new setting also suppresses focus for explicit user actions (new/split/close terminal, terminal tab activation), which goes beyond the setting's documented scope and can leave keyboard focus on nothing. Details inline.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This adds a persisted user-facing setting and changes production terminal focus behavior across ChatView and the terminal lifecycle, including asynchronous mount and visibility transitions. The implementation is broader than a simple settings-only change and lacks new tests for the runtime focus paths.

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

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Aug 27, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5823e3e. Configure here.

Comment thread apps/web/src/components/ChatView.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new focus-gating logic in TerminalViewport: the mount-time focus grant (mountFocusPending) is handed to every viewport mounted on a request render, including split panes that are not the request target, and it never expires — so it can be redeemed by a later automatic reveal even when terminalAutoFocus is off. Details inline.

Separately, the focus decision is now a four-signal protocol (focusRequestId, autoFocus, visible, mountFocusPending) plus three refs, and none of it is covered by a test. This file already follows the pattern of exporting pure predicates (shouldHandleTerminalExit, shouldHandleTerminalSelectionMouseUp) and unit-testing them in ThreadTerminalDrawer.test.ts; extracting the permission rule (shouldAutoFocus || (autoFocus && mountFocusPending)) and the reveal/request-change transitions into such a predicate would make the new state transitions verifiable without changing behavior.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 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.

1 participant