BTerminal/docs/task_plan.md

13 KiB

BTerminal v2 — Claude Agent Mission Control

Goal

Redesign BTerminal from a GTK3 terminal emulator into a multi-session Claude agent dashboard optimized for 32:9 ultrawide (5120x1440). Simultaneous visibility of all active sessions, agent tree visualization, inline markdown rendering, maximum information density.

Status: Phases 1-6 Complete + Phase 7 (Agent Teams) in progress — Rev 4


Adversarial Review Corrections

The initial plan had critical gaps surfaced by a devil's advocate review. Key corrections:

  1. Node.js sidecar is required — Claude Agent SDK is TS/Python, not Rust. Cannot run in Tauri's webview or Rust. Must spawn a Node.js sidecar process. This has real packaging/complexity implications.
  2. SDK is 0.2.x (pre-1.0) — 127 versions in 5 months. We MUST have an abstraction layer (message adapter) between SDK wire format and UI renderers.
  3. Three-tier observation → Two-tier — Drop JSONL tailing of interactive CLI sessions. Too fragile (undocumented internal format). Just two tiers: SDK (structured) and Terminal (raw).
  4. Scope reduction — Phases 1-4 are the MVP. Phases 5-8 are post-MVP. Ship a usable tool after Phase 4.
  5. Svelte 5 over Solid.js — Adversarial review is right: Solid's ecosystem is too small, Svelte 5 runes match its reactivity model with much larger ecosystem.
  6. Responsive layout required — Cannot design only for 32:9. Must work on 1920x1080 with degraded but functional layout.
  7. Packaging story must be planned upfront — Not a Phase 8 afterthought.
  8. Error handling and testing strategy required — Not optional.

Phase 0: Technology Decision [status: complete]

Decision: Tauri 2.x + Svelte 5 + Claude Agent SDK (via Node.js sidecar)

Why Tauri over Electron:

  • Rust backend is genuinely useful for PTY management and file watching
  • Memory overhead matters when running 4+ agent sidecars
  • Better security model (no Node.js in renderer)
  • Acknowledged limitation: WebKit2GTK has no WebGL. xterm.js uses Canvas fallback. Acceptable for 2-4 AI output panes. NOT for 8+ high-throughput terminals.
  • If Canvas proves unacceptable: escape hatch is switching to Electron (frontend code is framework-agnostic web tech, mostly portable)

Why Svelte 5 (revised from Solid.js):

  • Fine-grained reactivity via $state/$derived runes — comparable to Solid signals
  • No VDOM — same performance characteristic
  • Much larger ecosystem (xterm.js wrappers, layout libraries, component libs)
  • Better TypeScript support and devtools
  • Svelte 5 runes eliminated the ceremony that older Svelte versions had

Why NOT React:

  • VDOM reconciliation across 4+ simultaneously streaming panes = CPU waste
  • Larger bundle (40KB vs ~5KB Svelte runtime)

Architecture: Two-Tier Observation

Session Type Backend Frontend Observation
SDK Agent Node.js sidecar → Rust bridge → Tauri events Structured rich panels Full: streaming, subagents, hooks, cost
Terminal (SSH/CLI/Shell) PTY via portable-pty (Rust) xterm.js terminal Raw terminal only
File viewer Rust file watcher (notify) Markdown renderer N/A

Dropped: Interactive CLI JSONL tailing (undocumented internal format, fragile). Dropped: CLI stream-json tier (SDK handles this better for non-interactive use).

Node.js Sidecar Architecture (critical detail)

The Agent SDK cannot run in Rust or the webview. Solution:

