Commit graph

252 commits

Author SHA1 Message Date
Hibryda
fd2f626c20 fix(electrobun): GTK FFI direct resize via gtk_window_resize/move
Electrobun's setSize respects WebView min-size constraint. Bypass it
with direct gtk_window_resize() + gtk_window_move() FFI calls.
clearMinSizeTree() runs on every resize frame to suppress WebView
re-propagation. gtkSetFrame() exported as new RPC endpoint.
2026-03-25 13:16:39 +01:00
Hibryda
e6635e436c fix(electrobun): JS-based window resize replaces GTK begin_resize_drag
GTK begin_resize_drag loses grip when cursor moves inward past the 6px
handle zone. Replaced with document-level mousemove/mouseup listeners
that compute delta from initial frame and call setPosition+setSize.

- clearMinSize RPC clears WebView min-size before resize starts
- Cursor locks to resize direction during drag (body.style.cursor)
- user-select disabled during drag to prevent text selection
- Frame captured async before resize starts (no race condition)
2026-03-25 13:09:15 +01:00
Hibryda
290ae8ef86 fix(electrobun): clear min-size before each resize drag (enables shrink)
WebKitWebView re-propagates content size as minimum on every layout cycle,
overriding the one-time init fix. Now clearMinSizeTree() runs right before
each gtk_window_begin_resize_drag call, allowing resize-in (shrink).

Also removed red debug background from resize handles.
2026-03-25 13:00:11 +01:00
Hibryda
d84feb6c67 fix(electrobun): enable window resize via recursive GTK min-size override
Root cause: WebKitWebView requests min_size = content_size (5120x1387 on
ultrawide), which GTK propagates to WM_NORMAL_HINTS, blocking all resize.

Fix: recursively walk the GTK widget tree (GtkWindow → GtkBin → WebView)
and call gtk_widget_set_size_request(-1, -1) on every widget. Then set
gtk_window_set_geometry_hints with min=400x300.

Result: WM_NORMAL_HINTS now shows min=10x10, window is fully resizable.
2026-03-25 12:53:24 +01:00
Hibryda
9da9d96ebd feat(electrobun): native GTK drag/resize via gtk_window_begin_resize_drag
- gtk-window.ts: FFI wrapper calling libgtk-3.so.0 directly via bun:ffi
- begin_resize_drag: delegates resize to window manager (zero CPU, smooth)
- begin_move_drag: delegates move to window manager (replaces JS drag)
- Removed all JavaScript-based drag/resize logic (no mousemove/mouseup)
- RPC: window.beginResize + window.beginMove
- Resize handles: 4px edges + 8px corners with proper cursors
2026-03-25 02:23:24 +01:00
Hibryda
48d32f6f28 fix(electrobun): use setPosition+setSize instead of setFrame, add resize throttle 2026-03-25 02:09:54 +01:00
Hibryda
cfb7dafa17 fix(electrobun): setFrame takes positional args, not object — was causing resize jump 2026-03-25 02:02:36 +01:00
Hibryda
31e7c422e4 fix(electrobun): resize race condition — use cached frame, not async getFrame 2026-03-25 01:57:21 +01:00
Hibryda
31338ad949 feat(electrobun): custom window chrome — no title bar, sidebar drag, edge resize
- titleBarStyle: "hidden" removes native GTK decoration
- Sidebar + right-bar: mousedown starts window drag (skips buttons)
- 8 resize handles (N/S/E/W + 4 corners) with 4px hot zones
- window.setFrame RPC for atomic position+size updates
- Min window size: 600x400
- Cursor feedback: grab on sidebars, directional resize on edges
- Frame persisted to SQLite on drag/resize end (debounced)
2026-03-25 01:52:17 +01:00
Hibryda
1de6c93e01 feat(electrobun): settings overhaul — fonts, shells, providers, retention, chords
- Settings drawer: responsive width clamp(24rem, 45vw, 50rem)
- System font detection: fc-list for UI fonts (preferred sans-serif starred)
  and mono fonts (Nerd Fonts starred), fallback to hardcoded lists
