diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 2f57128..e03bbc4 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -4,7 +4,7 @@ - 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`. -- All 6 phases complete + extras (SSH, ctx, themes, detached mode, auto-updater, shiki, copy/paste, session resume, drag-resize, testing). +- All 6 phases complete + extras (SSH, ctx, themes, detached mode, auto-updater, shiki, copy/paste, session resume, drag-resize, session groups, Deno sidecar, 104 vitest + 29 cargo tests). - Consult Memora (tag: `bterminal`) before making architectural changes. ## Documentation References @@ -26,12 +26,13 @@ - WebKit2GTK has no WebGL — xterm.js must use Canvas addon explicitly. - Agent sessions use `claude` CLI with `--output-format stream-json` (not Agent SDK npm package). All output goes through the adapter layer (`src/lib/adapters/sdk-messages.ts`). -- Node.js sidecar (`sidecar/agent-runner.ts`) spawns claude subprocesses, communicates with Rust via stdio NDJSON. +- Sidecar uses Deno-first + Node.js fallback (`sidecar/agent-runner-deno.ts` preferred, `sidecar/agent-runner.ts` fallback). SidecarCommand struct in sidecar.rs abstracts runtime. Communicates with Rust via stdio NDJSON. - Agent dispatcher (`src/lib/agent-dispatcher.ts`) is a singleton that routes sidecar events to the agent store. - Maximum 4 active xterm.js instances to avoid WebKit2GTK memory issues. - 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 change (fire-and-forget). Restores on app startup via `restoreFromDb()`. +- 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, `SettingsDialog.svelte` component. - Notifications use ephemeral toast system: `notifications.svelte.ts` store (max 5, 4s auto-dismiss), `ToastContainer.svelte` display. Agent dispatcher emits toasts on agent complete/error/crash. diff --git a/CHANGELOG.md b/CHANGELOG.md index 807a0b6..11364c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Session groups/folders: group_name column in sessions table, setPaneGroup in layout store, collapsible group headers in sidebar with arrow/count, right-click pane to set group +- Auto-update signing key: generated minisign keypair, pubkey configured in tauri.conf.json updater section +- Deno-first sidecar: SidecarCommand struct in sidecar.rs, resolve_sidecar_command() prefers Deno (runs TS directly) with Node.js fallback, both runners bundled via tauri.conf.json resources +- Vitest integration tests: layout.test.ts (30 tests), agent-bridge.test.ts (11 tests), agent-dispatcher.test.ts (18 tests) — total 104 vitest tests passing +- E2E test scaffold: v2/tests/e2e/README.md documenting WebDriver approach - Terminal copy/paste: Ctrl+Shift+C copies selection, Ctrl+Shift+V pastes from clipboard to PTY (TerminalPane.svelte) - Terminal theme hot-swap: onThemeChange() callback registry in theme.svelte.ts, open terminals update immediately when flavor changes - Agent tree node click: clicking a tree node scrolls to the corresponding message in the agent pane (scrollIntoView smooth) @@ -21,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - tempfile dev dependency for Rust test isolation ### Changed +- Sidecar manager refactored from Node.js-only to Deno-first with Node.js fallback (SidecarCommand abstraction) +- Session struct: added group_name field with serde default +- SessionDb: added update_group method, list/save queries updated for group_name column +- SessionList sidebar: uses Svelte 5 snippets for grouped pane rendering with collapsible headers - Agent tree NODE_H increased from 32 to 40 to accommodate subtree cost display - release.yml build step now passes TAURI_SIGNING_PRIVATE_KEY and PASSWORD env vars from secrets - release.yml uploads latest.json alongside .deb and .AppImage artifacts diff --git a/CLAUDE.md b/CLAUDE.md index a034e49..081dba2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,11 +27,11 @@ Terminal emulator with SSH and Claude Code session management. v1 (GTK3+VTE Pyth | `docs/progress.md` | Session progress log | | `v2/src-tauri/src/pty.rs` | PTY backend (portable-pty, PtyManager) | | `v2/src-tauri/src/lib.rs` | Tauri commands (pty + agent + session + file + settings) | -| `v2/src-tauri/src/sidecar.rs` | SidecarManager (Node.js lifecycle, NDJSON) | +| `v2/src-tauri/src/sidecar.rs` | SidecarManager (Deno-first + Node.js fallback, SidecarCommand, NDJSON) | | `v2/src-tauri/src/session.rs` | SessionDb (rusqlite, sessions + layout + settings + ssh_sessions) | | `v2/src-tauri/src/watcher.rs` | FileWatcherManager (notify crate, file change events) | | `v2/src-tauri/src/ctx.rs` | CtxDb (read-only access to ~/.claude-context/context.db) | -| `v2/src/lib/stores/layout.svelte.ts` | Layout store (panes, presets, persistence, Svelte 5 runes) | +| `v2/src/lib/stores/layout.svelte.ts` | Layout store (panes, presets, groups, persistence, Svelte 5 runes) | | `v2/src/lib/stores/agents.svelte.ts` | Agent session store (messages, cost) | | `v2/src/lib/components/Terminal/TerminalPane.svelte` | xterm.js terminal pane | | `v2/src/lib/components/Agent/AgentPane.svelte` | Agent session pane (prompt, messages, cost) | @@ -57,12 +57,15 @@ Terminal emulator with SSH and Claude Code session management. v1 (GTK3+VTE Pyth | `v2/src/lib/components/StatusBar/StatusBar.svelte` | Global status bar (pane counts, cost) | | `v2/src/lib/components/Notifications/ToastContainer.svelte` | Toast notification display | | `v2/src/lib/components/Settings/SettingsDialog.svelte` | Settings modal (shell, cwd, max panes, theme) | -| `v2/src/lib/adapters/session-bridge.ts` | Session/layout persistence IPC wrapper | +| `v2/src/lib/adapters/session-bridge.ts` | Session/layout/group persistence IPC wrapper | | `v2/src/lib/components/Markdown/MarkdownPane.svelte` | Markdown file viewer (marked.js + shiki, live reload) | | `v2/sidecar/agent-runner.ts` | Node.js sidecar (spawns claude CLI) | -| `v2/sidecar/agent-runner-deno.ts` | Deno sidecar proof-of-concept (experimental) | -| `v2/src/lib/adapters/sdk-messages.test.ts` | Vitest tests for SDK message adapter | -| `v2/src/lib/utils/agent-tree.test.ts` | Vitest tests for agent tree builder | +| `v2/sidecar/agent-runner-deno.ts` | Deno sidecar (preferred when deno available) | +| `v2/src/lib/adapters/sdk-messages.test.ts` | Vitest tests for SDK message adapter (25 tests) | +| `v2/src/lib/adapters/agent-bridge.test.ts` | Vitest tests for agent IPC bridge (11 tests) | +| `v2/src/lib/agent-dispatcher.test.ts` | Vitest tests for agent dispatcher (18 tests) | +| `v2/src/lib/stores/layout.test.ts` | Vitest tests for layout store (30 tests) | +| `v2/src/lib/utils/agent-tree.test.ts` | Vitest tests for agent tree builder (20 tests) | ## v1 Stack @@ -76,7 +79,7 @@ Terminal emulator with SSH and Claude Code session management. v1 (GTK3+VTE Pyth - Tauri 2.x (Rust backend) + Svelte 5 (frontend) - xterm.js with Canvas addon (no WebGL on WebKit2GTK) - Agent sessions via `claude` CLI subprocess with `--output-format stream-json` -- Node.js sidecar manages claude processes (stdio NDJSON to Rust) +- Sidecar manages claude processes (Deno-first + Node.js fallback, stdio NDJSON to Rust) - portable-pty for terminal management - SQLite session persistence (rusqlite, WAL mode) + layout restore on startup - File watcher (notify crate) for live markdown viewer diff --git a/README.md b/README.md index ab10e0f..81e7fb5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Terminal with session panel (MobaXterm-style), built with GTK 3 + VTE. Catppuccin Mocha theme. -> **v2 all phases complete + extras:** Multi-session Claude agent dashboard using Tauri 2.x + Svelte 5. Features: multi-pane terminal with PTY backend and copy/paste, agent panes with structured output, tree visualization with subtree cost, and session resume, SSH session management, ctx context database viewer, SQLite session persistence with layout restore, live markdown file viewer with Shiki syntax highlighting, global status bar with cost tracking, toast notifications, settings dialog with theme flavors (Catppuccin Latte/Frappe/Macchiato/Mocha) and live hot-swap, detached pane mode (pop-out windows), pane drag-resize handles, auto-updater plugin with CI signing support, CSS Grid tiling, .deb + AppImage packaging, GitHub Actions CI, vitest + cargo test suites. Branch `v2-mission-control`. See [docs/task_plan.md](docs/task_plan.md) for architecture and [docs/phases.md](docs/phases.md) for implementation plan. +> **v2 all phases complete + extras:** Multi-session Claude agent dashboard using Tauri 2.x + Svelte 5. Features: multi-pane terminal with PTY backend and copy/paste, agent panes with structured output, tree visualization with subtree cost and session resume, session groups/folders with collapsible sidebar headers, SSH session management, ctx context database viewer, SQLite session persistence with layout restore, live markdown file viewer with Shiki syntax highlighting, global status bar with cost tracking, toast notifications, settings dialog with theme flavors (Catppuccin Latte/Frappe/Macchiato/Mocha) and live hot-swap, detached pane mode (pop-out windows), pane drag-resize handles, auto-updater plugin with signing key configured, Deno-first sidecar with Node.js fallback, CSS Grid tiling, .deb + AppImage packaging, GitHub Actions CI, 104 vitest + 29 cargo tests. Branch `v2-mission-control`. See [docs/task_plan.md](docs/task_plan.md) for architecture and [docs/phases.md](docs/phases.md) for implementation plan. ![BTerminal](screenshot.png) diff --git a/TODO.md b/TODO.md index 2429f01..bcb9cdf 100644 --- a/TODO.md +++ b/TODO.md @@ -2,20 +2,21 @@ ## Active -- [ ] **Auto-update signing key** -- Generate TAURI_SIGNING_PRIVATE_KEY, set as GitHub repo secret. CI workflow ready (latest.json + signing). -- [ ] **Deno sidecar integration** -- Proof-of-concept done (agent-runner-deno.ts). Needs real claude CLI testing, benchmark vs Node.js, sidecar.rs integration. -- [ ] **E2E testing (Playwright)** -- No e2e tests yet. Test: open terminal, run command, open agent, verify output. -- [ ] **Session groups/folders** -- Organize sessions in sidebar by folder (deferred from Phase 4). +- [ ] **Set TAURI_SIGNING_PRIVATE_KEY secret** -- Private key generated; must be added to GitHub repo settings for auto-update signing to work in CI. +- [ ] **Deno sidecar real-world testing** -- Integrated into sidecar.rs (Deno-first + Node.js fallback). Needs testing with real claude CLI and startup time benchmark vs Node.js. +- [ ] **E2E testing (Playwright/WebDriver)** -- Scaffold at v2/tests/e2e/README.md. Needs display server to run. Test: open terminal, run command, open agent, verify output. +- [ ] **Multi-machine support** -- Remote agents via WebSocket (Phase 7+ feature). +- [ ] **Agent Teams integration** -- Experimental Anthropic feature. Each teammate gets its own pane (Phase 7+ feature). ## Completed +- [x] **Session groups/folders** -- group_name column in sessions table, setPaneGroup in layout store, collapsible group headers in sidebar, right-click to set group. | Done: 2026-03-06 +- [x] **Auto-update signing key** -- Generated minisign keypair, pubkey set in tauri.conf.json. | Done: 2026-03-06 +- [x] **Deno sidecar integration** -- SidecarCommand struct, resolve_sidecar_command() with Deno-first + Node.js fallback, both runners bundled in tauri.conf.json resources. | Done: 2026-03-06 +- [x] **E2E/integration test suite** -- 104 vitest tests (layout 30, agent-bridge 11, agent-dispatcher 18, sdk-messages 25, agent-tree 20) + 29 cargo tests. | Done: 2026-03-06 - [x] **Copy/paste (Ctrl+Shift+C/V)** -- TerminalPane attachCustomKeyEventHandler, C copies selection, V writes clipboard to PTY. | Done: 2026-03-06 - [x] **Terminal theme hot-swap** -- onThemeChange callback registry in theme.svelte.ts, TerminalPane subscribes. All open terminals update. | Done: 2026-03-06 - [x] **Tree node click -> scroll to message** -- handleTreeNodeClick in AgentPane, scrollIntoView smooth. | Done: 2026-03-06 - [x] **Subtree cost display** -- Yellow cost text below each tree node (subtreeCost util, NODE_H 32->40). | Done: 2026-03-06 - [x] **Session resume** -- Follow-up prompt in AgentPane, resume_session_id passed to SDK. | Done: 2026-03-06 - [x] **Pane drag-resize handles** -- Splitter overlays in TilingGrid with mouse drag, 10-90% clamping. | Done: 2026-03-06 -- [x] **Vitest + cargo tests** -- sdk-messages.test.ts, agent-tree.test.ts, session.rs tests, ctx.rs tests. | Done: 2026-03-06 -- [x] **Auto-update CI workflow** -- latest.json generation, signing env vars, artifact upload. | Done: 2026-03-06 -- [x] **Deno sidecar PoC** -- agent-runner-deno.ts proof-of-concept with same NDJSON protocol. | Done: 2026-03-06 -- [x] **Phase 6: Packaging + Distribution** -- install-v2.sh, bundle config, GitHub Actions release. | Done: 2026-03-06 diff --git a/docs/phases.md b/docs/phases.md index 97dc7d5..9400701 100644 --- a/docs/phases.md +++ b/docs/phases.md @@ -20,7 +20,7 @@ bterminal-v2/ src/ main.rs # Tauri app entry pty.rs # PTY management (portable-pty, not plugin) - sidecar.rs # Node.js sidecar lifecycle (spawn, restart, health) + sidecar.rs # Sidecar lifecycle (Deno-first + Node.js fallback, SidecarCommand) watcher.rs # File watcher for markdown viewer session.rs # Session + SSH session persistence (SQLite via rusqlite) ctx.rs # Read-only ctx context DB access @@ -75,6 +75,7 @@ bterminal-v2/ app.css sidecar/ agent-runner.ts # Node.js sidecar entry point + agent-runner-deno.ts # Deno sidecar (preferred when deno available) package.json # Agent SDK dependency esbuild.config.ts # Bundle to single file package.json @@ -168,7 +169,7 @@ bterminal-v2/ - [x] SQLite persistence for sessions (rusqlite with bundled feature) - [x] Session types: terminal, agent, markdown (SSH via terminal args) - [x] Session CRUD: save, delete, update_title, touch (last_used_at) -- [ ] Session groups/folders (deferred — not needed for MVP) +- [x] Session groups/folders — group_name column, setPaneGroup, grouped sidebar with collapsible headers - [x] Remember last layout on restart (preset + pane_ids in layout_state table) - [x] Auto-restore panes on app startup (restoreFromDb in layout store) @@ -226,7 +227,8 @@ bterminal-v2/ - [x] Auto-updater plugin integrated (tauri-plugin-updater Rust + @tauri-apps/plugin-updater npm + updater.ts) - [x] Auto-update latest.json generation in CI (version, platform URL, signature from .sig file) - [x] release.yml: TAURI_SIGNING_PRIVATE_KEY env vars passed to build step -- [ ] Auto-update signing key generation + TAURI_SIGNING_PRIVATE_KEY secret in GitHub repo +- [x] Auto-update signing key generated, pubkey set in tauri.conf.json +- [ ] TAURI_SIGNING_PRIVATE_KEY secret must be set in GitHub repo settings ### System Requirements - Node.js 20+ (for Agent SDK sidecar) diff --git a/docs/progress.md b/docs/progress.md index f627743..607b1b8 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -178,8 +178,42 @@ Architecture decision: Uses `claude` CLI with `--output-format stream-json` inst - [x] session.rs tests: SessionDb CRUD (sessions, SSH sessions, settings, layout), uses tempfile::tempdir() - [x] ctx.rs tests: CtxDb error handling with missing database (conn=None) +### Session: 2026-03-06 (continued) — Session Groups, Auto-Update Key, Deno Sidecar, Tests + +#### Auto-Update Signing Key +- [x] Generated Tauri signing keypair (minisign) +- [x] Set pubkey in tauri.conf.json updater.pubkey field (base64 encoded) +- [x] Private key to be stored as TAURI_SIGNING_PRIVATE_KEY GitHub secret by user + +#### Session Groups/Folders +- [x] Added group_name column to sessions table (session.rs, ALTER TABLE migration with pragma_table_info check) +- [x] Session struct: added group_name field with #[serde(default)] +- [x] SessionDb: update_group(id, group_name) method + save/list queries updated +- [x] Tauri command: session_update_group registered in lib.rs +- [x] Frontend: updateSessionGroup() in session-bridge.ts +- [x] Layout store: Pane.group? field, setPaneGroup(id, group) function in layout.svelte.ts +- [x] SessionList.svelte: grouped sidebar with collapsible headers (arrow + count), right-click to set group via prompt() +- [x] Svelte 5 snippet pattern used for paneItem to avoid duplication across grouped/ungrouped rendering + +#### Deno Sidecar Integration (upgraded from PoC) +- [x] SidecarCommand struct in sidecar.rs: { program, args } abstracts runtime choice +- [x] resolve_sidecar_command(): Deno-first resolution (checks agent-runner-deno.ts + deno availability), Node.js fallback +- [x] Both sidecar runners bundled in tauri.conf.json resources array +- [x] Graceful fallback: if Deno binary not found in PATH, falls back to Node.js with log warning + +#### E2E/Integration Tests +- [x] layout.test.ts (30 tests): addPane, removePane, focusPane, setPreset, autoPreset, getGridTemplate, getPaneGridArea, renamePaneTitle, setPaneGroup +- [x] agent-bridge.test.ts (11 tests): Tauri IPC mock pattern with vi.hoisted(), invoke/listen wrappers +- [x] agent-dispatcher.test.ts (18 tests): sidecar event routing, crash detection, vi.useFakeTimers() for async setTimeout +- [x] sdk-messages.test.ts rewrite (25 tests): removed unused ErrorContent import +- [x] E2E scaffold: v2/tests/e2e/README.md documenting WebDriver approach +- [x] Total: 104 vitest tests + 29 cargo tests, all passing + +Build status: TypeScript 0 errors, Rust 0 errors (1 pre-existing warning), all tests green. + ### Next Steps -- [ ] Auto-update signing key generation + TAURI_SIGNING_PRIVATE_KEY secret in GitHub repo -- [ ] Deno sidecar: test with real claude CLI, benchmark vs Node.js, integrate with sidecar.rs -- [ ] E2E testing with Playwright -- [ ] Session groups/folders in sidebar +- [ ] Set TAURI_SIGNING_PRIVATE_KEY secret in GitHub repo settings +- [ ] Deno sidecar: test with real claude CLI, benchmark startup time vs Node.js +- [ ] E2E testing with Playwright/WebDriver (when display server available) +- [ ] Multi-machine support (remote agents via WebSocket) +- [ ] Agent Teams integration diff --git a/docs/task_plan.md b/docs/task_plan.md index f143fab..f8aef31 100644 --- a/docs/task_plan.md +++ b/docs/task_plan.md @@ -136,10 +136,13 @@ See [phases.md](phases.md) for the full phased implementation plan (Phases 1-6). | 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 | ## Open Questions -1. **Node.js or Deno for sidecar?** Node.js has the SDK package. Deno would be a single binary (better packaging) but needs SDK compatibility testing. → Start Node.js, evaluate Deno later. +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?** Experimental Anthropic feature. Natural fit but adds complexity. Phase 7+. 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.