┌─────────────────────────────────────────────────────┐
│ Tauri App                                            │
│                                                      │
│  ┌──────────┐    Tauri IPC    ┌──────────────────┐  │
│  │ WebView  │ ←────────────→  │ Rust Backend     │  │
│  │ (Svelte) │                 │                  │  │
│  └──────────┘                 │  ├── PTY manager │  │
│                               │  ├── File watcher│  │
│                               │  └── Sidecar mgr │──┼──→ Node.js process
│                               └──────────────────┘  │     (Agent SDK)
│                                                      │     stdio JSON-RPC
└─────────────────────────────────────────────────────┘
  • Rust spawns Node.js child process on app launch (auto-start in setup)
  • Communication: stdio with newline-delimited JSON (simple, no socket server)
  • Node.js process runs a thin wrapper that spawns claude -p --output-format stream-json and forwards NDJSON events
  • If sidecar crashes: detect via process exit, show error in UI, offer restart
  • Packaging: Bundle the sidecar JS as a single file (esbuild bundle). Require Node.js 20+ as system dependency. Document in install.sh.
  • Future: Could replace Node.js with Deno (single binary, no npm) for better packaging.

SDK Abstraction Layer

// adapters/sdk-messages.ts — insulates UI from SDK wire format changes
interface AgentMessage {
  id: string;
  type: 'text' | 'tool_call' | 'tool_result' | 'subagent_spawn' | 'status' | 'cost';
  parentId?: string;  // for subagent tracking
  content: unknown;   // type-specific payload
  timestamp: number;
}

// Adapter function — this is the ONLY place that knows SDK internals
function adaptSDKMessage(raw: SDKMessage): AgentMessage { ... }

When SDK changes its message format, only the adapter needs updating.


Implementation Phases

See phases.md for the full phased implementation plan (Phases 1-6).

  • MVP: Phases 1-4 (scaffolding, terminal+layout, agent SDK, session mgmt+markdown)
  • Post-MVP: Phases 5-7 (agent tree, polish, packaging, agent teams)

Decisions Log

Decision Rationale Date
Tauri 2.x over GTK4 Web frontend for markdown, tiling, agent viz; Rust backend for PTY/SDK 2026-03-05
Tauri over Electron Memory efficiency, Rust backend value, security model. Escape hatch: port to Electron if Canvas perf unacceptable 2026-03-05
Svelte 5 over Solid.js Larger ecosystem, Svelte 5 runes match Solid's reactivity, better tooling 2026-03-05
Two-tier over three-tier Drop JSONL tailing (undocumented internal format). SDK or raw terminal, nothing in between 2026-03-05
portable-pty over tauri-plugin-pty Direct Rust crate (used by WezTerm) vs 38-star community plugin 2026-03-05
Node.js sidecar for SDK SDK is TS/Python only. Sidecar with stdio NDJSON. Future: replace with Deno 2026-03-05
SDK abstraction layer SDK is 0.2.x, 127 versions in 5 months. Must insulate UI from wire format changes 2026-03-05
MVP = Phases 1-4 Ship usable tool before tackling tree viz, packaging, polish 2026-03-05
Canvas addon (not WebGL) WebKit2GTK has no WebGL. Explicit Canvas addon avoids silent fallback 2026-03-05
claude CLI over Agent SDK query() Use claude -p --output-format stream-json instead of SDK npm package. Avoids dependency + version churn, identical structured output 2026-03-06
.svelte.ts for rune stores Svelte 5 $state/$derived runes require .svelte.ts extension (not .ts). Compiler silently passes .ts but runes fail at runtime. All store files must use .svelte.ts. 2026-03-06
SQLite settings table for app config Key-value settings table in session.rs for persisting user preferences (shell, cwd, max panes). Simple and extensible without schema migrations. 2026-03-06
Toast notifications over persistent log Ephemeral toasts (4s auto-dismiss, max 5) for agent events rather than a persistent notification log. Keeps UI clean; persistent logs can be added later if needed. 2026-03-06
Build-from-source installer over pre-built binaries install-v2.sh checks deps and builds locally. Pre-built binaries via GitHub Actions CI (.deb + AppImage on v* tags). Auto-update deferred until signing key infrastructure is set up. 2026-03-06
ctx read-only access from Rust Open ~/.claude-context/context.db with SQLITE_OPEN_READ_ONLY. Never write — ctx CLI owns the schema. Separate CtxDb struct in ctx.rs with Option for graceful absence. 2026-03-06
SSH via PTY shell args SSH sessions spawn TerminalPane with shell=/usr/bin/ssh and args=[-p, port, [-i, keyfile], user@host]. No special SSH library — PTY handles it natively. 2026-03-06
Catppuccin 4 flavors at runtime CSS variables overridden at runtime. onThemeChange() callback registry in theme.svelte.ts allows open terminals to hot-swap themes. 2026-03-06
Detached pane via URL params Pop-out windows use ?detached=1&type=terminal URL params. App.svelte conditionally renders single pane without sidebar/grid chrome. Simple, no IPC needed. 2026-03-06
Shiki over highlight.js Shiki provides VS Code-grade syntax highlighting with Catppuccin theme. Lazy singleton pattern avoids repeated WASM init. 13 languages preloaded. 2026-03-06
Vitest for frontend tests Vitest over Jest — zero-config with Vite, same transform pipeline, faster. Test config in vite.config.ts. 2026-03-06
Deno sidecar evaluation Proof-of-concept agent-runner-deno.ts created. Deno compiles to single binary (better packaging). Same NDJSON protocol. Not yet integrated. 2026-03-06
Splitter overlays for pane resize Fixed-position divs outside CSS Grid (avoids layout interference). Mouse drag updates customColumns/customRows state. Resets on preset change. 2026-03-06
Deno-first sidecar with Node.js fallback SidecarCommand struct abstracts runtime. resolve_sidecar_command() checks Deno first (runs TS directly, no build step), falls back to Node.js. Both bundled in tauri.conf.json resources. 2026-03-06
Session groups/folders group_name column in sessions table with ALTER TABLE migration. Pane.group field in layout store. Collapsible group headers in sidebar. Right-click to set group. 2026-03-06
Auto-update signing key Generated minisign keypair. Pubkey set in tauri.conf.json. Private key for TAURI_SIGNING_PRIVATE_KEY GitHub secret. 2026-03-06
Agent teams: frontend routing only Subagent panes created by frontend dispatcher, not separate sidecar processes. Parent sidecar handles all messages; routing uses SDK's parentId field. Avoids process explosion for nested subagents. 2026-03-06
SUBAGENT_TOOL_NAMES detection Detect subagent spawn by tool_call name ('Agent', 'Task', 'dispatch_agent'). Simple Set lookup, easily extensible. 2026-03-06

