16 KiB
BTerminal v3 — Mission Control Redesign
Goal
Transform BTerminal from a multi-pane terminal/agent tool into a multi-project mission control — a helm for managing multiple development projects simultaneously, each with its own Claude agent session, team agents, terminals, and settings.
Status: All Phases Complete (1-10) — Rev 2
Core Concept
Project Groups are workspaces. Each group has up to 5 projects arranged horizontally. One group visible at a time. Projects have their own Claude subscription, working directory, icon, and settings. The app is a dashboard for orchestrating Claude agents across a portfolio of projects.
Key Mental Model
BTerminal v2: Terminal emulator with agent sessions (panes in a grid)
BTerminal v3: Project orchestration dashboard (projects in a workspace)
User Requirements
- Projects arranged in project groups (many groups, switch between them)
- Each group has up to 5 projects shown horizontally
- Group/project config via main menu (command palette / hidden drawer, Ctrl+K)
- Per-project settings: Claude subscription, working dir, icon (nerd font), name, identifier, description, enabled
- Project group = workspace on screen
- Each project box: Claude session (default, resume previous) + team agents (right) + terminal tabs (below)
- 4 workspace tabs: Sessions | Docs | Context | Settings
- App launchable with
--group <name>CLI arg - JSON config file defines all groups (
~/.config/bterminal/groups.json) - Session continuity: resume previous + restore history visually
- SSH sessions: spawnable within a project's terminal tabs
- ctx viewer: workspace tab #3
Architecture (Post-Adversarial Review)
Adversarial Review Summary
3 agents reviewed the architecture: Architect (advocate), Devil's Advocate (attacker), UX+Performance Specialist.
12 issues identified by Devil's Advocate. Resolutions:
| # | Issue | Severity | Resolution |
|---|---|---|---|
| 1 | xterm.js 4-instance ceiling (WebKit2GTK OOM) | Critical | Lazy-init + scrollback serialization. Budget: 4 active xterm, unlimited suspended (text buffer). Enforced in code. |
| 2 | Single sidecar = SPOF for all projects | Critical | Accept for v3.0 (existing crash recovery). Per-project pool deferred to v3.1 if needed. |
| 3 | Session identity collision (sdkSessionId not persisted) | Major | Persist sdkSessionId in SQLite project_agent_state table. Per-project CLAUDE_CONFIG_DIR isolation. |
| 4 | Layout store has no workspace concept | Critical | Full rewrite: workspace.svelte.ts replaces layout.svelte.ts. |
| 5 | 384px per project unusable on 1920px | Major | Adaptive: compute visible count from viewport width (Math.floor(width / 520)). 5@5120px, 3@1920px, scroll-snap for rest. min-width 480px. |
| 6 | JSON config + SQLite = split-brain | Major | JSON for groups/projects config (human-editable). SQLite for session state. JSON loaded at startup only, no hot-reload. |
| 7 | Agent dispatcher is global singleton, no project scoping | Major | Add projectId to AgentSession. Dispatcher routes by project. Per-project cleanup on workspace switch. |
| 8 | Markdown discovery undefined | Minor | Priority list: CLAUDE.md, README.md, docs/*.md (max 20). Rust command scans with depth limit. |
| 9 | Keyboard shortcut conflicts (3 layers) | Major | Shortcut manager: Terminal layer (focused only), Workspace layer (Ctrl+1-5), App layer (Ctrl+K, Ctrl+G). |
| 10 | Remote machine support orphaned | Major | Elevate to project level (project.remote_machine_id). Defer integration to v3.1. |
| 11 | No graceful degradation for broken projects | Major | Project health state: healthy/degraded/unavailable/error. Colored dot indicator. |
| 12 | Flat event stream wastes CPU for hidden projects | Minor | Buffer messages for inactive workspace projects. Flush on activation. |
Data Model
Project Group Config (~/.config/bterminal/groups.json)
{
"version": 1,
"groups": [
{
"id": "work-ai",
"name": "AI Projects",
"projects": [
{
"id": "bterminal",
"name": "BTerminal",
"identifier": "bterminal",
"description": "Terminal emulator with Claude integration",
"icon": "\uf120",
"cwd": "/home/hibryda/code/ai/BTerminal",
"profile": "default",
"enabled": true
}
]
}
],
"activeGroupId": "work-ai"
}
TypeScript Types (v2/src/lib/types/groups.ts)
export interface ProjectConfig {
id: string;
name: string;
identifier: string;
description: string;
icon: string;
cwd: string;
profile: string;
enabled: boolean;
}
export interface GroupConfig {
id: string;
name: string;
projects: ProjectConfig[]; // max 5
}
export interface GroupsFile {
version: number;
groups: GroupConfig[];
activeGroupId: string;
}
SQLite Schema Additions
ALTER TABLE sessions ADD COLUMN project_id TEXT DEFAULT '';
CREATE TABLE IF NOT EXISTS agent_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
project_id TEXT NOT NULL,
sdk_session_id TEXT,
message_type TEXT NOT NULL,
content TEXT NOT NULL,
parent_id TEXT,
created_at INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
);
CREATE INDEX idx_agent_messages_session ON agent_messages(session_id);
CREATE INDEX idx_agent_messages_project ON agent_messages(project_id);
CREATE TABLE IF NOT EXISTS project_agent_state (
project_id TEXT PRIMARY KEY,
last_session_id TEXT NOT NULL,
sdk_session_id TEXT,
status TEXT NOT NULL,
cost_usd REAL DEFAULT 0,
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
last_prompt TEXT,
updated_at INTEGER NOT NULL
);
Component Architecture
Component Tree
App.svelte [REWRITTEN]
├── CommandPalette.svelte [NEW]
├── GlobalTabBar.svelte [NEW] Sessions | Docs | Context | Settings
├── [Tab: Sessions]
│ └── ProjectGrid.svelte [NEW] Horizontal flex + scroll-snap
│ └── ProjectBox.svelte [NEW] Per-project container
│ ├── ProjectHeader.svelte [NEW] Icon + name + status dot
│ ├── ClaudeSession.svelte [NEW, from AgentPane] Main session
│ ├── TeamAgentsPanel.svelte [NEW] Right panel for subagents
│ │ └── AgentCard.svelte [NEW] Compact subagent view
│ └── TerminalTabs.svelte [NEW] Tabbed terminals
│ ├── TabBar.svelte [NEW]
│ └── TerminalPane.svelte [SURVIVES]
├── [Tab: Docs]
│ └── DocsTab.svelte [NEW]
│ ├── MdFilePicker.svelte [NEW]
│ └── MarkdownPane.svelte [SURVIVES]
├── [Tab: Context]
│ └── ContextPane.svelte [SURVIVES, extracted from pane]
├── [Tab: Settings]
│ └── SettingsTab.svelte [NEW]
│ ├── ProjectSettingsEditor.svelte [NEW]
│ └── GlobalSettings.svelte [NEW]
├── StatusBar.svelte [MODIFIED]
└── ToastContainer.svelte [SURVIVES]
What Dies
| v2 Component/Store | Reason |
|---|---|
| TilingGrid.svelte | Replaced by ProjectGrid |
| PaneContainer.svelte | Fixed project box structure |
| SessionList.svelte (sidebar) | No sidebar; project headers replace |
| SshSessionList.svelte | Absorbed into TerminalTabs |
| SettingsDialog.svelte | Replaced by SettingsTab |
| AgentPane.svelte | Split into ClaudeSession + TeamAgentsPanel |
| layout.svelte.ts | Replaced by workspace.svelte.ts |
| layout.test.ts | Replaced by workspace tests |
What Survives
TerminalPane, MarkdownPane, AgentTree, ContextPane, StatusBar, ToastContainer, theme store, notifications store, agents store (modified), all adapters (agent-bridge, pty-bridge, claude-bridge, sdk-messages, session-bridge, ctx-bridge, ssh-bridge), all Rust backend (sidecar, pty, session, ctx, watcher), highlight utils.
Layout System
Project Grid (Flexbox + scroll-snap)
.project-grid {
display: flex;
gap: 4px;
height: 100%;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.project-box {
flex: 0 0 calc((100% - (N-1) * 4px) / N);
scroll-snap-align: start;
min-width: 480px;
}
N computed from viewport: Math.min(projects.length, Math.max(1, Math.floor(containerWidth / 520)))
Project Box Internal Layout
┌─ ProjectHeader (28px) ──────────────────┐
├─────────────────────┬───────────────────┤
│ ClaudeSession │ TeamAgentsPanel │
│ (flex: 1) │ (240px or overlay)│
├─────────────────────┴───────────────────┤
│ [Tab1] [Tab2] [+] TabBar 26px │
├─────────────────────────────────────────┤
│ Terminal content (xterm or scrollback) │
└─────────────────────────────────────────┘
Team panel: inline at >2560px, overlay at <2560px. Collapsed when no subagents.
Responsive Breakpoints
| Width | Visible Projects | Team Panel |
|---|---|---|
| 5120px+ | 5 | inline 240px |
| 3840px | 4 | inline 200px |
| 2560px | 3 | overlay |
| 1920px | 3 | overlay |
| <1600px | 1 + project tabs | overlay |
xterm.js Budget: 4 Active Instances
| State | xterm? | Memory |
|---|---|---|
| Active-Focused | Yes | ~20MB |
| Active-Background | Yes (if budget allows) | ~20MB |
| Suspended | No (HTML pre scrollback) | ~200KB |
| Uninitialized | No (placeholder) | 0 |
On focus: serialize least-recent xterm scrollback, destroy it, create new for focused tab, reconnect PTY.
Project Accent Colors (Catppuccin)
| Slot | Color | Variable |
|---|---|---|
| 1 | Blue | --ctp-blue |
| 2 | Green | --ctp-green |
| 3 | Mauve | --ctp-mauve |
| 4 | Peach | --ctp-peach |
| 5 | Pink | --ctp-pink |
Sidecar Strategy
Single shared sidecar (unchanged from v2). Per-project isolation via:
cwdper query (already implemented)claude_config_dirper query (already implemented)session_idrouting (already implemented)
No sidecar changes needed for v3.0.
Keyboard Shortcuts
| Shortcut | Action | Layer |
|---|---|---|
| Ctrl+K | Command palette | App |
| Ctrl+G | Switch group (palette filtered) | App |
| Ctrl+1..5 | Focus project by index | App |
| Alt+1..4 | Switch workspace tab | App |
| Ctrl+N | New terminal in focused project | Workspace |
| Ctrl+Shift+N | New agent query | Workspace |
| Ctrl+Tab | Next terminal tab | Project |
| Ctrl+W | Close terminal tab | Project |
| Ctrl+, | Settings tab | App |
| Ctrl+Shift+C/V | Copy/paste in terminal | Terminal |
Implementation Phases
Phase 1: Data Model + Config [status: complete]
Milestone: Groups config loads/saves, SQLite migrations pass, workspace store works
- Create
v2/src/lib/types/groups.ts(TypeScript interfaces) - Create
v2/src-tauri/src/groups.rs(Rust structs + load/save) - Add
groups_load,groups_saveTauri commands to lib.rs - SQLite migrations in session.rs: project_id column, agent_messages table, project_agent_state table
- Create
v2/src/lib/adapters/groups-bridge.ts(IPC wrapper) - Create
v2/src/lib/stores/workspace.svelte.ts(replaces layout.svelte.ts) - Add
--groupCLI argument parsing in main.rs - Write tests for groups load/save and workspace store (24 vitest + 7 cargo)
Phase 2: Project Box Shell [status: complete]
Milestone: Project boxes render horizontally with headers, workspace tabs switch
- Create GlobalTabBar.svelte (Sessions | Docs | Context | Settings)
- Create ProjectGrid.svelte (flex + scroll-snap container)
- Create ProjectBox.svelte (CSS grid: header | session-area | terminal-area)
- Create ProjectHeader.svelte (icon + name + status dot + accent color)
- Rewrite App.svelte (GlobalTabBar + tab content + StatusBar)
- Create CommandPalette.svelte, DocsTab.svelte, ContextTab.svelte, SettingsTab.svelte
- CSS for responsive project count + accent colors
Phase 3: Claude Session Integration [status: complete]
Milestone: Claude sessions run within project boxes, per-project profile isolation
- Create ClaudeSession.svelte (wraps AgentPane, passes project cwd/profile/config_dir)
Phase 4: Terminal Tabs [status: complete]
Milestone: Each project has tabbed terminals with xterm budget enforcement
- Create TerminalTabs.svelte (tab state per project, shell/SSH/agent tab types)
Phase 5: Team Agents Panel [status: complete]
Milestone: Subagents appear in right panel, not separate panes. MVP complete.
- Create TeamAgentsPanel.svelte (right side of session area)
- Create AgentCard.svelte (compact subagent: status, messages, cost)
--- MVP BOUNDARY ---
Phase 6: Session Continuity [status: complete]
- Persist agent messages to SQLite on session complete (agent-dispatcher persistSessionForProject)
- Persist sdkSessionId in project_agent_state
- On startup, load cached messages per project (ClaudeSession restoreMessagesFromRecords)
- Session-project mapping via registerSessionProject()
Phase 7: Command Palette + Group Switching [status: complete]
- CommandPalette.svelte (overlay, Ctrl+K, fuzzy search)
- Group list with switching
- Workspace teardown on switch (clearAllAgentSessions + terminal tabs reset)
Phase 8: Docs Tab [status: complete]
- DocsTab.svelte with split layout (file picker + MarkdownPane)
- Auto-discovers markdown files per project via discoverMarkdownFiles
Phase 9: Settings Tab [status: complete]
- SettingsTab.svelte (group CRUD + project CRUD)
- 5-project limit enforcement
Phase 10: Polish + Cleanup [status: complete]
- Remove dead v2 components (TilingGrid, PaneContainer, PaneHeader, SessionList, SshSessionList, SshDialog, SettingsDialog)
- Remove empty directories (Layout/, Sidebar/, Settings/, SSH/)
- Rewrite StatusBar for workspace store
- Fix subagent routing (skip layout pane for project-scoped agents)
- All 138 vitest + cargo tests pass, vite build succeeds
Decisions Log
| Decision | Rationale | Date |
|---|---|---|
| JSON for groups config, SQLite for session state | JSON is human-editable, shareable, version-controllable. SQLite for ephemeral runtime state. Load at startup only. | 2026-03-07 |
| Adaptive project count from viewport width | 5@5120px, 3@1920px, scroll-snap for overflow. min-width 480px. Better than forcing 5 at all sizes. | 2026-03-07 |
| Single shared sidecar (v3.0) | Existing multiplexed protocol handles concurrent sessions. Per-project pool deferred to v3.1 if crash isolation needed. Saves ~200MB RAM. | 2026-03-07 |
| xterm budget: 4 active, unlimited suspended | WebKit2GTK OOM at ~5 instances. Serialize scrollback to text buffer, destroy xterm, recreate on focus. PTY stays alive. | 2026-03-07 |
| Flexbox + scroll-snap over CSS Grid | Allows horizontal scroll on narrow screens. Scroll-snap gives clean project-to-project scrolling. | 2026-03-07 |
| Team panel: inline >2560px, overlay <2560px | Adapts to available space. Collapsed when no subagents running. | 2026-03-07 |
| 4 workspace tabs (Sessions/Docs/Context/Settings) | ctx viewer as separate tab per user requirement. Docs auto-discovered from projects. | 2026-03-07 |
| Project accent colors from Catppuccin palette | Visual distinction: blue/green/mauve/peach/pink per slot 1-5. Applied to border + header tint. | 2026-03-07 |
| Remote machines deferred to v3.1 | Elevate to project level (project.remote_machine_id) but don't implement in MVP. | 2026-03-07 |
| Keyboard shortcut layers: App > Workspace > Terminal | Prevents conflicts. Terminal captures raw keys only when focused. App layer uses Ctrl+K/G. | 2026-03-07 |
| AgentPane splits into ClaudeSession + TeamAgentsPanel | Team agents shown inline in right panel, not as separate panes. Saves xterm/pane slots. | 2026-03-07 |
| Unmount/remount on group switch | Serialize xterm scrollbacks, destroy, remount new group. <100ms perceived. Frees ~80MB. | 2026-03-07 |
Errors Encountered
| Error | Cause | Fix | Date |
|---|