- Scrollback: default 5000, min 1000, step 500
- Shell detection: system.shells RPC, pre-selects $SHELL login shell
- Provider enablement: provider.scan gates toggle, unavailable shown as N/A
- Session retention: count 0-100 (0=Keep all), age 0-365 (0=Forever)
- Chord keybindings: Ctrl+K → Ctrl+S style multi-key sequences,
  1s prefix wait, arrow separator display, 26 tests passing
2026-03-25 01:42:34 +01:00
Hibryda
afaa2253de feat(electrobun): auto-detect Claude models via OAuth token from CLI credentials 2026-03-25 01:15:37 +01:00
Hibryda
a4d180d382 fix(electrobun): wizard fixes — native dialog, models, PathBrowser, ensureDir
- Native dialog: resolve to nearest existing parent dir, detect user cancel
  (exit code 1) vs actual error, add createIfMissing option
- Claude models: fallback to KNOWN_CLAUDE_MODELS (6 models) when API key
  unavailable. Adds Opus 4.6, Sonnet 4.6, Opus 4.5, Sonnet 4, Haiku 4.5,
  Sonnet 3.7. Live API paginated to limit=100.
- PathBrowser: Select button moved to sticky header (always visible).
  Current path shown compact in header with RTL ellipsis.
- files.ensureDir RPC: creates directory recursively before project creation
- files.ensureDir added to RPC schema
2026-03-25 01:05:15 +01:00
Hibryda
162b5417e4 feat(electrobun): hierarchical state tree (Rule 58)
New files:
- project-state.types.ts: all per-project state interfaces
- project-state.svelte.ts: unified per-project state with version counter
- app-state.svelte.ts: root facade re-exporting all stores as appState.*

Rewired components (no more local $state):
- ProjectCard: reads via appState.agent.* and appState.project.tab.*
- TerminalTabs: state in appState.project.terminals.*
- FileBrowser: state in appState.project.files.*
- CommsTab: state in appState.project.comms.*
- TaskBoardTab: state in appState.project.tasks.*

All follow Rule 57 (no $derived with new objects) and Rule 58
(state tree architecture, components are pure renderers).
2026-03-24 15:20:09 +01:00
Hibryda
ae4c07c160 chore: add rule 58 — hierarchical state tree architecture (mandatory) 2026-03-24 14:42:07 +01:00
Hibryda
aaeee808c3 chore: add rule 57 — Svelte 5 reactivity safety (prevent infinite loops) 2026-03-24 13:09:04 +01:00
Hibryda
20e4d2cdec fix(electrobun): eliminate $effect/$derived cycles in 3 more components
TaskBoardTab: $effect called loadTasks() which wrote ++pollToken ($state)
→ triggered $effect re-run → infinite loop. Fix: onMount instead.
Also tasksByCol $derived created new objects via .reduce/.filter.

FileBrowser: $effect read openDirs (via new Set(openDirs)) AND wrote to
it (openDirs = s) → infinite loop. Fix: onMount with fresh Set.

CommsTab: $effect called loadChannels()/loadAgents() which wrote $state
→ potential cycle. Fix: onMount instead.

Rule: NEVER use $effect for initialization that writes to $state.
Always use onMount for async init + side effects.
2026-03-24 13:03:48 +01:00
Hibryda
8d09632879 fix(electrobun): eliminate ALL $derived from ProjectCard — 0% CPU achieved
Root cause: $derived with store getter functions (.filter/.map/?.operator)
created new object references on every evaluation. Svelte 5 interpreted
these as "changed values" → triggered re-render → re-evaluated $derived
→ new references → infinite loop (115% CPU).

Fix: replaced ALL $derived in ProjectCard with plain getter functions.
Functions are called in the template — Svelte tracks the inner $state
reads but doesn't create intermediate reactive nodes that can loop.

Verified via bisect:
- Skeleton (no ProjectCard): 0% CPU
- ProjectCard with $derived: 115% CPU
- ProjectCard with plain functions: 0% CPU (0 ticks in 5s)

