agent-orchestrator/docs/progress/v3.md
Hibryda a89e2b9f69 docs: restructure docs — eliminate v3- prefix, merge findings, create decisions.md
Merge v3-task_plan.md content into architecture.md (data model, layout system,
keyboard shortcuts) and new decisions.md (22-entry categorized decisions log).
Merge v3-findings.md into unified findings.md (16 sections covering all research).
Move progress logs to progress/ subdirectory (v2.md, v3.md, v2-archive.md).
Rename v3-release-notes.md to release-notes.md. Update all cross-references.
Delete v3-task_plan.md and v3-findings.md (content fully incorporated).
2026-03-14 02:51:13 +01:00

1076 lines
63 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# v3 Progress Log
### Session: 2026-03-07 — Architecture Planning + MVP Implementation (Phases 1-5)
#### Phase: Adversarial Design Review
- [x] Launch 3 architecture agents (Architect, Devil's Advocate, UX+Performance Specialist)
- [x] Collect findings — 12 issues identified, all resolved
- [x] Produce final architecture plan in docs/v3-task_plan.md
- [x] Create 10-phase implementation plan
#### Phase 1: Data Model + Config
- [x] Created `v2/src/lib/types/groups.ts` — TypeScript interfaces (ProjectConfig, GroupConfig, GroupsFile)
- [x] Created `v2/src-tauri/src/groups.rs` — Rust structs + load/save groups.json
- [x] Added `groups_load`, `groups_save` Tauri commands to lib.rs
- [x] SQLite migrations in session.rs: project_id column, agent_messages table, project_agent_state table
- [x] Created `v2/src/lib/adapters/groups-bridge.ts` (IPC wrapper)
- [x] Created `v2/src/lib/stores/workspace.svelte.ts` (replaces layout.svelte.ts, Svelte 5 runes)
- [x] Added `--group` CLI argument parsing in main.rs
- [x] Wrote 24 vitest tests for workspace store (workspace.test.ts)
- [x] Wrote cargo tests for groups load/save/default
#### Phase 2: Project Box Shell
- [x] Created GlobalTabBar.svelte (Sessions | Docs | Context | Settings)
- [x] Created ProjectGrid.svelte (flex + scroll-snap container)
- [x] Created ProjectBox.svelte (CSS grid: header | session-area | terminal-area)
- [x] Created ProjectHeader.svelte (icon + name + status dot + accent color)
- [x] Rewrote App.svelte (GlobalTabBar + tab content + StatusBar, no sidebar/TilingGrid)
- [x] Created CommandPalette.svelte (Ctrl+K overlay with fuzzy search)
- [x] Created DocsTab.svelte (markdown file browser per project)
- [x] Created ContextTab.svelte (wrapper for ContextPane)
- [x] Created SettingsTab.svelte (per-project + global settings editor)
- [x] CSS for responsive project count + Catppuccin accent colors
#### Phase 3: Claude Session Integration
- [x] Created ClaudeSession.svelte (wraps AgentPane, passes project cwd/profile/config_dir)
#### Phase 4: Terminal Tabs
- [x] Created TerminalTabs.svelte (tab bar + content, shell/SSH/agent tab types)
#### Phase 5: Team Agents Panel
- [x] Created TeamAgentsPanel.svelte (right panel for subagents)
- [x] Created AgentCard.svelte (compact subagent view: status, messages, cost)
#### Bug Fix
- [x] Fixed AgentPane Svelte 5 event modifier syntax: `on:click` -> `onclick` (Svelte 5 requires lowercase event attributes)
#### Verification
- All 138 vitest tests pass (114 existing + 24 new workspace tests)
- All 36 cargo tests pass (29 existing + 7 new groups tests)
- Vite build succeeds
### Session: 2026-03-07 — Phases 6-10 Completion
#### Phase 6: Session Continuity
- [x] Added `persistSessionForProject()` to agent-dispatcher — saves agent state + messages to SQLite on session complete
- [x] Added `registerSessionProject()` — maps sessionId -> projectId for persistence routing
- [x] Added `sessionProjectMap` (Map<string, string>) in agent-dispatcher
- [x] Updated ClaudeSession.svelte: `restoreMessagesFromRecords()` restores cached messages into agent store on mount
- [x] ClaudeSession loads previous state via `loadProjectAgentState()`, restores session ID and messages
- [x] Added `getAgentSession()` export to agents store
#### Phase 7: Workspace Teardown on Group Switch
- [x] Added `clearAllAgentSessions()` to agents store (clears sessions array)
- [x] Updated `switchGroup()` in workspace store to call `clearAllAgentSessions()` + reset terminal tabs
- [x] Updated workspace.test.ts to mock `clearAllAgentSessions`
#### Phase 10: Dead Component Removal + Polish
- [x] Deleted `TilingGrid.svelte` (328 lines), `PaneContainer.svelte` (113 lines), `PaneHeader.svelte` (44 lines)
- [x] Deleted `SessionList.svelte` (374 lines), `SshSessionList.svelte` (263 lines), `SshDialog.svelte` (281 lines), `SettingsDialog.svelte` (433 lines)
- [x] Removed empty directories: Layout/, Sidebar/, Settings/, SSH/
- [x] Rewrote StatusBar.svelte for workspace store (group name, project count, agent count, "BTerminal v3" label)
- [x] Fixed subagent routing in agent-dispatcher: project-scoped sessions skip layout pane creation (subagents render in TeamAgentsPanel instead)
- [x] Updated v3-task_plan.md to mark all 10 phases complete
#### Verification
- All 138 vitest tests pass (including updated workspace tests with clearAllAgentSessions mock)
- All 36 cargo tests pass
- Vite build succeeds
- ~1,836 lines of dead code removed
### Session: 2026-03-07 — SettingsTab Global Settings + Cleanup
#### SettingsTab Global Settings Section
- [x] Added "Global" section to SettingsTab.svelte with three settings:
- Theme flavor dropdown (Catppuccin Latte/Frappe/Macchiato/Mocha) via `setFlavor()` from theme store
- Default shell text input (persisted via `setSetting('default_shell', ...)`)
- Default CWD text input (persisted via `setSetting('default_cwd', ...)`)
- [x] Global settings load on mount via `getSetting()` from settings-bridge
- [x] Added imports: `onMount`, `getSetting`/`setSetting`, `getCurrentFlavor`/`setFlavor`, `CatppuccinFlavor` type
#### A11y Fixes
- [x] Changed project field labels from `<div class="project-field"><label>` to wrapping `<label class="project-field"><span class="field-label">` pattern — proper label/input association
- [x] Global settings use `id`/`for` label association (e.g., `id="theme-flavor"`, `id="default-shell"`)
#### CSS Cleanup
- [x] Removed unused `.project-field label` selector (replaced by `.field-label`)
- [x] Simplified `.project-field input[type="text"], .project-field input:not([type])` to `.project-field input:not([type="checkbox"])`
#### Rust Cleanup (committed separately)
- [x] Removed dead `update_ssh_session()` method from session.rs and its test
- [x] Fixed stale TilingGrid comment in AgentPane.svelte
### Session: 2026-03-07 — Multi-Theme System (7 Editor Themes)
#### Theme System Generalization
- [x] Generalized `CatppuccinFlavor` type to `ThemeId` union type (11 values)
- [x] Added 7 new editor themes: VSCode Dark+, Atom One Dark, Monokai, Dracula, Nord, Solarized Dark, GitHub Dark
- [x] Added `ThemePalette` interface (26-color slots) — all themes map to same slots
- [x] Added `ThemeMeta` interface (id, label, group, isDark) for UI metadata
- [x] Added `THEME_LIST: ThemeMeta[]` with group metadata ('Catppuccin' or 'Editor')
- [x] Added `ALL_THEME_IDS: ThemeId[]` derived from THEME_LIST for validation
- [x] Deprecated `CatppuccinFlavor`, `CatppuccinPalette`, `FLAVOR_LABELS`, `ALL_FLAVORS` (kept as backwards compat aliases)
#### Theme Store Updates
- [x] `getCurrentTheme(): ThemeId` replaces `getCurrentFlavor()` as primary getter
- [x] `setTheme(theme: ThemeId)` replaces `setFlavor()` as primary setter
- [x] `initTheme()` validates saved theme against `ALL_THEME_IDS`
- [x] Deprecated `getCurrentFlavor()` and `setFlavor()` with delegation wrappers
#### SettingsTab Theme Selector
- [x] Theme dropdown uses `<optgroup>` per theme group (Catppuccin, Editor)
- [x] `themeGroups` derived from `THEME_LIST` using Map grouping
- [x] `handleThemeChange()` replaces direct `setFlavor()` call
- [x] Fixed input overflow in `.setting-row` with `min-width: 0`
#### Design Decision
All editor themes map to the same `--ctp-*` CSS custom property names (26 vars). This means every component works unchanged — no component-level theme awareness needed. Each theme provides its own mapping of colors to the 26 semantic slots.
#### Verification
- All 138 vitest + 35 cargo tests pass
### Session: 2026-03-07 — Deep Dark Theme Group (6 Themes)
#### New Theme Group: Deep Dark
- [x] Added 6 new "Deep Dark" themes to `v2/src/lib/styles/themes.ts`:
- Tokyo Night (base: #1a1b26)
- Gruvbox Dark (base: #1d2021)
- Ayu Dark (base: #0b0e14, near-black)
- Poimandres (base: #1b1e28)
- Vesper (base: #101010, warm dark)
- Midnight (base: #000000, pure OLED black)
- [x] Extended `ThemeId` union type from 11 to 17 values
- [x] Added `THEME_LIST` entries with `group: 'Deep Dark'`
- [x] Added all 6 palette definitions (26 colors each) mapped to --ctp-* slots
- [x] Total themes: 17 across 3 groups (Catppuccin 4, Editor 7, Deep Dark 6)
#### Verification
- No test changes needed — theme palettes are data-only, no logic changes
### Session: 2026-03-07 — Custom Theme Dropdown
#### SettingsTab Theme Picker Redesign
- [x] Replaced native `<select>` with custom themed dropdown in SettingsTab.svelte
- [x] Dropdown trigger shows color swatch (base color from getPalette()) + theme label + arrow indicator
- [x] Dropdown menu groups themes by category (Catppuccin/Editor/Deep Dark) with styled uppercase headers
- [x] Each option shows: color swatch + label + 4 accent color dots (red/green/blue/yellow)
- [x] Active theme highlighted with surface0 background + bold text
- [x] Click-outside handler and Escape key to close dropdown
- [x] Uses --ctp-* CSS vars throughout — fully themed with any active theme
- [x] Added `getPalette` import from themes.ts for live color rendering
- [x] Added aria-haspopup/aria-expanded attributes for accessibility
#### Verification
- No test changes needed — UI-only change, no logic changes
### Session: 2026-03-07 — Theme Dropdown CSS Polish
#### SettingsTab Dropdown Sizing Fix
- [x] Set `min-width: 180px` on `.theme-dropdown` container (was `min-width: 0`) to prevent trigger from collapsing
- [x] Set `min-width: 280px` on `.theme-options` dropdown menu (was `right: 0`) to ensure full theme names visible
- [x] Increased `max-height` from 320px to 400px on dropdown menu for better scrolling experience
- [x] Added `white-space: nowrap` on `.theme-option-label` (was `min-width: 0`) to prevent label text wrapping
#### Verification
- No test changes needed — CSS-only change
### Session: 2026-03-07 — Global Font Controls
#### SettingsTab Font Family + Font Size Controls
- [x] Added font family `<select>` with 9 monospace font options (JetBrains Mono, Fira Code, Cascadia Code, Source Code Pro, IBM Plex Mono, Hack, Inconsolata, Ubuntu Mono, monospace) + "Default" option
- [x] Added font size +/- stepper control with numeric input (range 8-24px)
- [x] Both controls apply live preview via CSS custom properties (`--ui-font-family`, `--ui-font-size`)
- [x] Both settings persisted to SQLite via settings-bridge (`font_family`, `font_size` keys)
- [x] `handleFontFamilyChange()` and `handleFontSizeChange()` functions with validation
#### SettingsTab Layout Restructure
- [x] Restructured global settings from inline `.setting-row` (label left, control right) to 2-column `.global-grid` with `.setting-field` (label above control)
- [x] Labels now uppercase, 0.7rem, subtext0 color — consistent compact labeling
- [x] All inputs/selects use consistent styling (surface0 bg, surface1 border, 4px radius)
#### CSS Typography Variables
- [x] Added `--ui-font-family` and `--ui-font-size` to catppuccin.css `:root` (defaults: JetBrains Mono fallback chain, 13px)
- [x] Updated `app.css` body rule to use CSS vars instead of hardcoded font values
#### Theme Store Font Restoration
- [x] Extended `initTheme()` in `theme.svelte.ts` to load and apply saved `font_family` and `font_size` settings on startup
- [x] Font restoration wrapped in try/catch — failures are non-fatal (CSS defaults apply)
#### Verification
- No test changes needed — UI/CSS-only changes, no logic changes
### Session: 2026-03-07 — Settings Drawer Conversion
#### Settings Tab to Drawer
- [x] Converted Settings from a full-page tab to a collapsible side drawer
- [x] GlobalTabBar now has 3 tabs (Sessions/Docs/Context) + gear icon toggle for settings drawer
- [x] App.svelte renders SettingsTab in an `<aside>` drawer (right side, 32em width, semi-transparent backdrop)
- [x] Drawer close: Escape key, click-outside (backdrop), close button (X icon)
- [x] Gear icon in GlobalTabBar highlights blue when drawer is open (active state)
- [x] GlobalTabBar accepts props: `settingsOpen`, `ontoggleSettings`
- [x] Removed 'settings' from WorkspaceTab union type (now 'sessions' | 'docs' | 'context')
- [x] Alt+1..3 for tabs (was Alt+1..4), Ctrl+, toggles drawer (was setActiveTab('settings'))
- [x] SettingsTab padding reduced (12px 16px), max-width removed, flex:1 for drawer context
#### Verification
- All 138 vitest tests pass
### Session: 2026-03-08 — VSCode-Style Sidebar Redesign
#### UI Layout Redesign (Top Tab Bar -> Left Sidebar)
- [x] Redesigned GlobalTabBar.svelte from horizontal tab bar to vertical icon rail (36px wide)
- 4 SVG icon buttons: Sessions (grid), Docs (document), Context (clock), Settings (gear)
- Each button uses SVG path from `icons` record mapped by WorkspaceTab
- Props renamed: `settingsOpen` -> `expanded`, `ontoggleSettings` -> `ontoggle`
- `handleTabClick()` manages toggle: clicking active tab collapses drawer
- [x] Rewrote App.svelte layout from vertical (top tab bar + content area + settings drawer) to horizontal (icon rail + sidebar panel + workspace)
- `.main-row` flex container: GlobalTabBar | sidebar-panel (28em, max 50%) | workspace
- ProjectGrid always visible in main workspace (not inside tab content)
- Sidebar panel renders active tab content (Sessions/Docs/Context/Settings)
- Panel header with title + close button
- Removed backdrop overlay, drawer is inline sidebar not overlay
- [x] Re-added 'settings' to WorkspaceTab union type (was removed when settings was a drawer)
- [x] SettingsTab CSS: changed `flex: 1` to `height: 100%` for sidebar panel context
- [x] Updated keyboard shortcuts:
- Alt+1..4 (was Alt+1..3): switch tabs + open drawer, toggle if same tab
- Ctrl+B (new): toggle sidebar open/closed
- Ctrl+, : open settings panel (toggle if already active)
- Escape: close drawer
- [x] State variables renamed: `settingsOpen` -> `drawerOpen`, `toggleSettings()` -> `toggleDrawer()`
- [x] Added `panelTitles` record for drawer header labels
#### Design Decisions
- VSCode-style sidebar chosen for: always-visible workspace, progressive disclosure, familiar UX
- Settings as regular tab (not special drawer) simplifies code and mental model
- Icon rail at 36px minimizes horizontal space cost
- No backdrop overlay — sidebar is inline, not modal
#### Verification
- All 138 vitest tests pass
- svelte-check clean (only 2 third-party esrap warnings)
### Session: 2026-03-07 — SettingsTab Global Settings Redesign
#### Font Settings Split (UI Font + Terminal Font)
- [x] Split single font setting into UI font (sans-serif options) and Terminal font (monospace options)
- [x] UI font dropdown: System Sans-Serif, Inter, Roboto, Open Sans, Lato, Noto Sans, Source Sans 3, IBM Plex Sans, Ubuntu + Default
- [x] Terminal font dropdown: JetBrains Mono, Fira Code, Cascadia Code, Source Code Pro, IBM Plex Mono, Hack, Inconsolata, Ubuntu Mono, monospace + Default
- [x] Each font dropdown renders preview text in its own typeface
- [x] Size steppers (8-24px) for both UI and Terminal font independently
- [x] Changed setting keys: font_family -> ui_font_family, font_size -> ui_font_size, + new term_font_family, term_font_size
#### SettingsTab Layout Redesign
- [x] Rewrote global settings as single-column layout with labels above controls
- [x] Split into "Appearance" subsection (theme, UI font, terminal font) and "Defaults" subsection (shell, CWD)
- [x] All dropdowns now use reusable custom themed dropdowns (no native `<select>` anywhere)
#### CSS + Theme Store Updates
- [x] Added `--term-font-family` and `--term-font-size` CSS custom properties to catppuccin.css
- [x] Updated `initTheme()` in theme.svelte.ts: loads 4 font settings (ui_font_family, ui_font_size, term_font_family, term_font_size) instead of 2
- [x] UI font fallback changed from monospace to sans-serif
#### Verification
- No test changes needed — UI/CSS-only changes, no logic changes
### Session: 2026-03-08 — CSS Relative Units Rule
#### New Rule: 18-relative-units.md
- [x] Created `.claude/rules/18-relative-units.md` enforcing rem/em for layout CSS
- [x] Pixels allowed only for icon sizes, borders/outlines, box shadows
- [x] Exception: --ui-font-size/--term-font-size CSS vars store px (xterm.js API requirement)
- [x] Added rule #18 to `.claude/CLAUDE.md` rule index
#### CSS Conversions
- [x] GlobalTabBar.svelte: rail width 36px -> 2.75rem, button 28px -> 2rem, gap 2px -> 0.25rem, padding 6px 4px -> 0.5rem 0.375rem, border-radius 4px -> 0.375rem
- [x] App.svelte: sidebar header padding 8px 12px -> 0.5rem 0.75rem, close button 22px -> 1.375rem, border-radius 4px -> 0.25rem
- [x] Also changed GlobalTabBar rail-btn color from --ctp-overlay1 to --ctp-subtext0 for better contrast
### Session: 2026-03-08 — Content-Driven Sidebar Width
#### Sidebar Panel Sizing
- [x] Changed `.sidebar-panel` from fixed `width: 28em` to `width: max-content` with `min-width: 16em` and `max-width: 50%`
- [x] Changed `.sidebar-panel` and `.panel-content` from `overflow: hidden` to `overflow-y: auto` — hidden was blocking content from driving parent width
- [x] Each tab component now defines its own `min-width: 22em` (SettingsTab, ContextTab, DocsTab)
#### Additional px → rem Conversions
- [x] SettingsTab.svelte: padding 12px 16px → 0.75rem 1rem
- [x] DocsTab.svelte: file-picker 220px → 14em, picker-title padding → rem, file-btn padding → rem, empty/loading padding → rem
- [x] ContextPane.svelte: font-size, padding, margin, gap converted from px to rem; added `white-space: nowrap` on `.ctx-header`/`.ctx-error` for intrinsic width measurement
#### Fix: Sidebar Drawer Content-Driven Width
- [x] Root cause found: `#app` in `app.css` had leftover v2 grid layout (`display: grid; grid-template-columns: var(--sidebar-width) 1fr`) constraining `.app-shell` to 260px first column
- [x] Removed v2 grid + both media queries from `#app` — v3 `.app-shell` manages its own flexbox layout
- [x] Added JS `$effect` in App.svelte: measures content width via `requestAnimationFrame` + `querySelectorAll` for nowrap elements, headings, inputs, tab-specific selectors; `panelWidth` state drives inline `style:width`
- [x] Verified all 4 tabs scale to content: Sessions ~473px, Settings ~322px, Context ~580px, Docs varies by content
- [x] Investigation path: CSS intrinsic sizing (max-content, fit-content) failed due to column-flex circular dependency → JS measurement approach → discovered inline style set but rendered width wrong → Playwright inspection revealed parent `.main-row` only 260px → traced to `#app` grid layout
### Session: 2026-03-08 — Native Directory Picker
#### tauri-plugin-dialog Integration
- [x] Added `tauri-plugin-dialog` Rust crate + `@tauri-apps/plugin-dialog` npm package
- [x] Registered plugin in lib.rs (`tauri_plugin_dialog::init()`)
- [x] Removed stub `pick_directory` Tauri command (always returned None)
- [x] Added `browseDirectory()` helper in SettingsTab.svelte using `open({ directory: true })`
- [x] Added folder browse button (folder SVG icon) to: Default CWD, existing project CWD, Add Project path
- [x] Styled `.input-with-browse` layout (flex row, themed browse button)
- [x] Fixed nested input theme: `.setting-field .input-with-browse input` selector for dark background
- [x] Fixed dialog not opening: added `"dialog:default"` permission to `v2/src-tauri/capabilities/default.json` — Tauri IPC security blocked invoke() without capability
- [x] Verified via Playwright: error was `Cannot read properties of undefined (reading 'invoke')` in browser context (expected — Tauri IPC only exists in WebView), confirming code is correct
- [x] Clean rebuild required after capability changes (cached binary doesn't pick up new permissions)
#### Modal + Dark-Themed Dialog
- [x] Root cause: `tauri-plugin-dialog` skips `set_parent(&window)` on Linux via `cfg(any(windows, target_os = "macos"))` gate in commands.rs — dialog not modal
- [x] Root cause: native GTK file chooser uses system GTK theme, not app's CSS theme — dialog appears light
- [x] Fix: custom `pick_directory` Tauri command using `rfd::AsyncFileDialog` directly with `.set_parent(&window)` — modal on Linux
- [x] Fix: `std::env::set_var("GTK_THEME", "Adwaita:dark")` at start of `run()` in lib.rs — dark-themed dialog
- [x] Added `rfd = { version = "0.16", default-features = false, features = ["gtk3"] }` as direct dep — MUST disable defaults to avoid gtk3+xdg-portal feature conflict
- [x] Switched SettingsTab from `@tauri-apps/plugin-dialog` `open()` to `invoke<string | null>('pick_directory')`
### Session: 2026-03-08 — Project Workspace Layout Redesign + Icon Fix
#### Icon Fix
- [x] Replaced Nerd Font codepoints (`\uf120`) with emoji (`📁` default) — Nerd Font not installed, showed "?"
- [x] Added emoji picker grid (24 project-relevant emoji, 8-column popup) in SettingsTab instead of plain text input
- [x] Removed `font-family: 'NerdFontsSymbols Nerd Font'` from ProjectHeader and TerminalTabs
#### ProjectBox Layout Redesign
- [x] Switched ProjectBox from flex to CSS grid (`grid-template-rows: auto 1fr auto`) — header | session | terminal zones
- [x] Terminal area: explicit `height: 16rem` instead of collapsing to content
- [x] Session area: `min-height: 0` for proper flex child overflow
#### AgentPane Prompt Layout
- [x] Prompt area anchored to bottom (`justify-content: flex-end`) instead of vertical center
- [x] Removed `max-width: 600px` constraint on form and toolbar — uses full panel width
- [x] Toolbar sits directly above textarea
#### CSS px → rem Conversions
- [x] ProjectGrid.svelte: gap 4px → 0.25rem, padding 4px → 0.25rem, min-width 480px → 30rem
- [x] TerminalTabs.svelte: tab bar, tabs, close/add buttons all converted to rem
- [x] ProjectBox.svelte: min-width 480px → 30rem
### Session: 2026-03-08 — Project-Level Tabs + Clean AgentPane
#### ProjectHeader Info Bar
- [x] Added CWD path display (ellipsized from START via `direction: rtl` + `text-overflow: ellipsis`)
- [x] Added profile name as info-only text (right side of header)
- [x] Home dir shortening: `/home/user/foo``~/foo`
#### Project-Level Tab Bar
- [x] Added tab bar in ProjectBox below header: Claude | Files | Context
- [x] Content area switches between ClaudeSession, ProjectFiles, ContextPane based on selected tab
- [x] CSS grid updated to 4 rows: `auto auto 1fr auto` (header | tabs | content | terminal)
- [x] TeamAgentsPanel still renders alongside ClaudeSession in Claude tab
#### ProjectFiles Component (NEW)
- [x] Created `ProjectFiles.svelte` — project-scoped markdown file viewer
- [x] Accepts `cwd` + `projectName` props (not workspace store)
- [x] File picker sidebar (10rem) + MarkdownPane content area
- [x] Auto-selects priority file or first file
#### AgentPane Cleanup
- [x] Removed entire session toolbar (DIR/ACC interactive inputs + all CSS)
- [x] Added `profile` prop — resolved via `listProfiles()` to get config_dir
- [x] CWD passed as prop from parent (project.cwd), no longer editable in pane
- [x] Clean chat interface: prompt (bottom-anchored) + messages + send button
- [x] ClaudeSession now passes `project.profile` to AgentPane
#### Verification
- All 138 vitest tests pass
- Vite build succeeds
### Session: 2026-03-08 — Security Audit Fixes + OTEL Telemetry
#### Security Audit Fixes
- [x] Fixed all CRITICAL (5) + HIGH (4) findings — path traversal, race conditions, memory leaks, listener leaks, transaction safety
- [x] Fixed all MEDIUM (6) findings — runtime type guards, ANTHROPIC_* env stripping, timestamp mismatch, async lock, error propagation
- [x] Fixed all LOW (8) findings — input validation, mutex poisoning, log warnings, payload validation
- [x] 3 false positives dismissed with rationale
- [x] 172/172 tests pass (138 vitest + 34 cargo)
#### OTEL Telemetry Implementation
- [x] Added 6 Rust deps: tracing, tracing-subscriber, opentelemetry 0.28, opentelemetry_sdk 0.28, opentelemetry-otlp 0.28, tracing-opentelemetry 0.29
- [x] Created `v2/src-tauri/src/telemetry.rs` — TelemetryGuard, layer composition, OTLP export via BTERMINAL_OTLP_ENDPOINT env var
- [x] Integrated into lib.rs: TelemetryGuard in AppState, init before Tauri builder
- [x] Instrumented 10 Tauri commands with `#[tracing::instrument]`: pty_spawn, pty_kill, agent_query/stop/restart, remote_connect/disconnect/agent_query/agent_stop/pty_spawn
- [x] Added `frontend_log` Tauri command for frontend→Rust tracing bridge
- [x] Created `v2/src/lib/adapters/telemetry-bridge.ts``tel.info/warn/error/debug/trace()` convenience API
- [x] Wired agent dispatcher lifecycle events: agent_started, agent_stopped, agent_error, sidecar_crashed, cost metrics
- [x] Created Docker compose stack: `docker/tempo/` — Tempo (4317/4318/3200) + Grafana (port 9715)
### Session: 2026-03-08 — Teardown Race Fix + px→rem Conversion
#### Workspace Teardown Race Fix
- [x] Added `pendingPersistCount` counter + `waitForPendingPersistence()` export in agent-dispatcher.ts
- [x] `persistSessionForProject()` increments/decrements counter in try/finally
- [x] `switchGroup()` in workspace.svelte.ts now awaits `waitForPendingPersistence()` before clearing state
- [x] SettingsTab.svelte switchGroup onclick handler made async with await
- [x] Added test for `waitForPendingPersistence` in agent-dispatcher.test.ts
- [x] Added mock for `waitForPendingPersistence` in workspace.test.ts
- [x] Last open HIGH audit finding resolved (workspace teardown race)
#### px→rem Conversion (Rule 18 Compliance)
- [x] Converted ~100 px layout violations to rem across 10 components
- [x] AgentPane.svelte (~35 violations: font-size, padding, gap, margin, max-height, border-radius)
- [x] ToastContainer.svelte, CommandPalette.svelte, TeamAgentsPanel.svelte, AgentCard.svelte
- [x] StatusBar.svelte, AgentTree.svelte, TerminalPane.svelte, AgentPreviewPane.svelte, SettingsTab.svelte
- [x] Icon/decorative dot dimensions kept as px per rule 18
- [x] 139 vitest + 34 cargo tests pass, vite build succeeds
### Session: 2026-03-08 — E2E Testing Infrastructure
#### WebdriverIO + tauri-driver Setup
- [x] Installed @wdio/cli, @wdio/local-runner, @wdio/mocha-framework, @wdio/spec-reporter (v9.24.0)
- [x] Created wdio.conf.js with tauri-driver lifecycle hooks (onPrepare builds debug binary, beforeSession/afterSession spawns/kills tauri-driver)
- [x] Created tsconfig.json for e2e test TypeScript compilation
- [x] Created smoke.test.ts with 6 tests: app title, status bar, version text, sidebar rail, workspace area, sidebar toggle
- [x] Added `test:e2e` npm script (`wdio run tests/e2e/wdio.conf.js`)
- [x] Updated README.md with complete setup instructions and CI guide
- [x] Key decision: WebdriverIO over Playwright (Playwright cannot control Tauri/WebKit2GTK apps)
- [x] Prerequisites: tauri-driver (cargo install), webkit2gtk-driver (apt), display server or xvfb-run
#### E2E Fixes (wdio v9 + tauri-driver compatibility)
- [x] Fixed wdio v9 BiDi: added `wdio:enforceWebDriverClassic: true` — wdio v9 injects webSocketUrl:true which tauri-driver rejects
- [x] Removed `browserName: 'wry'` from capabilities (not needed in wdio, only Selenium)
- [x] Fixed binary path: Cargo workspace target is v2/target/debug/, not v2/src-tauri/target/debug/
- [x] Fixed tauri-plugin-log panic: telemetry::init() registers tracing-subscriber before plugin-log → removed tauri-plugin-log entirely (redundant with telemetry::init())
- [x] Removed tauri-plugin-log from Cargo.toml dependency
#### E2E Coverage Expansion (25 tests, single spec file)
- [x] Consolidated 4 spec files into single bterminal.test.ts — Tauri creates one app session per spec file; after first spec completes, app closes and subsequent specs get "invalid session id"
- [x] Added Workspace & Projects tests (8): project grid, project boxes, header with name, 3 project tabs, active highlight, tab switching, status bar counts
- [x] Added Settings Panel tests (6): settings tab, sections, theme dropdown, dropdown open+options, group list, close button
- [x] Added Keyboard Shortcuts tests (5): Ctrl+K command palette, Ctrl+, settings, Ctrl+B sidebar, Escape close, palette group list
- [x] Fixed WebDriver clicks on Svelte 5 components: `element.click()` doesn't reliably trigger onclick inside complex components via WebKit2GTK/tauri-driver — use `browser.execute()` for JS-level clicks
- [x] Fixed CSS text-transform: `.ptab` getText() returns uppercase — use `.toLowerCase()` for comparison
- [x] Fixed element scoping: `browser.$('.ptab')` returns ALL tabs across project boxes — scope via `box.$('.ptab')`
- [x] Fixed keyboard focus: `browser.execute(() => document.body.focus())` before sending shortcuts
- [x] Removed old individual spec files (smoke.test.ts, keyboard.test.ts, settings.test.ts, workspace.test.ts)
- [x] All 25 E2E tests pass (9s runtime after build)
### Session: 2026-03-10 — Tab System Overhaul
#### Tab Renames + New Tabs
- [x] Renamed Claude → Model, Files → Docs in ProjectBox
- [x] Added 3 new tabs: Files (directory browser), SSH (connection manager), Memory (knowledge explorer)
- [x] Implemented PERSISTED-EAGER (Model/Docs/Context — display:flex/none) vs PERSISTED-LAZY (Files/SSH/Memory — {#if everActivated} + display:flex/none) mount strategy
- [x] Tab type union: 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories'
#### Files Tab (FilesTab.svelte)
- [x] VSCode-style tree sidebar (14rem) + content viewer
- [x] Rust list_directory_children command: lazy expansion, hidden files skipped, dirs-first sort
- [x] Rust read_file_content command: FileContent tagged union (Text/Binary/TooLarge), 10MB gate, 30+ language mappings
- [x] Frontend files-bridge.ts adapter (DirEntry, FileContent types)
- [x] Shiki syntax highlighting for code files, image display via convertFileSrc, emoji file icons
#### SSH Tab (SshTab.svelte)
- [x] CRUD panel for SSH connections using existing ssh-bridge.ts/SshSession model
- [x] Launch button spawns terminal tab in Model tab's TerminalTabs section via addTerminalTab()
#### Memory Tab (MemoriesTab.svelte)
- [x] Pluggable MemoryAdapter interface (memory-adapter.ts): name, available, list(), search(), get()
- [x] Adapter registry: registerMemoryAdapter(), getDefaultAdapter(), getAvailableAdapters()
- [x] UI: search bar, tag display, expandable cards, adapter switcher, placeholder when no adapter
#### Context Tab Repurpose (ContextTab.svelte)
- [x] Replaced ContextPane (ctx database viewer) with LLM context window visualization
- [x] Tribunal debate for design (S-1-R4 winner at 82% confidence)
- [x] Stats bar: input/output tokens, cost, turns, duration
- [x] Segmented token meter: CSS flex bar with color-coded categories (assistant/thinking/tool calls/tool results)
- [x] File references: extracted from tool_call messages, colored op badges
- [x] Turn breakdown: collapsible message groups by user prompt
- [x] Token estimation via ~4 chars/token heuristic
- [x] Wired into ProjectBox (replaces ContextPane, passes sessionId)
- [x] Sub-tab navigation: Overview | AST | Graph
- [x] AST tab: per-turn SVG conversation trees (Thinking/Response/ToolCall/File nodes, bezier edges, token counts)
- [x] Graph tab: bipartite tool→file DAG (tools left, files right, curved edges, count badges)
- [x] Compaction detection: sdk-messages.ts adapts `compact_boundary` system messages → `CompactionContent` type
- [x] Stats bar compaction pill: yellow count badge with tooltip (last trigger, tokens removed)
- [x] AST compaction boundaries: red "Compacted" nodes inserted between turns at compaction points
#### FilesTab Fixes & CodeMirror Editor
- [x] Fixed HTML nesting error: `<button>` inside `<button>``<div role="tab">`
- [x] Fixed Svelte 5 $state proxy reactivity: look up tab from reactive array before setting content
- [x] CodeEditor.svelte: CodeMirror 6 with 15 lazy-loaded language modes, Catppuccin theme
- [x] Dirty tracking, Ctrl+S save, save-on-blur setting (files_save_on_blur in SettingsTab)
- [x] write_file_content Rust command (safety: existing files only)
#### Project Health Dashboard (S-3 — Mission Control)
- [x] health.svelte.ts store: per-project ActivityState (running/idle/stalled), burn rate ($/hr EMA), context pressure (% of model limit), attention scoring
- [x] StatusBar → Mission Control bar: running/idle/stalled counts, $/hr burn rate, "needs attention" priority queue dropdown
- [x] ProjectHeader health indicators: status dot (color-coded), context pressure badge, burn rate badge
- [x] session_metrics SQLite table: per-project historical metrics (100-row retention)
- [x] Rust commands: session_metric_save, session_metrics_load
- [x] TypeScript bridge: SessionMetric interface, saveSessionMetric(), loadSessionMetrics()
- [x] agent-dispatcher wiring: recordActivity, recordToolDone, recordTokenSnapshot, sessionStartTimes, metric persistence on completion
- [x] ClaudeSession: trackProject() on session create/restore
- [x] App.svelte: startHealthTick()/stopHealthTick() lifecycle
- [x] workspace.svelte.ts: clearHealthTracking() on group switch
#### Verification
- [x] svelte-check: 0 new errors (only pre-existing esrap type errors)
- [x] vitest: 139/139 tests pass
- [x] cargo test: 34/34 pass
### Session: 2026-03-11 — S-1 Phase 1.5: Conflict Detection Enhancements
#### Bash Write Detection
- [x] BASH_WRITE_PATTERNS regex array in tool-files.ts: >, >>, sed -i, tee [-a], cp dest, mv dest, chmod/chown
- [x] extractBashWritePaths() helper with /dev/null and flag-target filtering
- [x] Write detection prioritized over read detection for ambiguous commands (cat file > out)
- [x] extractWritePaths() now captures Bash writes alongside Write/Edit
#### Acknowledge/Dismiss Conflicts
- [x] acknowledgeConflicts(projectId) API in conflicts.svelte.ts — marks current conflicts as acknowledged
- [x] acknowledgedFiles Map state — suppresses badge until new session writes to acknowledged file
- [x] ProjectHeader conflict badge → clickable button with ✕ (stopPropagation, hover darkens)
- [x] Ack auto-cleared when new session writes to previously-acknowledged file
#### Worktree-Aware Conflict Suppression
- [x] sessionWorktrees Map in conflicts store — tracks worktree path per session (null = main tree)
- [x] setSessionWorktree(sessionId, path) API
- [x] areInDifferentWorktrees() / hasRealConflict() — suppresses conflicts between sessions in different worktrees
- [x] extractWorktreePath(tc) in tool-files.ts — detects Agent/Task isolation:"worktree" and EnterWorktree
- [x] agent-dispatcher.ts wiring: registers worktree paths from tool_call events
- [x] useWorktrees?: boolean field on ProjectConfig (groups.ts) for future per-project setting
#### Verification
- [x] vitest: 194/194 tests pass (+24 new: 5 extractWorktreePath, 10 bash write, 9 acknowledge/worktree)
- [x] cargo test: 34/34 pass
### Session: 2026-03-11 — S-1 Phase 2: Filesystem Write Detection
#### Rust Backend — ProjectFsWatcher
- [x] New module `v2/src-tauri/src/fs_watcher.rs` — per-project recursive inotify watchers via notify crate v6
- [x] Debouncing (100ms per-file), ignored dirs (.git/, node_modules/, target/, etc.)
- [x] Emits `fs-write-detected` Tauri events with FsWritePayload { project_id, file_path, timestamp_ms }
- [x] Two Tauri commands: `fs_watch_project`, `fs_unwatch_project`
- [x] ProjectFsWatcher added to AppState, initialized in setup()
- [x] 5 Rust unit tests for path filtering (should_ignore_path)
#### Frontend Bridge
- [x] New `v2/src/lib/adapters/fs-watcher-bridge.ts` — fsWatchProject(), fsUnwatchProject(), onFsWriteDetected()
#### External Write Detection (conflicts store)
- [x] EXTERNAL_SESSION_ID = '__external__' sentinel for non-agent writers
- [x] agentWriteTimestamps Map — tracks when agents write files (for timing heuristic)
- [x] recordExternalWrite(projectId, filePath, timestampMs) — 2s grace window suppresses agent's own writes
- [x] getExternalConflictCount(projectId) — counts external-only conflicts
- [x] FileConflict.isExternal flag, ProjectConflicts.externalConflictCount field
- [x] clearAllConflicts/clearProjectConflicts clear timestamp state
#### Health Store Integration
- [x] externalConflictCount added to ProjectHealth interface
- [x] Attention reason includes "(N external)" note when external conflicts present
#### UI Updates
- [x] ProjectBox $effect: starts/stops fs watcher per project CWD, listens for events, calls recordExternalWrite
- [x] ProjectHeader: split conflict badge into orange "ext write" badge + red "agent conflict" badge
- [x] Toast notification on new external write conflict
#### Verification
- [x] vitest: 202/202 tests pass (+8 new external write tests)
- [x] cargo test: 39/39 pass (+5 new fs_watcher tests)
### Session: 2026-03-11 — Files Tab: PDF Viewer + CSV Table View
#### PDF Viewer
- [x] Added pdfjs-dist@5.5.207 dependency (WebKit2GTK has no built-in PDF viewer)
- [x] Created PdfViewer.svelte — canvas-based multi-page renderer
- [x] Zoom controls (0.5x3x, 25% steps), HiDPI-aware (devicePixelRatio scaling)
- [x] Reads PDF via convertFileSrc() → pdfjs (no new Rust commands needed)
- [x] Page shadow, themed toolbar, error handling
#### CSV Table View
- [x] Created CsvTable.svelte — RFC 4180 CSV parser (no external dependency)
- [x] Auto-detects delimiter (comma, semicolon, tab)
- [x] Sortable columns (numeric-aware), sticky header, row numbers
- [x] Row hover, text truncation at 20rem, themed via --ctp-* vars
#### FilesTab Routing
- [x] Binary+pdf → PdfViewer (via isPdfExt check)
- [x] Text+csv → CsvTable (via isCsvLang check)
- [x] Updated file icons: 📕 PDF, 📊 CSV
- [x] Both viewers are read-only
#### Verification
- [x] vitest: 202/202 tests pass (no regressions)
- [x] Vite build: clean
- [x] cargo check: clean
### Session: 2026-03-11 — S-2 Session Anchors
#### Implementation
- [x] Created types/anchors.ts — AnchorType, SessionAnchor, AnchorSettings, budget constants
- [x] Created adapters/anchors-bridge.ts — 5 Tauri IPC functions (save, load, delete, clear, updateType)
- [x] Created stores/anchors.svelte.ts — Svelte 5 rune store (per-project anchor management)
- [x] Created utils/anchor-serializer.ts — observation masking, turn grouping, token estimation
- [x] Created utils/anchor-serializer.test.ts — 17 tests (4 describe blocks)
- [x] Added session_anchors SQLite table + SessionAnchorRecord struct + 5 CRUD methods (session.rs)
- [x] Added 5 Tauri commands for anchor persistence (lib.rs)
- [x] Auto-anchor logic in agent-dispatcher.ts on first compaction event per project
- [x] Re-injection in AgentPane.startQuery() via system_prompt field
- [x] Pin button on AgentPane text messages
- [x] Anchor section in ContextTab: budget meter, promote/demote, remove
#### Verification
- [x] vitest: 219/219 tests pass (+17 new anchor tests)
- [x] cargo test: 42/42 pass (+3 new session_anchors tests)
### Session: 2026-03-11 — Configurable Anchor Budget + Truncation Fix
#### Research-backed truncation fix
- [x] Removed 500-char assistant text truncation in anchor-serializer.ts
- [x] Research consensus (JetBrains NeurIPS 2025, SWE-agent, OpenDev ACC): reasoning must never be truncated, only tool outputs get masked
#### Configurable anchor budget scale
- [x] Added AnchorBudgetScale type ('small'|'medium'|'large'|'full') with preset map (2K/6K/12K/20K)
- [x] Added anchorBudgetScale? field to ProjectConfig (persisted in groups.json)
- [x] Updated getAnchorSettings() to resolve budget from scale
- [x] Added 4-stop range slider to SettingsTab per-project settings
- [x] Updated ContextTab to derive budget from anchorBudgetScale prop
- [x] Updated agent-dispatcher to look up project's budget scale
#### Cleanup
- [x] Removed Ollama-specific warning toast from AgentPane (budget slider handles generically)
- [x] Removed unused notify import from AgentPane
#### Verification
- [x] vitest: 219/219 tests pass (no regressions)
- [x] cargo test: 42/42 pass (no regressions)
### Session: 2026-03-11 — S-1 Phase 3: Worktree Isolation Per Project
#### UI toggle
- [x] Added 'Worktree Isolation' checkbox to SettingsTab per-project card (card-field-row CSS layout)
- [x] ProjectConfig.useWorktrees? already existed — wired to toggle
#### Spawn with worktree flag
- [x] Added worktree_name: Option<String> to AgentQueryOptions (Rust sidecar.rs)
- [x] Added worktree_name?: string to TS AgentQueryOptions (agent-bridge.ts)
- [x] Sidecar JSON passes worktreeName field to claude-runner.ts
- [x] claude-runner.ts passes extraArgs: { worktree: name } to SDK query() (maps to --worktree CLI flag)
- [x] AgentPane: added useWorktrees prop, passes worktree_name=sessionId when enabled
- [x] AgentSession: passes useWorktrees={project.useWorktrees} to AgentPane
- [x] Rebuilt sidecar bundle (claude-runner.mjs)
#### CWD-based worktree detection
- [x] Added detectWorktreeFromCwd() to agent-dispatcher.ts (matches .claude/.codex/.cursor worktree patterns)
- [x] Init event handler now calls setSessionWorktree() when CWD contains worktree path
- [x] Dual detection: CWD-based (primary) + tool_call-based extractWorktreePath (subagent fallback)
#### Tests
- [x] Added 7 new tests to agent-dispatcher.test.ts (detectWorktreeFromCwd unit tests + init CWD integration)
- [x] vitest: 226/226 tests pass
- [x] cargo test: 42/42 pass
### Session: 2026-03-11 — Provider Runners (Codex + Ollama)
#### Codex Provider
- [x] providers/codex.ts — ProviderMeta (gpt-5.4, hasSandbox, supportsResume)
- [x] adapters/codex-messages.ts — adaptCodexMessage (ThreadEvents → AgentMessage[])
- [x] sidecar/codex-runner.ts — @openai/codex-sdk wrapper (dynamic import, graceful failure)
- [x] adapters/codex-messages.test.ts — 19 tests
#### Ollama Provider
- [x] providers/ollama.ts — ProviderMeta (qwen3:8b, modelSelection only)
- [x] adapters/ollama-messages.ts — adaptOllamaMessage (streaming chunks → AgentMessage[])
- [x] sidecar/ollama-runner.ts — Direct HTTP to localhost:11434 (zero deps)
- [x] adapters/ollama-messages.test.ts — 11 tests
#### Registration + Build
- [x] App.svelte: register CODEX_PROVIDER + OLLAMA_PROVIDER
- [x] message-adapters.ts: register codex + ollama adapters
- [x] package.json: build:sidecar builds all 3 runners
- [x] vitest: 256/256 tests pass
- [x] cargo test: 42/42 pass
### 2026-03-11 — Register Memora Adapter
**Duration:** ~15 min
**What happened:**
Registered a concrete MemoraAdapter that bridges the MemoryAdapter interface to the Memora SQLite database. Direct read-only SQLite access (no MCP/CLI dependency at runtime).
#### Rust Backend
- [x] memora.rs — MemoraDb struct (read-only SQLite, Option<Connection>, graceful absence)
- [x] list() with tag filtering via json_each() + IN clause
- [x] search() via FTS5 MATCH on memories_fts, optional tag join
- [x] get() by ID
- [x] 4 Tauri commands: memora_available, memora_list, memora_search, memora_get
- [x] 7 cargo tests (missing-db error paths)
#### TypeScript Bridge + Adapter
- [x] memora-bridge.ts — IPC wrappers + MemoraAdapter class implementing MemoryAdapter
- [x] App.svelte — registers MemoraAdapter on mount with async availability check
- [x] memora-bridge.test.ts — 16 tests (IPC + adapter)
#### Results
- [x] vitest: 272/272 tests pass
- [x] cargo test: 49/49 pass
### 2026-03-11 — Configurable Stall Threshold
**Duration:** ~10 min
**What happened:**
Made the hardcoded 15-minute stall threshold configurable per-project via a range slider in SettingsTab (560 min, step 5).
#### Changes
- [x] groups.ts — Added `stallThresholdMin?: number` to ProjectConfig
- [x] health.svelte.ts — Replaced hardcoded constant with per-project `stallThresholds` Map + `setStallThreshold()` API, fallback to DEFAULT_STALL_THRESHOLD_MS (15 min)
- [x] SettingsTab.svelte — Range slider per project card (560 min, step 5, default 15)
- [x] ProjectBox.svelte — `$effect` syncs `project.stallThresholdMin``setStallThreshold()` on mount/change
#### Results
- [x] No test changes — UI/config wiring only
- [x] vitest: 272/272 tests pass
- [x] cargo test: 49/49 pass
### 2026-03-11 — Nemesis Security Audit + Reconnect Loop Fix
**Duration:** ~15 min
**What happened:**
Ran nemezis-audit on Rust backend. 0 verified exploitable findings, 10 recon targets identified (all previously known from 2026-03-08 security audit). Fixed Priority 8 reconnect loop race condition.
#### Nemesis Audit
- [x] Ran nemezis orchestrator on v2/src-tauri (Rust backend, 496s, $0.57)
- [x] 0 verified findings, 10 attack surface targets in recon hit list
- [x] All targets match previous 2026-03-08 security audit — no new vulnerabilities
#### Reconnect Loop Fix
- [x] remote.rs — Added `cancelled: Arc<AtomicBool>` to RemoteMachine struct
- [x] remove_machine() and disconnect() set cancelled=true before aborting tasks
- [x] connect() resets cancelled=false for new connections
- [x] Reconnect loop checks flag at top of each iteration, exits immediately when set
#### Results
- [x] cargo check: clean
- [x] cargo test: 49/49 pass
### Session 2026-03-11 (SOLID Phase 1 Refactoring)
#### SOLID Analysis
- [x] Ran /solid on full v2 codebase (TypeScript + Rust)
- [x] Identified 3 critical, 5 high, 5 medium issues; 8 good-practice modules
#### Phase 1 Refactoring — High-impact, Low-risk
- [x] **AttentionScorer extraction**: scoreAttention() pure function from health.svelte.ts → utils/attention-scorer.ts (14 tests)
- [x] **Shared type guards**: str()/num() from 3 adapter copies → utils/type-guards.ts
- [x] **lib.rs command module split**: 976 → 170 lines, 48 commands → 11 domain modules under commands/
- [x] Skipped withRemoteSupport() HOF — parameter shapes differ, 3-line duplication doesn't justify abstraction
#### Results
- [x] vitest: 286/286 pass (14 new attention-scorer tests)
- [x] cargo check: clean
- [x] cargo test: 49/49 pass
### Session 2026-03-11 (SOLID Phase 2 Refactoring)
#### agent-dispatcher.ts Split (496→260 lines)
- [x] Extracted utils/worktree-detection.ts — detectWorktreeFromCwd() pure function (17 lines, 5 tests)
- [x] Extracted utils/session-persistence.ts — session maps + persistSessionForProject (107 lines)
- [x] Extracted utils/auto-anchoring.ts — triggerAutoAnchor (48 lines)
- [x] Extracted utils/subagent-router.ts — spawnSubagentPane + SUBAGENT_TOOL_NAMES (73 lines)
- [x] Dispatcher is now thin coordinator with re-exports for backward compat
#### session.rs Split (1,008 lines → 7 sub-modules)
- [x] session/mod.rs — SessionDb struct + open() + migrate() + re-exports (153 lines)
- [x] session/sessions.rs — Session CRUD (9 tests)
- [x] session/layout.rs — LayoutState save/load (3 tests)
- [x] session/settings.rs — Settings CRUD (5 tests)
- [x] session/ssh.rs — SshSession CRUD (4 tests)
- [x] session/agents.rs — AgentMessageRecord + ProjectAgentState
- [x] session/metrics.rs — SessionMetric save/load
- [x] session/anchors.rs — SessionAnchorRecord CRUD
- [x] conn field: pub(in crate::session) for sub-module access
#### Results
- [x] vitest: 286/286 pass (5 worktree tests moved to new file)
- [x] cargo check: clean
- [x] cargo test: 49/49 pass
### Session 2026-03-11 (SOLID Phase 3 — Branded Types)
#### Implementation
- [x] Introduced SessionId/ProjectId branded types (types/ids.ts)
- [x] Applied branded types to conflicts.svelte.ts and health.svelte.ts Map keys
- [x] Branded sessionId at sidecar boundary in agent-dispatcher
- [x] Applied branded types at Svelte component call sites
#### Results
- [x] cargo check: clean
- [x] vitest: 286/286 pass
### Session 2026-03-11 — Multi-Agent Orchestration System
#### btmsg Group Agent Messenger CLI
- [x] Created btmsg CLI tool — inter-agent messaging (inbox, send, reply, contacts, history, channels)
- [x] btmsg graph command — visual agent hierarchy with status
- [x] Admin role (tier 0), channel messaging (create/list/send/history), mark-read, global feed
#### btmsg Rust Backend + Tauri Bridge
- [x] Created btmsg.rs module — SQLite-backed messaging (shared DB: ~/.local/share/bterminal/btmsg.db)
- [x] 8+ Tauri commands: btmsg_inbox, btmsg_send, btmsg_read, btmsg_contacts, btmsg_feed, btmsg_channels, etc.
- [x] CommsTab: sidebar chat interface with activity feed, DMs, channels (Ctrl+M)
#### Agent Unification (Tier 1 → ProjectBoxes)
- [x] agentToProject() converter in groups.ts — Tier 1 agents rendered as full ProjectBoxes
- [x] getAllWorkItems() in workspace store combines agents + projects for ProjectGrid
- [x] GroupAgentsPanel: click-to-navigate agent cards to their ProjectBox
#### Agent System Prompts
- [x] Created utils/agent-prompts.ts — generateAgentPrompt() builds comprehensive introductory context
- Sections: Identity, Environment, Team hierarchy, btmsg docs, bttask docs, Custom context, Workflow
- Role-specific workflows (Manager: check inbox → review board → coordinate; Architect: code review focus; Tester: write/run tests)
- [x] AgentSession builds system prompt: Tier 1 gets full generated prompt, Tier 2 gets custom context
#### BTMSG_AGENT_ID Environment Passthrough (5-layer chain)
- [x] agent-bridge.ts: added extra_env?: Record<string,string> to AgentQueryOptions
- [x] bterminal-core/sidecar.rs: added extra_env: HashMap<String,String> with #[serde(default)]
- [x] claude-runner.ts: extraEnv merged into cleanEnv after provider var stripping
- [x] codex-runner.ts: same extraEnv pattern
- [x] AgentSession injects { BTMSG_AGENT_ID: project.id } for agent projects
#### Tier 1 Agent Config in SettingsTab
- [x] Agent cards: icon + name + role badge + enable toggle + CWD + model + wake interval (manager)
- [x] Custom Context textarea per agent (appended to auto-generated prompt)
- [x] Collapsible preview of full generated introductory prompt
- [x] updateAgent() function in workspace store for Tier 1 config persistence
#### Custom Context for Tier 2 Projects
- [x] Custom Context textarea in SettingsTab project cards
- [x] Stored as project.systemPrompt, passed through AgentSession → AgentPane
#### Periodic System Prompt Re-injection
- [x] AgentSession: 1-hour timer (REINJECTION_INTERVAL_MS = 3,600,000ms)
- [x] Checks every 60s if elapsed > 1 hour, sets contextRefreshPrompt
- [x] AgentPane: autoPrompt prop consumed only when agent is idle (done/error state)
- [x] Different refresh messages: Tier 1 (check inbox + task board), Tier 2 (review instructions)
#### bttask Kanban Backend + UI
- [x] Created bttask.rs — Task/TaskComment structs, 6 operations (list, comments, update_status, create, delete, add_comment)
- [x] Created commands/bttask.rs — 6 Tauri commands registered in lib.rs
- [x] Created bttask-bridge.ts — TypeScript IPC adapter
- [x] Created TaskBoardTab.svelte — Kanban board with 5 columns (todo/progress/review/done/blocked)
- Task creation form (title, description, priority)
- Expandable task detail with status actions, comments, delete
- 5-second polling
- Pending count badge
#### ArchitectureTab (PlantUML Diagrams)
- [x] Created ArchitectureTab.svelte — PlantUML diagram viewer/editor
- Sidebar with diagram list + new diagram form (4 templates: Class, Sequence, State, Component)
- PlantUML source editor + SVG preview via plantuml.com server (~h hex encoding)
- Stores .puml files in .architecture/ directory
- Read/write via files-bridge.ts
#### TestingTab (Selenium + Automated Tests)
- [x] Created TestingTab.svelte — dual-mode component
- Selenium mode: screenshot gallery (.selenium/screenshots/), session log viewer, 3s polling
- Tests mode: discovers test files in standard dirs (tests/, test/, spec/, __tests__/, e2e/), file content viewer
#### Role-Specific Tabs in ProjectBox
- [x] Extended ProjectTab type: added 'tasks' | 'architecture' | 'selenium' | 'tests'
- [x] Conditional tab buttons: Manager→Tasks, Architect→Arch, Tester→Selenium+Tests
- [x] PERSISTED-LAZY rendering via {#if everActivated[tab]} pattern
- [x] .ptab-role CSS class (mauve accent color for agent-specific tabs)
#### Bug Fix
- [x] Fixed FileContent type case: 'text' → 'Text' in ArchitectureTab and TestingTab (files-bridge uses capital T)
#### Verification
- [x] cargo check: clean (bttask module + commands)
- [x] svelte-check: 0 project errors
- [x] Sidecar rebuilt with extraEnv support
### Night Integration Session — Bug Fixes for dexter_changes (2026-03-11)
Reviewed and integrated Dexter's multi-agent orchestration branch (dexter_changes, ~6,500 lines, 36 files). Fast-forward merged. Fixed 5 critical bugs + 2 hardening improvements.
#### Critical Bug Fixes
- [x] **Bug 1 — btmsg.rs column index mismatch**: `get_agents()` used `SELECT a.*` with positional index 7 for `status`, but column 7 is `system_prompt` (column 8 is `status`). Converted ALL query functions in btmsg.rs and bttask.rs from positional to named column access (`row.get("column_name")`). Added SQL aliases for JOIN columns.
- [x] **Bug 2 — btmsg-bridge.ts serde camelCase mismatch**: `BtmsgAgent` and `BtmsgMessage` TypeScript interfaces used snake_case (`group_id`, `unread_count`, `from_agent`) but Rust `#[serde(rename_all = "camelCase")]` sends camelCase. Fixed interfaces to match wire format.
- [x] **Bug 3 — GroupAgentsPanel event propagation**: toggleAgent button click propagated to parent card click handler (setActiveProject). Added `e.stopPropagation()`.
- [x] **Bug 4 — ArchitectureTab PlantUML encoding**: `rawDeflate()` was a no-op, `encode64()` did hex encoding. Collapsed into single `plantumlEncode()` using PlantUML's `~h` hex encoding.
- [x] **Bug 5 — TestingTab Tauri 2.x asset URL**: Used `asset://localhost/` (Tauri 1.x pattern). Fixed to `convertFileSrc()` from `@tauri-apps/api/core`.
#### Hardening
- [x] Added WAL mode + 5s busy_timeout to both btmsg.rs and bttask.rs `open_db()` for safe concurrent access from Python CLIs + Rust backend
- [x] Fixed misleading "Read-only access" comment in btmsg.rs (it opens READ_WRITE)
#### Verification
- [x] cargo check: clean
- [x] svelte-check: 0 project errors (2 pre-existing in node_modules/esrap)
### Regression Tests + Sidecar Security Session (2026-03-11 night, continued)
#### Regression Tests (49 new tests total)
- [x] **btmsg.rs cargo tests (8)**: In-memory SQLite with full schema, verifies named column access returns status (not system_prompt), unread counts, JOIN alias disambiguation (all_feed, unread_messages, channel_messages), serde camelCase serialization
- [x] **bttask.rs cargo tests (7)**: Named column access (list_tasks, task_comments), serde camelCase serialization, status validation
- [x] **sidecar strip_provider_env_var tests (8)**: All prefix combinations (CLAUDE, CODEX, OLLAMA, ANTHROPIC stripped; CLAUDE_CODE_EXPERIMENTAL whitelisted; OPENAI kept), integration test with full env simulation
- [x] **btmsg-bridge.test.ts (17)**: camelCase field verification for all 5 interfaces, IPC command names, error propagation
- [x] **bttask-bridge.test.ts (10)**: camelCase field verification (Task, TaskComment), IPC command names
- [x] **plantuml-encode.test.ts (7)**: ~h hex prefix, ASCII/Unicode encoding, URL safety, plantuml.com URL generation
- [x] Fixed pre-existing groups.rs test (missing `agents` field from Dexter's schema change)
#### Security: Sidecar Env Allowlist
- [x] Added ANTHROPIC_* to Rust-level strip_provider_env_var() — defense-in-depth (Claude CLI uses credentials file, not env var for auth)
- [x] OPENAI_* intentionally NOT stripped at Rust level (Codex runner needs OPENAI_API_KEY from env)
- [x] Documented dual-layer stripping design in code comments
#### Test Counts
- Vitest: 327 passed (was 286, +41)
- Cargo src-tauri: 64 passed (was 49, +15)
- Cargo bterminal-core: 8 passed (was 0, +8)
### E2E Testing Engine — Phase A (2026-03-12)
#### Test Mode Infrastructure
- [x] Rust: watcher.rs — test mode bypass (skip file watching, return content directly)
- [x] Rust: fs_watcher.rs — test mode bypass (skip inotify watchers)
- [x] Rust: commands/misc.rs — added `is_test_mode` Tauri command
- [x] Rust: lib.rs — registered is_test_mode in invoke handler
- [x] Frontend: wake-scheduler.svelte.ts — `disableWakeScheduler()` export + early return
- [x] Frontend: App.svelte — test mode detection via IPC, disables wake scheduler
#### E2E Test Anchors (data-testid attributes)
- [x] AgentPane: data-testid="agent-pane", data-agent-status, agent-messages, agent-stop, agent-prompt, agent-submit
- [x] ProjectBox: data-testid="project-box", data-project-id, project-tabs, terminal-toggle
- [x] StatusBar: data-testid="status-bar"
- [x] AgentSession: data-testid="agent-session"
- [x] GlobalTabBar: data-testid="sidebar-rail", settings-btn
- [x] CommandPalette: data-testid="command-palette", palette-input
- [x] TerminalTabs: data-testid="terminal-tabs", tab-add
#### WebDriverIO Config Improvements
- [x] TCP readiness probe replaces blind 2s sleep for tauri-driver startup
- [x] BTERMINAL_TEST=1 env var passed to Tauri app via capabilities
- [x] Optional BTERMINAL_TEST_DATA_DIR / BTERMINAL_TEST_CONFIG_DIR passthrough
#### Test Infrastructure Files
- [x] `v2/tests/e2e/fixtures.ts` — isolated test fixture generator (temp dirs, git repos, groups.json)
- [x] `v2/tests/e2e/results-db.ts` — JSON-based test results store (no native deps)
- [x] `v2/tests/e2e/specs/agent-scenarios.test.ts` — 7 Phase A scenarios (22 test cases)
#### Phase A Scenarios (7 scenarios, 22 tests)
1. **App Structural Integrity** — verifies all data-testid anchors render correctly
2. **Settings Panel (data-testid)** — open/close settings via stable selector
3. **Agent Pane Initial State** — idle status, prompt textarea, empty messages
4. **Terminal Tab Management** — add/close tabs via data-testid, empty state
5. **Command Palette (data-testid)** — open, focus, filter, close
6. **Project Focus & Tab Switching** — focus, tab persistence, agent status preservation
7. **Agent Prompt Submission** — textarea input, submit button state, graceful Claude CLI skip
#### Verification
- [x] cargo test: 68 passed, 0 failed
- [x] vitest: 345 passed across 18 files, 0 failed
- [x] svelte-check: 0 project errors (2 pre-existing esrap node_modules)
#### Test Counts
- Vitest: 345 passed (was 327, +18 — new wake-scorer + metrics tests from prior session)
- Cargo src-tauri: 68 passed (was 64, +4)
- E2E scenarios: 22 new test cases across 7 scenarios
### Session: 2026-03-12 — E2E Testing Engine Phase B+
#### LLM Judge Helper
- [x] Created `v2/tests/e2e/llm-judge.ts` — Claude API-based test assertion judge
- Raw fetch to Anthropic API (zero new deps), uses claude-haiku-4-5 for speed/cost
- `judge()` evaluates actual output against criteria, returns structured verdict (pass/fail, reasoning, confidence)
- `assertWithJudge()` convenience with minimum confidence threshold (default 0.7)
- `isJudgeAvailable()` check — tests skip gracefully when ANTHROPIC_API_KEY absent
#### Phase B Scenarios (6 scenarios, ~15 tests)
- [x] Created `v2/tests/e2e/specs/phase-b.test.ts`
- **B1: Multi-Project Grid** — renders multiple project boxes, unique IDs, independent agent panes, CWD paths, focus/active styling
- **B2: Independent Tab Switching** — different tabs active in different project boxes simultaneously
- **B3: Status Bar Fleet State** — agent count display, burn rate $0.00 when all idle
- **B4: LLM-Judged Agent Response** — sends file listing prompt, evaluates response quality + tool usage via LLM judge (requires ANTHROPIC_API_KEY)
- **B5: LLM-Judged Code Generation** — sends code explanation prompt, evaluates correctness via LLM judge
- **B6: Context Tab After Activity** — verifies context tab shows token usage data after agent activity
- [x] Per-project helper functions: focusProject(), getAgentStatus(), sendPromptInProject(), waitForProjectAgentStatus(), getAgentMessages(), switchProjectTab()
- [x] All LLM-judged tests skip gracefully when ANTHROPIC_API_KEY not set
- [x] Added phase-b.test.ts to wdio.conf.js specs array
#### CI Workflow
- [x] Created `.github/workflows/e2e.yml`
- Triggers: push to v2-mission-control, PRs to master/v2-mission-control, manual dispatch
- Path filters: v2/src/**, v2/src-tauri/**, v2/tests/e2e/**
- 3 jobs: unit-tests (vitest), cargo-tests, e2e-tests (needs both)
- E2E job: installs xvfb + tauri-driver, builds debug binary, runs Phase A + Phase B specs
- LLM-judged tests gated on ANTHROPIC_API_KEY secret availability
- Uploads test-results/ artifact on all outcomes
#### Verification
- [x] Vitest: 388 passed, 0 failed (was 345, +43 from prior sessions)
- [x] Cargo: 68 passed, 0 failed
- [x] No regressions
---
### Session: v3 Production Readiness — Tribunal Implementation (2026-03-12)
Implemented ALL 13 features from tribunal assessment in 3 parallel waves (11 sub-agents total). 60+ files changed, 3861 insertions.
#### Wave 1: Core Infrastructure (3 agents)
- [x] **Sidecar supervisor**`bterminal-core/src/supervisor.rs`: SidecarSupervisor with exponential backoff (1s-30s, 5 retries), SidecarHealth enum, 5min stability window, 17 tests
- [x] **FTS5 search** — rusqlite `bundled-full`, SearchDb with 3 FTS5 virtual tables, SearchOverlay.svelte (Ctrl+Shift+F), search-bridge.ts
- [x] **Secrets management**`keyring` crate (linux-native/libsecret), SecretsManager, secrets-bridge.ts, SettingsTab section
#### Wave 2: Features (5 agents)
- [x] **Notification system** — notify-rust + NotificationCenter.svelte (bell icon, history, 6 types), notifications-bridge.ts
- [x] **Keyboard-first UX** — Alt+1-5 jump, Ctrl+H/L vi-nav, Ctrl+Shift+1-9 tabs, Ctrl+J terminal, CommandPalette rewrite (18+ cmds, 6 categories)
- [x] **Agent health monitoring** — heartbeats + dead_letter_queue tables, 15s poll, ProjectHeader heart indicator, StatusBar badge
- [x] **Plugin system** — plugins.rs discovery, plugin-host.ts sandboxed runtime, plugins.svelte.ts store, example plugin
- [x] **Landlock sandbox** — bterminal-core/src/sandbox.rs, SandboxConfig, pre_exec() integration, per-project toggle
#### Wave 3: Integration (3 agents)
- [x] **Error classification** — error-classifier.ts (6 types, retry logic), 20 tests
- [x] **Audit log** — audit_log table, AuditLogTab.svelte, audit-bridge.ts
- [x] **Team agent orchestration** — install_cli_tools(), register_agents_from_groups(), bidirectional contacts, review channels
- [x] **Optimistic locking** — version column in bttask, conflict detection in Rust + Python CLI
- [x] **Usage meter** — UsageMeter.svelte, AgentPane integration
#### Key Fixes
- PRAGMA journal_mode=WAL: changed to query_row for bundled-full compatibility (session/mod.rs, btmsg.rs, bttask.rs)
- SidecarConfig: changed to `Mutex<SidecarConfig>` for interior mutability (sandbox updates)
- New Cargo deps: notify-rust 4, keyring 3 (linux-native), landlock 0.4
#### Verification
- [x] Vitest: 409 passed, 0 failed (+21 from prior)
- [x] Cargo: 109 passed, 0 failed (+41 from prior)
- [x] No regressions
---
### Session: v3 Hardening Sprint (2026-03-12)
Executed tribunal-recommended hybrid S-2/S-1 hardening sprint. Fixed 3 security/resilience issues, added TLS, fixed gitignore bug.
#### Subagent Delegation Fix
- [x] Root cause: Manager system prompt had no mention of Agent tool / delegation capability
- [x] Added "Multi-Agent Delegation" section to Manager workflow in `agent-prompts.ts`
- [x] Inject `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var for Manager agents in `AgentSession.svelte`
#### TLS for bterminal-relay
- [x] Added `--tls-cert` and `--tls-key` optional CLI args to relay binary
- [x] `build_tls_acceptor()` using `native_tls::Identity::from_pkcs8`
- [x] Refactored to generic `accept_ws_with_auth<S>` and `run_ws_session<S>` (avoids code duplication)
- [x] Client side already supports `wss://` via `connect_async` with native-tls feature — no changes needed
- [x] Certificate pinning deferred to v3.1 per tribunal risk matrix
#### Security Hardening
- [x] **WAL checkpoint**`checkpoint_wal()` + `spawn_wal_checkpoint_task()` in lib.rs. Runs `PRAGMA wal_checkpoint(TRUNCATE)` every 5 minutes on sessions.db + btmsg.db. 2 tests
- [x] **Landlock logging** — Improved fallback message: "Kernel 6.2+ required for enforcement" + 3-state enforcement comments
- [x] **Plugin sandbox** — Already hardened (13 shadowed globals, `this` binding to undefined). Documented known `new Function()` escape vectors in JSDoc
#### Gitignore Fix
- [x] Root `.gitignore` had `plugins/` which matched `v2/src/lib/plugins/` (source code). Narrowed to `/plugins/` and `/v2/plugins/` (runtime dirs only)
- [x] Tracked previously-ignored `plugin-host.ts` source file
- [x] Added `plugin-host.test.ts` with 35 tests (sandbox globals, permissions, lifecycle)
#### Verification
- [x] Vitest: 444 passed, 0 failed (+35 plugin sandbox tests)
- [x] Cargo: 111 passed, 0 failed (+2 WAL checkpoint tests)
- [x] Full workspace compiles clean