# BTerminal — Claude Behavioral Guide ## Workflow - v1 is a single-file Python app (`bterminal.py`). Changes are localized. - v2 docs are in `docs/`. Architecture decisions are in `docs/task_plan.md`. - v2 Phases 1-7 + multi-machine (A-D) + profiles/skills complete. Extras: SSH, ctx, themes, detached mode, auto-updater, shiki, copy/paste, session resume, drag-resize, session groups, Deno sidecar, Claude profiles, skill discovery. - v3 Mission Control (All Phases 1-10 Complete): project groups, workspace store, 12 Workspace components, session continuity, workspace teardown, dead v2 component cleanup. 139 vitest + 34 cargo tests. - v3 docs: `docs/v3-task_plan.md`, `docs/v3-findings.md`, `docs/v3-progress.md`. - Consult Memora (tag: `bterminal`) before making architectural changes. ## Documentation References - Architecture & decisions: [docs/task_plan.md](../docs/task_plan.md) - Implementation phases: [docs/phases.md](../docs/phases.md) - Research findings: [docs/findings.md](../docs/findings.md) - Progress log: [docs/progress.md](../docs/progress.md) - v3 architecture: [docs/v3-task_plan.md](../docs/v3-task_plan.md) - v3 findings: [docs/v3-findings.md](../docs/v3-findings.md) - v3 progress: [docs/v3-progress.md](../docs/v3-progress.md) ## Rules - Do not modify v1 code (`bterminal.py`) unless explicitly asked — it is production-stable. - v2/v3 work goes on the `v2-mission-control` branch, not master. - v2 architecture decisions must reference `docs/task_plan.md` Decisions Log. - v3 architecture decisions must reference `docs/v3-task_plan.md` Decisions Log. - When adding new decisions, append to the Decisions Log table with date. - Update `docs/progress.md` after each significant work session. ## Key Technical Constraints - WebKit2GTK has no WebGL — xterm.js must use Canvas addon explicitly. - Agent sessions use `@anthropic-ai/claude-agent-sdk` query() function (migrated from raw CLI spawning due to piped stdio hang bug). SDK handles subprocess management internally. All output goes through the adapter layer (`src/lib/adapters/sdk-messages.ts`) — SDK message format matches CLI stream-json. - Sidecar uses a single pre-built bundle (`sidecar/dist/agent-runner.mjs`) that runs on both Deno and Node.js. SidecarCommand struct in sidecar.rs abstracts runtime (Deno preferred for faster startup, Node.js fallback). Communicates with Rust via stdio NDJSON. Claude CLI auto-detected at startup via `findClaudeCli()` — checks ~/.local/bin/claude, ~/.claude/local/claude, /usr/local/bin/claude, /usr/bin/claude, then `which claude`. Path passed to SDK via `pathToClaudeCodeExecutable` option. Agents error immediately if CLI not found. CLAUDE* env var stripping is dual-layer: (1) Rust SidecarManager uses env_clear() + envs(clean_env) to strip all CLAUDE* vars before spawning the sidecar process, (2) JS runner also strips via SDK's `env` option (defense-in-depth). Without this, nesting detection triggers when BTerminal is launched from a Claude Code terminal. Session stop uses AbortController.abort() (not process.kill()). `agent-runner-deno.ts` exists as standalone alternative runner but is NOT used by SidecarManager. - AgentPane does NOT stop agents in onDestroy — onDestroy fires on layout remounts, not just explicit close. Stop-on-close is handled externally (was TilingGrid in v2, now workspace teardown in v3). - Agent dispatcher (`src/lib/agent-dispatcher.ts`) is a singleton that routes sidecar events to the agent store. Handles subagent routing (project-scoped sessions skip layout pane, render in TeamAgentsPanel; detached mode creates layout pane). Session persistence via registerSessionProject() + persistSessionForProject() (saves state + messages to SQLite on complete). - AgentQueryOptions supports `permission_mode` field (flows Rust -> sidecar -> SDK). Defaults to 'bypassPermissions', supports 'default' mode. allowDangerouslySkipPermissions is conditionally set. Also supports: `setting_sources` (defaults to ['user', 'project']), `system_prompt`, `model`, `claude_config_dir` (for multi-account), `additional_directories`. - Claude profiles: claude_list_profiles() reads ~/.config/switcher/profiles/ with profile.toml metadata. Profile set per-project in Settings (project.profile field), passed through ClaudeSession -> AgentPane `profile` prop -> resolved to config_dir for SDK. Profile name shown as info-only in ProjectHeader. - ProjectBox has project-level tab bar: Claude | Files | Context. Content area switches between ClaudeSession+TeamAgentsPanel, ProjectFiles (project-scoped markdown viewer), and ContextPane. ProjectFiles.svelte accepts cwd/projectName props (not workspace store). - ProjectHeader shows CWD (ellipsized from START via `direction: rtl`) + profile name as info-only text on right side. AgentPane no longer has DIR/ACC toolbar — CWD and profile are props from parent. - Skill discovery: claude_list_skills() reads ~/.claude/skills/ (dirs with SKILL.md or .md files). claude_read_skill() reads content. AgentPane `/` prefix triggers autocomplete menu. Skill content injected as prompt via expandSkillPrompt(). - claude-bridge.ts adapter wraps profile/skill Tauri commands (ClaudeProfile, ClaudeSkill interfaces). - Sidecar build: `npm run build:sidecar` bundles SDK into agent-runner.mjs via esbuild (no --external, SDK included in bundle). - Agent preview terminal: `AgentPreviewPane.svelte` is a read-only xterm.js terminal (disableStdin:true) that subscribes to an agent session's messages via `$derived(getAgentSession(sessionId))` and renders tool calls/results in real-time. Bash commands shown as cyan `❯ cmd`, file ops as yellow `[Read] path`, results as plain text (80-line truncation), errors in red. Spawned via 👁 button in TerminalTabs (appears when agentSessionId prop is set). TerminalTab type: `'agent-preview'` with `agentSessionId` field. Deduplicates — won't create two previews for the same session. ProjectBox passes mainSessionId to TerminalTabs. - Maximum 4 active xterm.js instances to avoid WebKit2GTK memory issues. Agent preview uses disableStdin and no PTY so is lighter, but still counts. - Store files using Svelte 5 runes (`$state`, `$derived`) MUST have `.svelte.ts` extension (not `.ts`). Import with `.svelte` suffix. Plain `.ts` compiles but fails at runtime with "rune_outside_svelte". - Session persistence uses rusqlite (bundled) with WAL mode. Data dir: `dirs::data_dir()/bterminal/sessions.db`. - Layout store persists to SQLite on every addPane/removePane/setPreset/setPaneGroup change (fire-and-forget). Restores on app startup via `restoreFromDb()`. - Session groups: Pane.group? field in layout store, group_name column in sessions table, collapsible group headers in sidebar. Right-click pane to set group. - File watcher uses notify crate v6, watches parent directory (NonRecursive), emits `file-changed` Tauri events. - Settings use key-value `settings` table in SQLite (session.rs). Frontend: `settings-bridge.ts` adapter. v3 uses SettingsTab.svelte rendered in sidebar drawer panel (v2 SettingsDialog.svelte deleted in P10). SettingsTab has two sections: Global (single-column layout, split into Appearance [theme dropdown, UI font dropdown with sans-serif options + size stepper, Terminal font dropdown with monospace options + size stepper] and Defaults [shell, CWD] — all custom themed dropdowns, no native `