Also fixed: CommandPalette $effect that read+wrote selectedIdx.
2026-03-24 12:59:11 +01:00
Hibryda
774016ba11 fix(electrobun): remove read+write cycle in CommandPalette clamp selection 2026-03-24 12:54:58 +01:00
Hibryda
b2b1e429dc chore: add rule 56 — Electrobun launch sequence (PTY + Vite before app) 2026-03-24 12:19:04 +01:00
Hibryda
2709600319 fix(electrobun): isolate blink state to store, prevent prop-cascade re-renders
Root cause found via bisect: blinkVisible prop changed every 500ms,
causing complete re-render of ALL ProjectCard trees (AgentPane, Terminal,
all tabs) — even display:none content is re-evaluated by Svelte 5.

Fix: blink-store.svelte.ts owns the timer. StatusDot reads directly
from store, not from parent prop. No prop cascades.

Also: replaced $derived with .filter()/.map() (creates new arrays)
with plain functions in ProjectCard to prevent reactive loops.
2026-03-24 12:05:39 +01:00
Hibryda
d08227fc98 fix(electrobun): re-apply ALL reactive cycle fixes after backup restore
All object-creating template calls replaced with $derived locals:
filteredProjects, activeGroup, totalTokens, totalCost, wizardGroups,
wizardExistingNames. getMountedGroupIds removed entirely.
2026-03-23 22:28:21 +01:00
Hibryda
86251f9d92 fix(electrobun): eliminate remaining reactive cycles (tabs store + palette)
project-tabs-store: replaced Map reassignment (_tabs = new Map(_tabs)) with
version counter pattern. Map reassignment created new object reference on
every getActiveTab() call from $derived → infinite loop.

CommandPalette: replaced $derived COMMANDS array with plain function call.
$derived with .map() created new array every evaluation → infinite loop
when any i18n state changed.
2026-03-23 22:19:38 +01:00
Hibryda
9d45caa8df fix(electrobun): replace $effect scroll with MutationObserver in onMount
The $effect reading messages.length + requestAnimationFrame was a secondary
cause of effect_update_depth_exceeded. MutationObserver is non-reactive —
observes DOM changes directly without Svelte dependency tracking.
2026-03-23 22:07:16 +01:00
Hibryda
02560e341d fix(electrobun): remove requestAnimationFrame scroll effect (was contributing to effect cycle) 2026-03-23 22:04:33 +01:00
Hibryda
085b88107f fix(electrobun): replace object-creating store calls in template with $derived locals
getMountedGroupIds()/getFilteredProjects()/getActiveGroup()/getTotalCost/Tokens
all created new objects per render → Svelte 5 saw 'changed' → re-render → new
objects → infinite effect_update_depth_exceeded loop.

Fix: compute once in $derived variables, reference stable locals in template.
2026-03-23 22:03:12 +01:00
Hibryda
de59f0e4d0 fix(electrobun): replace tick() with requestAnimationFrame in AgentPane
tick() inside $effect triggers effect re-entry: flush_effects → tick →
flushSync → flush_effects → infinite loop (effect_update_depth_exceeded).
requestAnimationFrame defers DOM scroll to next frame outside the effect.
2026-03-23 21:46:16 +01:00
Hibryda
4a5e4d9733 fix(electrobun): attempt new-window-open handler for GTK false Ctrl+click
Root cause: WebKitGTK reports stale modifier state (0x14=Ctrl+Alt) after
SIGTERM of previous instance. Electrobun interprets this as Cmd+click and
opens a new window, which closes the main window.

Finding: when modifier state is clean (0x10, isCtrlHeld=0), the window
opens correctly. The event emitter API isn't publicly exported from
electrobun/bun — needs upstream fix or different approach.
2026-03-23 21:43:01 +01:00
Hibryda
85f55c19a6 fix(electrobun): remove i18n cycle — move lang/dir sync into setLocale() 2026-03-23 21:35:47 +01:00
Hibryda
805d1e533d fix(electrobun): loadLastSession in onMount not $effect (prevents infinite reactive cycle) 2026-03-23 21:19:30 +01:00
Hibryda
a4c0435b56 fix(electrobun): untrack blink+session timer writes to prevent effect_update_depth_exceeded 2026-03-23 21:16:54 +01:00
Hibryda
f2e8b07d7f refactor(electrobun): simplify bun backend — extract db-utils, merge handlers
- db-utils.ts: shared openDb() (WAL, busy_timeout, foreign_keys, mkdirSync)
- 5 DB modules use openDb() instead of duplicated PRAGMA boilerplate
- bttask-db shares btmsg-db's Database handle (was duplicate connection)
- misc-handlers.ts: 14 inline handlers extracted from index.ts
- index.ts: 349→195 lines (only window controls remain inline)
- updater.ts: removed dead getLastKnownVersion()
- Net reduction: ~700 lines of duplicated boilerplate
2026-03-23 21:09:57 +01:00
Hibryda
2b1194c809 refactor(electrobun): centralize all shared state into global stores
New stores:
- ui-store.svelte.ts: settingsOpen, paletteOpen, searchOpen, notifDrawerOpen,
  showWizard, settingsCategory, projectToDelete, showAddGroup, newGroupName
- project-tabs-store.svelte.ts: per-project activeTab + activatedTabs via Map

Wired:
- App.svelte: 8 inline $state removed, reads/writes via ui-store
- ProjectCard: activeTab/activatedTabs from project-tabs-store
- SettingsDrawer: activeCategory from ui-store
- CommandPalette: 4 commands call ui-store directly (no CustomEvent dispatch)

Components are now pure view layers reading from stores.
2026-03-23 20:26:07 +01:00
Hibryda
c88577a34a refactor(electrobun): modularize stores + shared UI components
Stores:
- notifications-store.svelte.ts: owns notifications array (was inline in App)
- workspace-store.svelte.ts: extended with addProjectFromWizard, loadGroupsFromDb,
  loadProjectsFromDb, derived getters (totalCost, totalTokens, mountedGroupIds)

Shared UI components (ui/):
- SegmentedControl.svelte: replaces repeated .seg button groups
- SliderInput.svelte: labeled range slider with value display
- StatusDot.svelte: colored dot with pulse support
- IconButton.svelte: icon-only button with tooltip, 3 sizes
- Section.svelte: settings section wrapper with heading

