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
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 sizedicon-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:
⌘⌥[useSidebarToggleHotkeys⌘⌥]useSidebarToggleHotkeys⌘KuseCommandPaletteHotkey⌘↑/⌘↓useSessionSwitchHotkey⌘⌥VuseVoiceDictationHotkeyThe 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.
useCommandPaletteHotkeybails whendocument.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 atAppShell, next to its siblings, in a newuseViewModeToggleHotkeyhook modeled onuseSidebarToggleHotkeys.Guards, mirroring the existing hooks:
e.repeat;preventDefault()+stopPropagation()to claim the chord;.xtermbail — this is the deliberate difference fromuseCommandPaletteHotkey, and it should carry an inline comment saying why, since the next reader will assume the omission is a bug. Keep the.monaco-editorbail if⌘⌥Tcollides with a Monaco binding.No-ops (silently, no toast) wherever
ViewModeToggleself-gates tonull: non-terminal-first sessions,isShellView, and the iOS shell where the native bar owns the switcher. Switching to Terminal additionally requiresterminalsAvailable— matching the dropdown item'sdisabledstate, so the hotkey can't strand you on a view with no PTY.Both directions go through
ctx.setViewfromTerminalFirstContext, sosessionStoragepersistence, the native-bridge mirror (useNativeChatTerminalBar/pushChatTerminalState), and composer focus restoration all keep working unchanged.Command palette action. Add
toggle-view-modeto theactionsarray inCommandPalette.tsx: label "Toggle chat / terminal view",TerminalIcon, keywords["terminal", "chat", "view", "shell", "pty"], running the samesetViewflip. 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 readuseTerminalFirst().Discoverability. Add a
Toggle chat / terminal view · ⌘⌥Trow toKeyboardShortcutsDialog.tsxunder Navigation (with the sidebar toggles), and surface the chord inViewModeToggle's existing tooltip so the mouse path teaches the keyboard path.Acceptance criteria
⌘⌥Tflips Chat ↔ Terminal on a terminal-first session, and fires while focus is inside the terminal so Terminal → Chat works without reaching for the mouse.isShellView, in the iOS shell, and in embedded mode.terminalsAvailableis false, matching the dropdown's disabled state.e.repeatignored).Alternatives considered
⌘Kwith a mode. Conflates a search/command surface with an instant view flip, and ⌘K is precisely the chord the terminal must keep.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.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