Workspaces and panes
A workspace is an in-memory collection of panes laid out as a rectangular tiling. The top bar shows workspaces as tabs. The sidebar lists them in creation order or by recency. Nothing persists across a full app quit — by design, not by omission.
Workspaces are ephemeral. State lives in RAM until you quit.
The workspacesStore and uiStore hold every workspace, its layout tree, its panes, the focused pane, and any open modals. Both stores are Zustand instances with no persistence layer. Quitting Kadro discards them.
This is intentional. Panes wrap real PTYs and PTYs do not survive their parent process. Pretending workspaces persist would mean silently dropping the terminal state inside each pane, and that gap is worse than reopening a fresh workspace. Preferences (theme, default shell, sidebar geometry) do persist; the workspace tree does not.
Per-workspace persistence with versioned migrations is tracked on the roadmap. Until it ships, treat each launch as a clean slate and rely on command palette recency, not on saved layouts.
The top bar renders workspaces as colored tabs. Each workspace gets a stable color index from workspaceColors so a workspace looks the same in the top bar, the sidebar, and the palette.
Nine numeric shortcuts — ⌘1 through ⌘9 — jump to the Nth non-draft workspace in creation order. Each slot is registered as its own command (workspace.jumpTo1, workspace.jumpTo2, …) so it shows up individually in the palette and can be remapped in Settings → Keybindings.
⌘⇧] and ⌘⇧[ cycle through workspaces in order. A new workspace is created with ⌘N — the command produces a draft, and the app mounts the creation flow when it sees the active workspace become a draft.
- workspacesStore — in-memory; owns the workspace tree, panes, and all layout mutations.
- uiStore — in-memory; tracks active workspace, focused pane per workspace, modals, command frecency.
- preferencesStore — debounced to a Tauri settings file; holds theme, default shell, sidebar width, collapsed state, and the sidebarRecentsEnabled toggle.
- Per-workspace partitioned persistence with versioned migrations is on the roadmap, not shipped.
Rectangular tiling, not a recursive split tree.
The layout primitives live in src/shared/lib/layoutTree.ts. Each pane owns an axis-aligned rectangle in the unit square. The invariant is simple — every pane is one rectangle, no gaps and no overlaps — and it is enforced after every mutation by isValidRectTiling(). If a mutation would break tiling, the operation is rejected and the layout stays put.
Resize handles are derived, not stored. Adjacent panes that share an edge expose a handle only when moving that edge keeps every pane as a single rectangle. Edges that would orphan a slice are read-only. This replaces a previous recursive split-tree model where every divider was owned by a parent group node.
Default layouts pick a near-square grid for N panes (1x1, 1x2, 2x2, 2x3, …). The blueprint flow in workspace creation writes the same shape; from there, splits and moves can produce any rectilinear arrangement.
splitPane⌘D / ⌘⇧DHorizontal or vertical split of the focused pane. The new pane inherits the source pane's provider and rendering mode unless ⌘⌥D opens the provider picker.
movePanedragDrop a pane onto another pane's center to swap them, or onto one of its four outer edge zones to slot in beside it. Both paths preserve the rectangular tiling invariant.
removePane⌘WClosing the only pane in a workspace removes the workspace. Closing a non-last pane merges its rectangle into the best-fitting neighbor, falling back to the default grid layout if no clean merge exists.
resizeRectEdgedrag handleShared edges between adjacent panes expose resize handles when moving that edge preserves the tiling. Minimum pane width/height enforced at 180px.
findNeighbor⌘← ⌘→ ⌘↑ ⌘↓Directional focus jumps to the pane sharing the largest overlap on the requested edge. Cycles ⌘⌥[ / ⌘⌥] step through the natural top-to-bottom, left-to-right pane order.
toggleFullscreenPane⌘⇧↵Lifts the focused pane to fill the workspace surface without mutating the underlying layout tree. Toggling again restores the original grid.
One pane, one PTY, one provider.
Each pane runs exactly one process — either an agent CLI launched through your default shell or a raw shell pane. The provider is chosen at creation time via the picker described in Providers and recorded on the pane. Splits inherit the source pane's provider unless you use ⌘⌥D.
Rendering mode comes from the provider spec. tui-replace is used for full-screen TUI tools (Claude, Codex, Gemini) so the alternate-screen buffer renders cleanly; shell panes use a normal scrollback buffer of ~10,000 lines.
Pane-scoped commands — pane.splitHorizontal, pane.splitVertical, pane.duplicate, pane.rename, pane.copyCwd, pane.revealCwd, pane.clearScrollback, pane.toggleFullscreen — all check hasFocusedPane before firing, so they never run on the landing screen.
The left sidebar lists workspaces under a Workspaces ↔ Teams toggle. Width is collapsible between 240 and 420 pixels (default 296). Width, collapsed state, and recents-ordering preference are the only workspace-related values that survive a quit — they live in preferencesStore.
Ordering defaults to creation order. Toggle sidebarRecentsEnabled in Settings → General to sort by lastOpenedAt instead.
The shell, sidebar, and top bar are pure app chrome. Taglines, version badges, stat rows, and marketing strips deliberately do not appear in the workspace surface — those belong on the marketing site, not in the workspace you're trying to work in.
- Sidebar width persists; workspace contents do not.
- Workspace colors come from a stable index assigned at creation.
- Draft workspaces are mid-creation and never appear in the palette or ⌘1..9 list.
- Right-clicking a workspace row exposes rename, color, and close.
Keep every session visible.
Workspaces and the layout tree are built so you never lose track of an agent or a terminal.