Skip to content

[Feature] Chat/Terminal view toggle is mouse-only — add a hotkey (⌘⌥T) and a command-palette action #4253

Description

@randypitcherii

Problem or use case

On a terminal-first session, Chat and Terminal are two views of the same work and you switch between them constantly — read the agent's reasoning in Chat, drop to Terminal to watch what it is actually doing, come back. That round trip is mouse-only today.

The only affordance is ViewModeToggle (web/src/shell/ViewModeToggle.tsx): a ghost icon button in the chat header that opens a dropdown, from which you pick Chat or Terminal from a radio group. Two pointer interactions per switch, on a control sized icon-xs, in the busiest strip of the app. On iOS the equivalent is the native Liquid Glass bar — also tap-only.

Meanwhile the app has an established hotkey vocabulary for exactly this kind of view flip, and every neighbor of this control is already bound:

Action Key Hook
Toggle conversations sidebar ⌘⌥[ useSidebarToggleHotkeys
Toggle workspace sidebar ⌘⌥] useSidebarToggleHotkeys
Open command palette ⌘K useCommandPaletteHotkey
Previous / next session ⌘↑ / ⌘↓ useSessionSwitchHotkey
Toggle voice dictation ⌘⌥V useVoiceDictationHotkey
Toggle Chat / Terminal

The command palette has the same hole: CommandPalette.tsx (~L129) ships six actions including Toggle conversations sidebar and Toggle workspace sidebar, but nothing for the Chat/Terminal view — even though it is a more frequent switch than either sidebar.

There is a second-order problem specific to this control, and it is the reason this isn't just "add another hook". Getting out of Terminal view means firing a hotkey while focus is inside xterm — and the app's convention is to deliberately not fire there. useCommandPaletteHotkey bails when document.activeElement.closest(".xterm, .monaco-editor") matches, so ⌘K reaches the PTY. That guard is correct for ⌘K and wrong for this action: a toggle you cannot fire from the view you want to leave is half a toggle.

Proposed solution

Hotkey: ⌘⌥T / Ctrl+Alt+T. Joins the ⌘⌥ view-toggle family ([, ], V), mnemonic for Terminal, and — critically — is not a chord xterm forwards or that CLI agents bind. Bound once at AppShell, next to its siblings, in a new useViewModeToggleHotkey hook modeled on useSidebarToggleHotkeys.

Guards, mirroring the existing hooks:

  • ignore e.repeat;
  • preventDefault() + stopPropagation() to claim the chord;
  • disabled in embedded mode;
  • no .xterm bail — this is the deliberate difference from useCommandPaletteHotkey, and it should carry an inline comment saying why, since the next reader will assume the omission is a bug. Keep the .monaco-editor bail if ⌘⌥T collides with a Monaco binding.

No-ops (silently, no toast) wherever ViewModeToggle self-gates to null: non-terminal-first sessions, isShellView, and the iOS shell where the native bar owns the switcher. Switching to Terminal additionally requires terminalsAvailable — matching the dropdown item's disabled state, so the hotkey can't strand you on a view with no PTY.

Both directions go through ctx.setView from TerminalFirstContext, so sessionStorage persistence, the native-bridge mirror (useNativeChatTerminalBar / pushChatTerminalState), and composer focus restoration all keep working unchanged.

Command palette action. Add toggle-view-mode to the actions array in CommandPalette.tsx: label "Toggle chat / terminal view", TerminalIcon, keywords ["terminal", "chat", "view", "shell", "pty"], running the same setView flip. Listed only when the active session is terminal-first and the toggle is live, so it never appears as a dead command on the landing page or a non-terminal session — the array is currently static and will need to read useTerminalFirst().

Discoverability. Add a Toggle chat / terminal view · ⌘⌥T row to KeyboardShortcutsDialog.tsx under Navigation (with the sidebar toggles), and surface the chord in ViewModeToggle's existing tooltip so the mouse path teaches the keyboard path.

Acceptance criteria

  • ⌘⌥T flips Chat ↔ Terminal on a terminal-first session, and fires while focus is inside the terminal so Terminal → Chat works without reaching for the mouse.
  • The hotkey is inert on non-terminal-first sessions, in isShellView, in the iOS shell, and in embedded mode.
  • Switching to Terminal is refused (no view change) when terminalsAvailable is false, matching the dropdown's disabled state.
  • Held-down keys don't flap the view (e.repeat ignored).
  • The command palette lists "Toggle chat / terminal view" when — and only when — the toggle is live, and running it produces the same result as the dropdown.
  • The shortcut appears in the keyboard-shortcuts dialog and in the toggle's tooltip.
  • Vitest coverage for the hook (including the in-terminal-focus case) and for the palette action's conditional presence.

Alternatives considered

  • Reuse ⌘K with a mode. Conflates a search/command surface with an instant view flip, and ⌘K is precisely the chord the terminal must keep.
  • **A bare chord like Ctrl+`` (VS Code's terminal toggle).** Directly conflicts with the PTY — Ctrl-prefixed bare keys are the shell's, and a bare backtick is a character. ⌘⌥` keeps it in the app layer.
  • Escape to leave the terminal. Escape is already the composer's "stop response" and is heavily used inside terminal apps; overloading it would be actively harmful.
  • Palette command only, no hotkey. ⌘K → type → Enter is three interactions and doesn't work from inside the terminal, where the ⌘K guard bails. It solves discoverability, not the round trip.
  • Restore a persistent segmented pill instead of the dropdown (the pre-dropdown design referenced in ViewModeToggle's docstring). Cuts it to one click, but it is a deliberate design reversal and still mouse-only.

Harness

Claude, Codex, Cursor

Platform or device

macOS, Linux, Windows, Desktop app

Harness mode

SDK, Native

Expected reach

A substantial user segment

Authentication type

Not authentication-related

Metadata

Metadata

Assignees

Labels

FeatureNew feature or requestP2-mediumPriority: bug with workaround, important feature requestcomp:web-uiComponent: web frontend (ap-web)triagedIssue has been triaged by the bot

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions