BTerminal/docs/v3-progress.md

23 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