Before submitting
Area
apps/web
Problem or use case
The right panel can host a terminal surface, but there is no way to open one from the keyboard.
rightPanel.toggle only flips visibility (ChatView.tsx:3189-3199, calling useRightPanelStore.toggleVisibility). It restores whatever surface that thread last had, which may be diff, preview, or plan.
terminal.new does open a right-panel terminal, but only when focus is already there: if (terminalFocusOwner === "right-panel") { addTerminalSurface(); ... } (ChatView.tsx:4350-4356). That's a chicken-and-egg problem. To reach right-panel focus from the keyboard, the panel already has to be showing a terminal.
So from the composer there is no keystroke that puts a terminal on the right. The bottom drawer has terminal.toggle; the right panel has no equivalent, and sitting beside the diff is the main reason to want a terminal there at all.
Panel state is per-thread (right-panel-state:v2, byThreadKey), so even after opening one by hand, every new thread starts without it.
Related but different: #4641 asks for terminal.focus, which is about the bottom drawer. This one is about placement, getting a terminal into the right panel in the first place.
Proposed solution
Add one command, rightPanel.openTerminal, to STATIC_KEYBINDING_COMMANDS (packages/contracts/src/keybindings.ts:50-72), and dispatch it in ChatView.tsx alongside the existing rightPanel.toggle branch (ChatView.tsx:4303-4308):
- If the right panel is open and
activeRightPanelSurface?.kind === "terminal", focus it by bumping terminalFocusRequestId, the way activateRightPanelSurface already does.
- Otherwise call
addTerminalSurface() (ChatView.tsx:3068), which already opens the panel, creates the surface via useRightPanelStore.getState().openTerminal (apps/web/src/rightPanelStore.ts:289), and spawns the pty.
The capability is all there; it just has no command ID, so the store needs no changes. Shipping it with no default binding is fine. I'd bind mod+j.
Why this matters
A right-panel terminal sits next to the diff and stays put, which is a different job from the bottom drawer. Today, getting one requires the mouse on every new thread, and there is no config-level workaround, because the command set is a closed schema literal and a keybinding for this cannot be expressed.
Smallest useful scope
One command literal plus one dispatch branch that reuses addTerminalSurface. Even the open-only version, with no focus-if-already-open branch, would cover the common case.
Alternatives considered
Rebinding mod+j to rightPanel.toggle only works after manually creating a terminal surface in that thread, and it breaks as soon as the last surface was a diff or preview.
Patching app.asar locally invalidates the Developer ID signature under hardened runtime, and the ad-hoc re-sign then breaks Squirrel.Mac auto-update, since it validates the update against the running app's signing identity. That rules it out as a workaround.
Risks or tradeoffs
Naming: terminal.newInRightPanel would sit with the other terminal.* commands, while rightPanel.openTerminal reads as a panel action. Either works.
If #4641's terminal.focus lands, the two should agree on whether "focus" implies "open if closed".
Examples or references
No response
Contribution
Before submitting
Area
apps/web
Problem or use case
The right panel can host a terminal surface, but there is no way to open one from the keyboard.
rightPanel.toggleonly flips visibility (ChatView.tsx:3189-3199, callinguseRightPanelStore.toggleVisibility). It restores whatever surface that thread last had, which may be diff, preview, or plan.terminal.newdoes open a right-panel terminal, but only when focus is already there:if (terminalFocusOwner === "right-panel") { addTerminalSurface(); ... }(ChatView.tsx:4350-4356). That's a chicken-and-egg problem. To reach right-panel focus from the keyboard, the panel already has to be showing a terminal.So from the composer there is no keystroke that puts a terminal on the right. The bottom drawer has
terminal.toggle; the right panel has no equivalent, and sitting beside the diff is the main reason to want a terminal there at all.Panel state is per-thread (
right-panel-state:v2,byThreadKey), so even after opening one by hand, every new thread starts without it.Related but different: #4641 asks for
terminal.focus, which is about the bottom drawer. This one is about placement, getting a terminal into the right panel in the first place.Proposed solution
Add one command,
rightPanel.openTerminal, toSTATIC_KEYBINDING_COMMANDS(packages/contracts/src/keybindings.ts:50-72), and dispatch it inChatView.tsxalongside the existingrightPanel.togglebranch (ChatView.tsx:4303-4308):activeRightPanelSurface?.kind === "terminal", focus it by bumpingterminalFocusRequestId, the wayactivateRightPanelSurfacealready does.addTerminalSurface()(ChatView.tsx:3068), which already opens the panel, creates the surface viauseRightPanelStore.getState().openTerminal(apps/web/src/rightPanelStore.ts:289), and spawns the pty.The capability is all there; it just has no command ID, so the store needs no changes. Shipping it with no default binding is fine. I'd bind
mod+j.Why this matters
A right-panel terminal sits next to the diff and stays put, which is a different job from the bottom drawer. Today, getting one requires the mouse on every new thread, and there is no config-level workaround, because the command set is a closed schema literal and a keybinding for this cannot be expressed.
Smallest useful scope
One command literal plus one dispatch branch that reuses
addTerminalSurface. Even the open-only version, with no focus-if-already-open branch, would cover the common case.Alternatives considered
Rebinding
mod+jtorightPanel.toggleonly works after manually creating a terminal surface in that thread, and it breaks as soon as the last surface was a diff or preview.Patching
app.asarlocally invalidates the Developer ID signature under hardened runtime, and the ad-hoc re-sign then breaks Squirrel.Mac auto-update, since it validates the update against the running app's signing identity. That rules it out as a workaround.Risks or tradeoffs
Naming:
terminal.newInRightPanelwould sit with the otherterminal.*commands, whilerightPanel.openTerminalreads as a panel action. Either works.If #4641's
terminal.focuslands, the two should agree on whether "focus" implies "open if closed".Examples or references
No response
Contribution