This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
zplex (zac + multiplex) is a terminal multiplexer desktop app purpose-built for zpit. It replaces scattered terminal tabs with a single Electron window — one fixed panel for zpit's TUI, plus dynamically spawned panels for each Claude Code agent.
Daemon/Client architecture: A Go daemon owns PTY sessions and a WebSocket/REST API. Electron is a display shell only. Closing Electron does NOT kill the daemon or sessions — reopening reconnects and replays via ring buffer.
cd daemon
go build -o zplex-daemon.exe . # build
go run . # run (default port 17732)
go run . --port 17732 # explicit port
go test ./... # all tests
go test ./session/ # session package tests
go test ./server/ # server package tests (REST + WebSocket)
go test -run TestSessionCreate ./session/ # single testcd app
npm install # install deps
npm run dev # dev mode (launches Electron + frontend)
npm run build # production build
npx electron-builder # produce .exe installerStart the daemon first, then the Electron app. The Electron main process can also auto-spawn the daemon as a child process.
Electron (main.ts)
→ spawns Go daemon (child process)
→ creates BrowserWindow → loads frontend
Frontend (TypeScript + xterm.js)
→ one xterm.js instance per panel
→ each connects via WebSocket to /ws/{session_id}
→ layout managed by CSS Grid + workspace tabs
Go Daemon (single binary, port 17732)
→ REST API: session CRUD, health, layout state
→ WebSocket: bidirectional PTY I/O
→ PTY via aymanbagabas/go-pty (ConPTY on Windows)
→ ring buffer (100KB default) per session for reconnect replay
The frontend uses an auto-tiled CSS Grid layout engine (app/src/layout.ts) that manages terminal panels dynamically:
- PanelGrid class (
layout.ts): Manages panel placement, gutter resize, and focus tracking. - Auto-tile algorithm: Panels auto-arrange in 1-2 rows. Columns = ceil(N/2). Bottom row panels span to fill width when fewer than top row.
- Maximum 8 panels per workspace. Attempts to add more are silently ignored with a console warning.
- Workspace-scoped registry: Sessions are organized as
Map<workspaceId, Map<sessionId, TerminalWrapper>>inapp.ts. Current default workspace is"default". - Gutter drag resize: Panels can be resized by dragging gutter bars between them. Minimum panel size: 120px wide, 80px tall.
The frontend supports multiple workspaces, each with its own set of terminal panels:
- WorkspaceManager (
workspace.ts): Manages workspace lifecycle — create, switch, close, rename. Renders the tab bar UI. Fires callbacks thatapp.tshandles for terminal dispose/recreate. - Dispose/Recreate mechanism: When switching workspaces, all xterm.js instances in the current workspace are disposed (freeing memory + WebGL contexts). The target workspace's panels are recreated from daemon layout data, with ring buffer replay providing near-instant recovery.
- Maximum 8 workspaces. Attempts to add more are ignored with a console warning.
- Auto-naming: New workspaces are named "Workspace N" (N auto-increments). Double-click tab to rename.
- Active workspace persistence: Current workspace ID stored in
localStoragekeyzplex:activeWorkspace. Restored on app restart. - Close workspace: Shows confirmation dialog if workspace has running sessions (
zplex:workspaceClosePreference). Panel close uses a separate key (zplex:panelClosePreference). Last workspace cannot be closed.
| Shortcut | Action |
|---|---|
Ctrl+Shift+N |
Create new terminal panel |
Ctrl+Shift+T |
Create new workspace tab |
Ctrl+Shift+W |
Close focused panel (shows dialog or uses saved preference) |
Ctrl+Shift+Arrow |
Move focus to adjacent panel (Up/Down/Left/Right) |
Ctrl+Shift+PageUp |
Switch to previous workspace (wraps around) |
Ctrl+Shift+PageDown |
Switch to next workspace (wraps around) |
Shift+Click [×] |
Force close dialog (override saved preference) |
| Method | Path | Purpose |
|---|---|---|
| GET | /api/health |
Health check + version |
| GET/POST | /api/sessions |
List / create sessions |
| GET/DELETE/PATCH | /api/sessions/{id} |
Get / kill / update session |
| GET/PUT | /api/layout |
Get / save panel layout (per workspace) |
| GET | /api/workspaces |
List workspace IDs with layout data |
| DELETE | /api/workspaces/{id} |
Delete workspace layout data |
| GET | /api/events |
SSE stream for real-time updates |
- Client→Server:
{ type: "input", data: "..." }or{ type: "resize", cols: N, rows: N } - Server→Client:
{ type: "output", data: "..." }or{ type: "exit", code: N }
- xterm.js v6: Use
@xterm/xtermand@xterm/addon-*(scoped packages). The old unscopedxterm-addon-*packages are deprecated and must NOT be used. - Go PTY:
aymanbagabas/go-pty— handles ConPTY (Windows) and /dev/ptmx (Unix). - WebSocket:
gorilla/websocketv1.5.3 — must be >=v1.4.1 (DoS fix). - Go logging:
log/slog(structured). Nofmt.Printlnfor logging. - Frontend logging:
console.warn/console.erroronly. Noconsole.login production code. - TypeScript: Strict mode. No
any. Preferconst. - Frontend code must be Electron-agnostic — no Electron APIs in
app/src/. Only use Electron APIs through the preload bridge inapp/electron/.
- Branch naming:
feat/ISSUE-ID-slug(e.g.,feat/1-pty-session-manager) - Git model:
main<-dev<- feature branches - Commit messages:
[ISSUE-ID] short description(e.g.,[#1] add session manager with PTY spawn) - Go style:
gofmt. No global state. - Go module:
github.com/zac15987/zplex/daemon
- zpit broker: 17731
- zplex daemon: 17732 (configurable via
--portflag orZPLEX_PORTenv)
zplex config lives at ~/.zplex/config.toml. Key sections: [daemon] (port, default_shell, buffer_size), [zpit] (auto-create fixed panel), [electron] (tray behavior, daemon lifecycle), [appearance] (theme, font).
zplex is tightly coupled with zpit. In M4+, zpit's LaunchClaude() detects a running zplex daemon and POSTs to its API to create agent panels instead of opening new terminal tabs. Sessions carry metadata: source, project_id, issue_id, role (coder/reviewer).
- Do NOT modify code unless explicitly asked. When the user asks a question, answer it — do not edit files. Wait for clear instruction (e.g., "fix it", "change it", "update it") before making changes.