Files
Claude-Code-Monitor/docs/superpowers/specs/2026-08-14-split-terminal-view-design.md

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 TerminalView keeps its own independent WebSocket connection, unchanged from today's single-instance behavior.

Testing

  • client/src/pages/__tests__/Workspace.test.tsx currently mocks TerminalView and 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 independent TerminalView/RunSetup instances.
  • Run npm run test:client before considering this done.