3.3 KiB
Split terminal view for the Workspace console
Status: approved 2026-08-14.
Problem
Workspace.tsx renders exactly one lane's run console at a time: a single
RunSetup/TerminalView switcher (client/src/pages/Workspace.tsx:789-824)
driven by page-level state (selectedLaneId, prompt, cwd, model,
permissionMode, effort, resumeSession, handle, busy, activeRuns,
runHistory, cwdSuggestions). Lanes are independent working directories
that can each have their own live tmux/PTY session running concurrently on
the server (server/lib/pty-attach.js), but the dashboard can only show one
at a time — comparing two lanes' output means switching back and forth.
The user wants to view multiple lanes' terminals side by side: 1 pane (today's behavior), 2 panes (left/right), or 4 panes (2x2 grid).
Approach
Extract a self-contained LaneConsolePane component. Move the existing
RunSetup/TerminalView switcher and all its state out of Workspace.tsx into
its own component that owns one lane's run lifecycle independently. Each
pane gets its own laneId (chosen via a dropdown in the pane header, listing
all lanes, not just ones with an active run) and manages its own
prompt/cwd/model/permissionMode/effort/resumeSession/handle/busy/activeRuns/
runHistory state — nothing is shared across panes.
Workspace keeps a paneLaneIds: (number | null)[] array sized to the current
layout (1, 2, or 4) and renders that many LaneConsolePane instances in a
CSS grid. This is the only viable approach given the existing state model is
single-lane; the alternative (keeping one shared state object indexed by
lane) would require rewriting every handler in Workspace.tsx to be
lane-aware and is a much larger, riskier diff for the same result.
Layout
A layout toggle (1 / 2 / 4 buttons) sits next to the existing console header. Grid via CSS:
- 1: full width — identical to today.
- 2:
grid-cols-2— left/right. - 4:
grid-cols-2 grid-rows-2— four corners.
Each pane has a small header with a lane-select dropdown. If the selected
lane has no active run, the pane shows a compact RunSetup (reused
component, same as today's pre-run form) so the user can start one directly
from the pane. If it has an active run, the pane shows TerminalView as
today.
Persistence
The chosen layout mode and each pane's selected laneId are saved to
localStorage (e.g. key ccam.workspace.splitView) and restored on next
visit to Workspace. If a persisted lane no longer exists, that pane falls
back to unselected (dropdown placeholder).
Non-goals
- No server/API changes — this is purely a client-side rendering feature. Each lane's run already exists independently server-side; this just lets the UI display more than one at once.
- No synchronized input across panes (typing in one pane's terminal does not
affect others) — each
TerminalViewkeeps its own independent WebSocket connection, unchanged from today's single-instance behavior.
Testing
client/src/pages/__tests__/Workspace.test.tsxcurrently mocksTerminalViewand exercises the single-console flow; update it (or add a sibling test file) to cover: layout toggle, per-pane lane dropdown, starting a run from within a pane, and multiple panes rendering independentTerminalView/RunSetupinstances.- Run
npm run test:clientbefore considering this done.