diff --git a/docs/superpowers/specs/2026-08-14-split-terminal-view-design.md b/docs/superpowers/specs/2026-08-14-split-terminal-view-design.md new file mode 100644 index 0000000..45075b5 --- /dev/null +++ b/docs/superpowers/specs/2026-08-14-split-terminal-view-design.md @@ -0,0 +1,74 @@ +# 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.