App.svelte: script 390→221 lines (removed all inline CRUD, delegates to stores)
ProjectCard: uses StatusDot shared component
AgentSettings + OrchestrationSettings: use SegmentedControl, SliderInput, Section
2026-03-23 19:42:47 +01:00
Hibryda
265ddd3f1d fix(electrobun): remove unused idx in CustomDropdown grouped each 2026-03-23 19:27:57 +01:00
Hibryda
e8278ef444 fix(electrobun): CustomDropdown groups .by + remove function call syntax 2026-03-23 19:26:21 +01:00
Hibryda
bd48a09fd8 fix(electrobun): remove {#if} lazy mount on all 7 tabs — eager mount with display:none (WebKitGTK hit-test fix) 2026-03-23 19:20:18 +01:00
Hibryda
b506dfc39a feat(electrobun): upgrade to xterm 6.0.0, disable incompatible Canvas/Image addons (DOM renderer) 2026-03-23 16:22:30 +01:00
Hibryda
adee8e03c5 fix(electrobun): downgrade xterm to 5.5.0 (CanvasAddon 0.7.0 requires ^5.0.0, not 6.0.0) 2026-03-23 16:07:17 +01:00
Hibryda
aee772aee0 fix(electrobun): load CanvasAddon+ImageAddon AFTER term.open() (fixes _linkifier2 crash) 2026-03-23 15:57:37 +01:00
Hibryda
f4a51ca6c0 fix(electrobun): defer xterm.open() until container visible (fixes splash crash) 2026-03-23 15:54:35 +01:00
Hibryda
75391fb1e9 fix(electrobun): add 10s init timeout + splash loading fallback text 2026-03-23 15:48:32 +01:00
Hibryda
e61473b025 fix(electrobun): wizard creation flow + GitLab probe + shell detection + dropdown flip
- Git probe tries GitHub then GitLab for owner/repo shorthand
- Shows "Found on GitHub/GitLab" with platform indicator
- system.shells RPC detects installed shells (bash/zsh/fish/sh/dash)
- CustomDropdown flip logic uses 200px threshold for flip-up
- Project creation properly persists all wizard fields + adds card
2026-03-23 15:34:57 +01:00
Hibryda
021feba3ed fix(electrobun): wizard 7 fixes — validation, GitLab, SSHFS, icons, model dropdown, keyboard nav
- Git Platform: validates repo via git.probe before enabling Next, supports GitHub + GitLab + any git URL
- Template dir configurable in Advanced Settings (template_dir key)
- SSHFS: checks sshfs availability, mountpoint selector when enabled
- CustomDropdown: flip-up when insufficient space below
- 50 Lucide icons (was 24) with categories (AI, Data, DevOps, Security, Media, Comms)
- Model: CustomDropdown from live API, max_tokens as slider, effort only with adaptive thinking
- Keyboard: Escape closes wizard, Tab navigation with :focus-visible rings, source cards navigable
2026-03-23 14:20:30 +01:00
Hibryda
41b8d46a19 feat(electrobun): complete project wizard phases 4-5
- ModelConfigPanel: Claude thinking/effort, Codex sandbox/approval, Ollama
  temp/ctx/predict, Gemini thinkingLevel/budget (mutually exclusive)
- CustomDropdown: themed with keyboard nav, groupBy, display:none pattern
- CustomCheckbox/CustomRadio: themed with Lucide icons
- Replaced native selects in 5 settings panels + wizard steps
- wizard-state.ts: shared type definitions
- Gemini added as 4th provider throughout
2026-03-23 13:12:47 +01:00
Hibryda
d4014a193d feat(electrobun): project wizard phases 1-5 (WIP)
- sanitize.ts: input sanitization (trim, control chars, path traversal)
- provider-scanner.ts: detect Claude/Codex/Ollama/Gemini availability
- model-fetcher.ts: live model lists from 4 provider APIs
- ModelConfigPanel.svelte: per-provider config (thinking, effort, sandbox, temperature)
- WizardStep1-3.svelte: split wizard into composable steps
- CustomDropdown/Checkbox/Radio: themed UI components
- provider-handlers.ts: provider.scan + provider.models RPC
- Wire providers into wizard step 3 (live detection + model lists)
- Replace native selects in 5 settings panels with CustomDropdown
2026-03-23 13:05:07 +01:00
Hibryda
b7fc3a0f9b fix(electrobun): replace native <select> with fully themed custom dropdowns
All 3 wizard selects (branch, group, shell) now use custom dropdown
components with --ctp-* themed menu, hover states, active highlight.
No native OS styling leaks through.
2026-03-22 12:52:24 +01:00
Hibryda
f1f965f8b2 docs: strengthen rule 55 — single atomic kill→launch, no exceptions 2026-03-22 12:44:39 +01:00
Hibryda
d8b831089a fix(electrobun): XDG dirs, sanitized errors, themed dropdowns + checkboxes
- PathBrowser: dynamic XDG shortcuts (only shows dirs that exist)
- Errors sanitized: "Directory not found" instead of raw ENOENT
- Select dropdowns: custom arrow, themed options, appearance:none
- Checkboxes: custom styled with --ctp-blue check, themed border
2026-03-22 12:40:56 +01:00
Hibryda
f71ca2e1ca fix(electrobun): PathBrowser width constrained, position relative (no full-width stretch) 2026-03-22 12:33:18 +01:00
Hibryda
42ca15535b fix(electrobun): limit PathBrowser max-width to 32rem (ultrawide fix) 2026-03-22 12:28:51 +01:00