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