Open Questions

  1. Node.js or Deno for sidecar? Resolved: Deno-first with Node.js fallback. SidecarCommand struct in sidecar.rs abstracts the choice. Deno preferred (runs TS directly, compiles to single binary). Falls back to Node.js if Deno not in PATH.
  2. Multi-machine support? Remote agents via WebSocket. Phase 7+ feature.
  3. Agent Teams integration? Phase 7 — frontend routing implemented (subagent pane spawning, parent/child navigation). Needs real-world testing with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1.
  4. Electron escape hatch threshold? If Canvas xterm.js proves >50ms latency on target system with 4 panes, switch to Electron. Benchmark in Phase 2.

Error Handling Strategy

Failure Response
Node.js sidecar crash Detect via process exit code, show error banner, offer restart button
Claude API 529 (overloaded) Exponential backoff in sidecar, show "rate limited" status in pane
API key expired Sidecar reports auth error, prompt user to update key in settings
PTY process exit Show exit code in terminal, offer reconnect for SSH
WebKit2GTK OOM Limit to 4 active xterm.js instances, lazy-init others
Simultaneous resize of N terminals Debounce resize events (100ms), batch PTY resize calls
SDK message format change Adapter layer catches unknown types, logs warning, renders as raw JSON fallback

Testing Strategy

Layer Tool What
SDK adapter Vitest Message parsing, type discrimination, unknown message fallback
Svelte components Svelte testing library Pane rendering, layout responsive breakpoints
Rust backend cargo test PTY lifecycle, sidecar spawn/kill, file watcher debounce
Integration Playwright Full app: open terminal, run command, verify output
Manual Developer testing xterm.js Canvas performance with 4 panes on target hardware

Errors Encountered

Error Cause Fix Date
Blank screen, "rune_outside_svelte" runtime error Store files used .ts extension but contain Svelte 5 $state/$derived runes. Runes only work in .svelte and .svelte.ts files. Compiler silently passes but fails at runtime. Renamed stores to .svelte.ts, updated all import paths to use .svelte suffix 2026-03-06