BTerminal/docs/v3-progress.md

31 KiB

BTerminal 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_save Tauri 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 --group CLI 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 call clearAllAgentSessions() + 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', ...))
  • Global settings load on mount via getSetting() from settings-bridge
  • Added imports: onMount, getSetting/setSetting, getCurrentFlavor/setFlavor, CatppuccinFlavor type

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/for label association (e.g., id="theme-flavor", id="default-shell")

CSS Cleanup

  • Removed unused .project-field label selector (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 CatppuccinFlavor type to ThemeId union type (11 values)
  • Added 7 new editor themes: VSCode Dark+, Atom One Dark, Monokai, Dracula, Nord, Solarized Dark, GitHub Dark
  • Added ThemePalette interface (26-color slots) — all themes map to same slots
  • Added ThemeMeta interface (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(): ThemeId replaces getCurrentFlavor() as primary getter
  • setTheme(theme: ThemeId) replaces setFlavor() as primary setter
  • initTheme() validates saved theme against ALL_THEME_IDS
  • Deprecated getCurrentFlavor() and setFlavor() with delegation wrappers

SettingsTab Theme Selector

  • Theme dropdown uses <optgroup> per theme group (Catppuccin, Editor)
  • themeGroups derived from THEME_LIST using Map grouping
  • handleThemeChange() replaces direct setFlavor() call
  • 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

  • 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 ThemeId union type from 11 to 17 values
  • Added THEME_LIST entries with group: '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 getPalette import 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: 180px on .theme-dropdown container (was min-width: 0) to prevent trigger from collapsing
  • Set min-width: 280px on .theme-options dropdown menu (was right: 0) to ensure full theme names visible
  • Increased max-height from 320px to 400px on dropdown menu for better scrolling experience
  • 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

  • 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_size keys)
  • handleFontFamilyChange() and handleFontSizeChange() functions with validation

SettingsTab Layout Restructure

  • Restructured global settings from inline .setting-row (label left, control right) to 2-column .global-grid with .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-family and --ui-font-size to catppuccin.css :root (defaults: JetBrains Mono fallback chain, 13px)
  • Updated app.css body rule to use CSS vars instead of hardcoded font values

Theme Store Font Restoration

  • Extended initTheme() in theme.svelte.ts to load and apply saved font_family and font_size settings 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 icons record 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-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
  • Re-added 'settings' to WorkspaceTab union type (was removed when settings was a drawer)
  • SettingsTab CSS: changed flex: 1 to height: 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 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)

  • 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-family and --term-font-size CSS 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.md enforcing 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.md rule 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-panel from fixed width: 28em to width: max-content with min-width: 16em and max-width: 50%
  • Changed .sidebar-panel and .panel-content from overflow: hidden to overflow-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: nowrap on .ctx-header/.ctx-error for intrinsic width measurement

Fix: Sidebar Drawer Content-Driven Width

  • 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
  • Removed v2 grid + both media queries from #app — v3 .app-shell manages its own flexbox layout
  • 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
  • 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-row only 260px → traced to #app grid layout

Session: 2026-03-08 — Native Directory Picker

tauri-plugin-dialog Integration

  • Added tauri-plugin-dialog Rust crate + @tauri-apps/plugin-dialog npm package
  • Registered plugin in lib.rs (tauri_plugin_dialog::init())
  • Removed stub pick_directory Tauri command (always returned None)
  • Added browseDirectory() helper in SettingsTab.svelte using open({ directory: true })
  • Added folder browse button (folder SVG icon) to: Default CWD, existing project CWD, Add Project path
  • Styled .input-with-browse layout (flex row, themed browse button)
  • Fixed nested input theme: .setting-field .input-with-browse input selector for dark background
  • Fixed dialog not opening: added "dialog:default" permission to v2/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-dialog skips set_parent(&window) on Linux via cfg(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_directory Tauri command using rfd::AsyncFileDialog directly with .set_parent(&window) — modal on Linux
  • Fix: std::env::set_var("GTK_THEME", "Adwaita:dark") at start of run() 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-dialog open() to invoke<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: 16rem instead of collapsing to content
  • Session area: min-height: 0 for proper flex child overflow

AgentPane Prompt Layout

  • Prompt area anchored to bottom (justify-content: flex-end) instead of vertical center
  • Removed max-width: 600px constraint 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 + projectName props (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 profile prop — resolved via listProfiles() 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.profile to 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_log Tauri command for frontend→Rust tracing bridge
  • Created v2/src/lib/adapters/telemetry-bridge.tstel.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 pendingPersistCount counter + waitForPendingPersistence() export in agent-dispatcher.ts
  • persistSessionForProject() increments/decrements counter in try/finally
  • switchGroup() in workspace.svelte.ts now awaits waitForPendingPersistence() before clearing state
  • SettingsTab.svelte switchGroup onclick handler made async with await
  • Added test for waitForPendingPersistence in agent-dispatcher.test.ts
  • Added mock for waitForPendingPersistence in 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:e2e npm 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 — use browser.execute() for JS-level clicks
  • Fixed CSS text-transform: .ptab getText() returns uppercase — use .toLowerCase() for comparison
  • Fixed element scoping: browser.$('.ptab') returns ALL tabs across project boxes — scope via box.$('.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)

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)

Verification

  • svelte-check: 0 new errors (only pre-existing esrap type errors)
  • vitest: 139/139 tests pass
  • cargo check: compiles cleanly