From 579157f6dacb0c45491b4579b0147dbb57570a5e Mon Sep 17 00:00:00 2001 From: Hibryda Date: Sun, 22 Mar 2026 03:48:41 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=202=20=E2=80=94=20store=20audit,?= =?UTF-8?q?=20migration=20clusters,=20ADR,=20settings=20domain=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MIGRATION_CLUSTERS.md: reactive dependency graph across 20 bridges/stores - PHASE1_STORE_AUDIT.md: 11 stores audited (3 clean, 5 bridge-dependent, 3 platform-specific) - ADR-001: dual-stack binding strategy (accepted, S-1+S-3 hybrid) - Settings domain migration: all 6 settings components + App.svelte + FilesTab migrated from settings-bridge to getBackend() calls --- MIGRATION_CLUSTERS.md | 215 ++++++++++++++ PHASE1_STORE_AUDIT.md | 267 ++++++++++++++++++ .../adr-001-dual-stack-binding.md | 131 +++++++++ src/App.svelte | 2 +- src/lib/adapters/settings-bridge.ts | 13 - src/lib/components/Workspace/FilesTab.svelte | 2 +- .../components/Workspace/SettingsTab.svelte | 2 +- .../categories/AdvancedSettings.svelte | 2 +- .../settings/categories/AgentSettings.svelte | 2 +- .../categories/AppearanceSettings.svelte | 2 +- .../categories/OrchestrationSettings.svelte | 2 +- .../categories/ProjectSettings.svelte | 2 +- .../categories/SecuritySettings.svelte | 2 +- src/lib/stores/plugins.svelte.ts | 2 +- src/lib/stores/settings-scope.svelte.ts | 2 +- src/lib/stores/settings-store.svelte.ts | 31 ++ src/lib/stores/theme.svelte.ts | 2 +- src/lib/styles/custom-themes.ts | 2 +- 18 files changed, 657 insertions(+), 26 deletions(-) create mode 100644 MIGRATION_CLUSTERS.md create mode 100644 PHASE1_STORE_AUDIT.md create mode 100644 docs/architecture/adr-001-dual-stack-binding.md delete mode 100644 src/lib/adapters/settings-bridge.ts create mode 100644 src/lib/stores/settings-store.svelte.ts diff --git a/MIGRATION_CLUSTERS.md b/MIGRATION_CLUSTERS.md new file mode 100644 index 0000000..700a0b9 --- /dev/null +++ b/MIGRATION_CLUSTERS.md @@ -0,0 +1,215 @@ +# Migration Clusters: Reactive Dependency Graph Analysis + +Phase 2 binding analysis — reactive state dependencies across stores, bridge adapters, and components. + +## Store Dependency Matrix + +### agents.svelte.ts +- **Bridge imports:** claude-messages (type only) +- **Store imports:** none +- **Reactive state:** `$state` (sessions) +- **Exported functions:** getAgentSessions, getAgentSession, createAgentSession, updateAgentStatus, setAgentSdkSessionId, setAgentModel, appendAgentMessage(s), updateAgentCost, findChildByToolUseId, getChildSessions, getTotalCost, clearAllAgentSessions, removeAgentSession +- **Consumed by stores:** workspace, health, wake-scheduler +- **Consumed by components:** AgentPane, AgentPreviewPane, AgentTree, AgentCard, AgentSession, ContextTab, MetricsPanel, StatusBar, TeamAgentsPanel +- **Consumed by utils:** session-persistence, subagent-router, agent-dispatcher + +### workspace.svelte.ts +- **Bridge imports:** groups-bridge, btmsg-bridge +- **Store imports:** agents, health, conflicts, wake-scheduler +- **Reactive state:** `$state`, `$state` (activeGroupId), `$state`, `$state` (activeProjectId), `$state>`, `$state` (focusFlash) +- **Exported functions:** get*/set* for all state, switchGroup, load/saveWorkspace, add/remove/update Group/Project/Agent, terminal tab mgmt, event callbacks +- **Consumed by stores:** wake-scheduler +- **Consumed by components:** GlobalTabBar, ProjectGrid, ProjectBox, GroupAgentsPanel, CommandPalette, SettingsTab, DocsTab, SearchOverlay, TerminalTabs, SshTab, AgentSession, StatusBar, CommsTab, ProjectSettings +- **Consumed by utils:** auto-anchoring, agent-dispatcher (via waitForPendingPersistence) + +### health.svelte.ts +- **Bridge imports:** none +- **Store imports:** agents, conflicts +- **Reactive state:** `$state` (trackers, stallThresholds), `$state` (tickTs) +- **Exported functions:** trackProject, untrackProject, setStallThreshold, updateProjectSession, recordActivity, recordToolDone, recordTokenSnapshot, start/stopHealthTick, setReviewQueueDepth, clearHealthTracking, getProjectHealth, getAllProjectHealth, getAttentionQueue, getHealthAggregates +- **Consumed by stores:** workspace (import clearHealthTracking), wake-scheduler +- **Consumed by components:** ProjectBox, ProjectHeader, MetricsPanel, StatusBar, AgentSession +- **Consumed by utils:** agent-dispatcher, attention-scorer (type only) + +### conflicts.svelte.ts +- **Bridge imports:** none (types/ids only) +- **Store imports:** none +- **Reactive state:** `$state` (projectFileWrites, acknowledgedFiles, sessionWorktrees, agentWriteTimestamps) +- **Exported functions:** setSessionWorktree, recordFileWrite, recordExternalWrite, getExternalConflictCount, getProjectConflicts, hasConflicts, getTotalConflictCount, acknowledgeConflicts, clearSessionWrites, clearProjectConflicts, clearAllConflicts +- **Consumed by stores:** workspace (import clearAllConflicts), health +- **Consumed by components:** ProjectBox, ProjectHeader, StatusBar +- **Consumed by utils:** agent-dispatcher + +### anchors.svelte.ts +- **Bridge imports:** anchors-bridge +- **Store imports:** none +- **Reactive state:** `$state` (projectAnchors), `$state` (autoAnchoredProjects) +- **Exported functions:** get/add/remove/change anchor(s), loadAnchorsForProject, hasAutoAnchored, markAutoAnchored, getAnchorSettings +- **Consumed by components:** AgentPane, ContextTab, AgentSession +- **Consumed by utils:** auto-anchoring, agent-dispatcher + +### theme.svelte.ts +- **Bridge imports:** settings-bridge +- **Store imports:** none +- **Reactive state:** `$state`, `$state` +- **Exported functions:** getCurrentTheme, getXtermTheme, setTheme, initTheme, previewPalette, clearPreview, setCustomTheme, onThemeChange +- **Consumed by components:** TerminalPane, AgentPreviewPane, SettingsTab, AppearanceSettings, ThemeEditor +- **Consumed by:** App.svelte (init) + +### plugins.svelte.ts +- **Bridge imports:** plugins-bridge, settings-bridge +- **Store imports:** none +- **Reactive state:** `$state`, `$state` +- **Exported functions:** get/add/removePluginCommands, pluginEventBus, getPluginEntries, setPluginEnabled, loadAllPlugins, reloadAllPlugins, destroyAllPlugins +- **Consumed by components:** CommandPalette, SettingsTab, AdvancedSettings +- **Consumed by:** plugin-host.ts + +### machines.svelte.ts +- **Bridge imports:** remote-bridge +- **Store imports:** notifications +- **Reactive state:** `$state` +- **Exported functions:** getMachines, getMachine, loadMachines, addMachine, removeMachine, connectMachine, disconnectMachine, init/destroyMachineListeners +- **Consumed by components:** (used by SettingsTab multi-machine section) + +### wake-scheduler.svelte.ts +- **Bridge imports:** bttask-bridge, audit-bridge +- **Store imports:** health, workspace, agents +- **Reactive state:** `$state` (registrations, pendingWakes) +- **Exported functions:** disableWakeScheduler, register/unregisterManager, updateManager*, getWakeEvent, consumeWakeEvent, forceWake, clearWakeScheduler +- **Consumed by stores:** workspace (import clearWakeScheduler) +- **Consumed by components:** ProjectBox, AgentSession, StatusBar + +### notifications.svelte.ts +- **Bridge imports:** notifications-bridge +- **Store imports:** none +- **Reactive state:** `$state`, `$state` +- **Exported functions:** getNotifications, notify, dismissNotification, addNotification, getNotificationHistory, getUnreadCount, markRead, markAllRead, clearHistory +- **Consumed by stores:** machines +- **Consumed by components:** ToastContainer, NotificationCenter, ProjectBox, AgentPane +- **Consumed by utils:** handle-error, global-error-handler, auto-anchoring, agent-dispatcher + +### layout.svelte.ts +- **Bridge imports:** session-bridge +- **Store imports:** none +- **Reactive state:** `$state`, `$state`, `$state` (focusedPaneId) +- **Exported functions:** getPanes, getActivePreset, getFocusedPaneId, add/removePane, focusPane, setPreset, renamePaneTitle, setPaneGroup, restoreFromDb, getGridTemplate, getPaneGridArea +- **Consumed by components:** AgentPane (focusPane) +- **Consumed by utils:** detach (type only), subagent-router + +### settings-scope.svelte.ts +- **Bridge imports:** settings-bridge +- **Store imports:** none +- **Reactive state:** `$state` (activeProjectId), `$state` (overrideCache) +- **Exported functions:** setActiveProject, getActiveProjectForSettings, scopedGet/Set, removeOverride, getOverrideChain, hasProjectOverride, invalidateSettingsCache +- **Consumed by components:** ProjectSettings + +### sessions.svelte.ts +- **Bridge imports:** none +- **Store imports:** none +- **Reactive state:** `$state` +- **Exported functions:** getSessions, addSession, removeSession +- **Consumed by:** (v2 legacy, minimal usage) + +--- + +## Migration Clusters + +Clusters are groups of files that share reactive state and must migrate atomically. + +### Cluster 1: Pure State (No Bridge Dependencies) +**Files:** `agents.svelte.ts`, `conflicts.svelte.ts`, `sessions.svelte.ts` +**Dependency:** None on bridges. Pure in-memory reactive state. +**Risk:** LOW. These are self-contained state containers with zero platform coupling. +**Migration:** Move to `@agor/stores` immediately. No BackendAdapter wiring needed. + +### Cluster 2: Settings Domain +**Files:** `settings-bridge.ts`, `settings-scope.svelte.ts`, `theme.svelte.ts`, `plugins.svelte.ts`, `custom-themes.ts` +**Dependency:** All import `settings-bridge.ts` (Tauri `invoke`). +**Component consumers:** SettingsTab, FilesTab, AppearanceSettings, AgentSettings, OrchestrationSettings, SecuritySettings, AdvancedSettings, ProjectSettings, App.svelte +**Risk:** MEDIUM. `settings-bridge` is the most-imported bridge (13 consumers). Theme init runs at app startup. Plugin lifecycle depends on settings for enabled state. +**Migration:** Replace `settings-bridge` imports with `getBackend().getSetting()` / `getBackend().setSetting()`. Migrate `settings-scope.svelte.ts` and `theme.svelte.ts` first, then `plugins.svelte.ts`. + +### Cluster 3: Workspace + Groups +**Files:** `workspace.svelte.ts`, `groups-bridge.ts`, `btmsg-bridge.ts` +**Dependency:** Imports groups-bridge (Tauri invoke) and btmsg-bridge (agent registration). +**Cross-store deps:** Imports agents, health, conflicts, wake-scheduler (clear functions). +**Risk:** HIGH. Central orchestration hub. 15+ component consumers. switchGroup() cascades clears across 4 stores. loadWorkspace() is app bootstrap path. +**Migration:** Replace groups-bridge calls with BackendAdapter.loadGroups()/saveGroups(). btmsg registration needs a new BackendAdapter method or stays as a separate bridge. + +### Cluster 4: Notification + Error Handling +**Files:** `notifications.svelte.ts`, `notifications-bridge.ts`, `handle-error.ts`, `global-error-handler.ts` +**Dependency:** notifications-bridge (Tauri invoke for desktop notifications). +**Risk:** LOW-MEDIUM. notifications-bridge is a single function (sendDesktopNotification). Capability-gated by `supportsDesktopNotifications`. handle-error is consumed everywhere. +**Migration:** Replace sendDesktopNotification with capability-checked BackendAdapter call. handle-error stays as-is (it calls notify() which is pure state). + +### Cluster 5: Machine Management +**Files:** `machines.svelte.ts`, `remote-bridge.ts` +**Dependency:** remote-bridge (Tauri invoke + listen for WebSocket events). +**Cross-store deps:** imports notifications +**Risk:** MEDIUM. Event listener lifecycle (listen/unlisten). 12 IPC commands. Not in BackendAdapter yet. +**Migration:** Defer. Needs BackendAdapter extension for remote machine operations. Low priority (multi-machine is advanced feature). + +### Cluster 6: Layout + Session Persistence +**Files:** `layout.svelte.ts`, `session-bridge.ts` +**Dependency:** session-bridge (Tauri invoke for SQLite session CRUD). +**Risk:** MEDIUM. V2 legacy layout — v3 uses workspace store. Still consumed by AgentPane.focusPane and subagent-router. +**Migration:** Replace session-bridge calls with BackendAdapter (needs session methods). Can coexist during transition. + +### Cluster 7: Anchors +**Files:** `anchors.svelte.ts`, `anchors-bridge.ts` +**Dependency:** anchors-bridge (Tauri invoke for SQLite anchor CRUD). +**Risk:** LOW. Self-contained, small API surface (save, load, delete, updateType). +**Migration:** Replace anchors-bridge with BackendAdapter (needs anchor methods) or standalone bridge kept temporarily. + +### Cluster 8: Wake Scheduler +**Files:** `wake-scheduler.svelte.ts`, `bttask-bridge.ts`, `audit-bridge.ts` +**Dependency:** bttask-bridge (Tauri invoke for task listing), audit-bridge (Tauri invoke for audit logging). +**Cross-store deps:** Reads from health, workspace, agents. +**Risk:** MEDIUM-HIGH. Complex evaluation logic + timer management. Multiple bridge dependencies. Reads across 3 stores. +**Migration:** Last cluster. Depends on clusters 1, 3, 8 completing first. + +--- + +## Cluster Dependency Graph + +``` +Cluster 1 (Pure State) + | + +---> Cluster 3 (Workspace) depends on Cluster 1 + | | + | +---> Cluster 8 (Wake Scheduler) depends on Clusters 1, 3, 4 + | + +---> Cluster 4 (Notifications) standalone + | + +---> Cluster 6 (Layout) standalone + | + +---> Cluster 7 (Anchors) standalone + +Cluster 2 (Settings) standalone + +Cluster 5 (Machines) depends on Cluster 4 +``` + +## Recommended Migration Order + +1. **Cluster 1: Pure State** (agents, conflicts, sessions) -- zero risk, no bridges +2. **Cluster 2: Settings Domain** -- most consumers, establishes BackendAdapter pattern +3. **Cluster 4: Notifications** -- small bridge surface, enables error handling migration +4. **Cluster 7: Anchors** -- self-contained, small +5. **Cluster 6: Layout** -- v2 legacy, low priority +6. **Cluster 3: Workspace** -- high risk, many cross-deps, central hub +7. **Cluster 5: Machines** -- needs BackendAdapter extension +8. **Cluster 8: Wake Scheduler** -- depends on everything else + +## Risk Assessment Summary + +| Cluster | Risk | Reason | +|---------|------|--------| +| 1. Pure State | LOW | No platform deps, pure memory | +| 2. Settings | MEDIUM | 13 consumers, startup path, theme init | +| 3. Workspace | HIGH | Central hub, cascading clears, 15+ components | +| 4. Notifications | LOW-MEDIUM | Single bridge function, capability-gated | +| 5. Machines | MEDIUM | Event listeners, not in BackendAdapter yet | +| 6. Layout | MEDIUM | V2 legacy, session persistence | +| 7. Anchors | LOW | Small, self-contained | +| 8. Wake Scheduler | MEDIUM-HIGH | Multi-bridge, cross-store reads, timers | diff --git a/PHASE1_STORE_AUDIT.md b/PHASE1_STORE_AUDIT.md new file mode 100644 index 0000000..ca0ef1a --- /dev/null +++ b/PHASE1_STORE_AUDIT.md @@ -0,0 +1,267 @@ +# Phase 1 Store Audit: Platform Dependency Analysis + +Categorization of each store for migration readiness to `@agor/stores`. + +## Category Definitions + +- **CLEAN:** No platform dependencies. Can move to `@agor/stores` as-is. +- **BRIDGE-DEPENDENT:** Uses bridge adapters (Tauri invoke/listen). Needs BackendAdapter migration first. +- **PLATFORM-SPECIFIC:** Contains platform-specific logic (paths, APIs). Stays in app-specific layer. + +--- + +## Store Analysis + +### 1. agents.svelte.ts +**Category: CLEAN** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | None (pure in-memory) | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | None | + +**Notes:** Only import is `type AgentMessage` from `claude-messages` (type-only, already in `@agor/types` as `AgentMessage`). Pure reactive state with no I/O. Ready to move immediately. + +--- + +### 2. workspace.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None directly (groups.json path resolved in Rust backend) | +| Persistence paths | `groups-bridge.ts` → Tauri `invoke('groups_load')` / `invoke('groups_save')` | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `groups-bridge` (Tauri invoke), `btmsg-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `loadGroups()` / `saveGroups()` → already in BackendAdapter +- `getCliGroup()` → Tauri-specific CLI arg parsing, not in BackendAdapter +- `registerAgents()` → btmsg-bridge, not in BackendAdapter + +**Notes:** `loadGroups`/`saveGroups` can migrate to `getBackend()`. `getCliGroup` and `registerAgents` need new BackendAdapter methods or remain as separate bridges. Cross-store imports (agents, health, conflicts, wake-scheduler) are all clean function calls. + +--- + +### 3. layout.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None (SQLite path resolved in Rust) | +| Persistence paths | `session-bridge.ts` → 8 Tauri invoke commands | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `session-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `listSessions`, `saveSession`, `deleteSession`, `updateSessionTitle`, `touchSession` → session CRUD +- `saveLayout`, `loadLayout` → layout persistence +- `updateSessionGroup` → group assignment + +**Notes:** V2 legacy store. V3 uses workspace.svelte.ts. Session persistence commands not yet in BackendAdapter. Could be deferred or added as BackendAdapter extension. + +--- + +### 4. health.svelte.ts +**Category: CLEAN** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | None (pure in-memory with timer) | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | None | + +**Notes:** Imports from stores only (agents, conflicts) and utils (attention-scorer). Pure computation + timers. Ready to move. + +--- + +### 5. conflicts.svelte.ts +**Category: CLEAN** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | None (session-scoped, no persistence) | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | None | + +**Notes:** Only imports `types/ids`. Pure in-memory conflict tracking. Ready to move. + +--- + +### 6. anchors.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None (SQLite path resolved in Rust) | +| Persistence paths | `anchors-bridge.ts` → 4 Tauri invoke commands | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `anchors-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `saveSessionAnchors`, `loadSessionAnchors`, `deleteSessionAnchor`, `updateAnchorType` + +**Notes:** Small, well-defined bridge surface. Could add anchor CRUD to BackendAdapter or keep as separate bridge temporarily. + +--- + +### 7. theme.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | `settings-bridge.ts` → `getSetting`/`setSetting` (Tauri invoke) | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `settings-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `getSetting('theme')`, `setSetting('theme', ...)` — theme persistence +- `getSetting('ui_font_family')` etc. — 4 font settings on init + +**Platform-specific behavior:** +- `document.documentElement.style.setProperty()` — browser API, works on both Tauri and Electrobun webviews. Not platform-specific. + +**Notes:** Simple migration — replace `getSetting`/`setSetting` with `getBackend().getSetting()`/`getBackend().setSetting()`. + +--- + +### 8. plugins.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None (plugin config dir resolved in Rust) | +| Persistence paths | `settings-bridge.ts` → plugin enabled state; `plugins-bridge.ts` → plugin discovery | +| Capability-conditioned defaults | `supportsPluginSandbox` could gate plugin loading | +| Tauri/Bun imports | `plugins-bridge` (Tauri invoke), `settings-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `discoverPlugins()` → plugins-bridge (not in BackendAdapter) +- `getSetting`/`setSetting` → plugin enabled state + +**Notes:** Plugin discovery is not in BackendAdapter. Settings part can migrate. Plugin host (`plugin-host.ts`) is pure Web Worker logic — platform-independent. + +--- + +### 9. machines.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | `remote-bridge.ts` → 5 invoke commands + 5 listen events | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `remote-bridge` (Tauri invoke + listen) | + +**Bridge dependencies:** +- `listRemoteMachines`, `addRemoteMachine`, `removeRemoteMachine`, `connectRemoteMachine`, `disconnectRemoteMachine` +- 5 event listeners: `onRemoteMachineReady`, `onRemoteMachineDisconnected`, `onRemoteError`, `onRemoteMachineReconnecting`, `onRemoteMachineReconnectReady` + +**Notes:** Heavy bridge dependency with event subscription lifecycle. Not in BackendAdapter. Defer migration. + +--- + +### 10. wake-scheduler.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | `bttask-bridge.ts` → `listTasks`; `audit-bridge.ts` → `logAuditEvent` | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `bttask-bridge` (Tauri invoke), `audit-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `listTasks(groupId)` → task listing for wake signal evaluation +- `logAuditEvent(...)` → audit logging + +**Notes:** Neither bttask nor audit operations are in BackendAdapter. Cross-store reads (health, workspace, agents). Complex timer logic is pure JS — platform independent. Bridges are the only blockers. + +--- + +### 11. notifications.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | None (ephemeral state) | +| Capability-conditioned defaults | `supportsDesktopNotifications` should gate `sendDesktopNotification` | +| Tauri/Bun imports | `notifications-bridge` (Tauri invoke) | + +**Bridge dependencies:** +- `sendDesktopNotification(title, body, urgency)` — single function, fire-and-forget + +**Notes:** Toast system is pure reactive state. Only the OS notification part needs the bridge. Easy to capability-gate: `if (getBackend().capabilities.supportsDesktopNotifications) sendDesktopNotification(...)`. + +--- + +### BONUS: settings-scope.svelte.ts +**Category: BRIDGE-DEPENDENT** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | `settings-bridge.ts` → `getSetting`/`setSetting` | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | `settings-bridge` (Tauri invoke) | + +**Notes:** Thin scoping layer over settings-bridge. Migrates with Cluster 2 (Settings Domain). + +--- + +### BONUS: sessions.svelte.ts +**Category: CLEAN** + +| Check | Result | +|-------|--------| +| Platform-specific paths | None | +| Persistence paths | None | +| Capability-conditioned defaults | None | +| Tauri/Bun imports | None | + +**Notes:** V2 legacy, minimal. Pure reactive state. + +--- + +## Summary Table + +| Store | Category | Bridge Deps | Migration Blocker | +|-------|----------|-------------|-------------------| +| agents | CLEAN | none | -- | +| conflicts | CLEAN | none | -- | +| sessions | CLEAN | none | -- | +| health | CLEAN | none | -- | +| theme | BRIDGE-DEPENDENT | settings-bridge | BackendAdapter.getSetting/setSetting (ready) | +| settings-scope | BRIDGE-DEPENDENT | settings-bridge | BackendAdapter.getSetting/setSetting (ready) | +| notifications | BRIDGE-DEPENDENT | notifications-bridge | Capability-gate sendDesktopNotification | +| anchors | BRIDGE-DEPENDENT | anchors-bridge | Need BackendAdapter anchor methods | +| plugins | BRIDGE-DEPENDENT | plugins-bridge, settings-bridge | Need BackendAdapter plugin discovery | +| layout | BRIDGE-DEPENDENT | session-bridge | Need BackendAdapter session methods | +| workspace | BRIDGE-DEPENDENT | groups-bridge, btmsg-bridge | BackendAdapter groups (ready), btmsg TBD | +| machines | BRIDGE-DEPENDENT | remote-bridge | Need BackendAdapter remote machine methods | +| wake-scheduler | BRIDGE-DEPENDENT | bttask-bridge, audit-bridge | Need BackendAdapter bttask/audit methods | + +### Ready to Migrate Now (4 stores) +- `agents.svelte.ts` — CLEAN +- `conflicts.svelte.ts` — CLEAN +- `health.svelte.ts` — CLEAN +- `sessions.svelte.ts` — CLEAN + +### Ready After Settings-Bridge Replacement (3 stores) +- `theme.svelte.ts` — settings-bridge only +- `settings-scope.svelte.ts` — settings-bridge only +- `plugins.svelte.ts` — settings-bridge + plugins-bridge + +### Requires BackendAdapter Extension (6 stores) +- `notifications.svelte.ts` — 1 method +- `anchors.svelte.ts` — 4 methods +- `layout.svelte.ts` — 8 methods +- `workspace.svelte.ts` — groups ready, btmsg TBD +- `machines.svelte.ts` — 10 methods + events +- `wake-scheduler.svelte.ts` — bttask + audit diff --git a/docs/architecture/adr-001-dual-stack-binding.md b/docs/architecture/adr-001-dual-stack-binding.md new file mode 100644 index 0000000..48b8abc --- /dev/null +++ b/docs/architecture/adr-001-dual-stack-binding.md @@ -0,0 +1,131 @@ +# ADR-001: Dual-Stack Frontend-Backend Binding Strategy + +**Status:** Accepted +**Date:** 2026-03-22 +**Deciders:** Human + Claude (tribunal consensus: Claude 72%, Codex 78%) + +## Context + +Agent Orchestrator runs on two backends: Tauri 2.x (Rust, production) and Electrobun (Bun/TypeScript, experimental). Both share the same Svelte 5 frontend but communicate through different IPC mechanisms: + +- **Tauri:** `@tauri-apps/api/core` invoke() + listen() +- **Electrobun:** RPC request/response + message listeners + +The initial implementation used 23 bridge adapter files that directly import `@tauri-apps/api`. This created three problems: + +1. **Duplicate code** — Each backend reimplements the same IPC surface. Electrobun adapters must manually replicate every bridge function. +2. **Drift risk** — When a Tauri bridge gains a new function, the Electrobun equivalent silently falls behind. No compile-time enforcement. +3. **58 Codex findings** — Three independent Codex audits identified duplicate SQL schemas, inconsistent naming (snake_case vs camelCase across the wire), missing error handling in Electrobun paths, and untested adapter code. + +The project needed a binding strategy that: +- Eliminates code duplication for shared frontend logic +- Provides compile-time guarantees that both backends implement the same surface +- Does not require a premature monolithic rewrite +- Supports incremental migration of existing bridge adapters + +## Decision + +Adopt the **S-1 + S-3 hybrid strategy**: a shared `BackendAdapter` interface (S-1) combined with scoped, audit-gated extraction (S-3). + +### Core Mechanism + +A single `BackendAdapter` TypeScript interface defines every operation the frontend can request from the backend. Two concrete implementations (`TauriAdapter`, `ElectrobunAdapter`) are compile-time selected via path aliases. A singleton `getBackend()` function provides the active adapter. + +Frontend stores and components call `getBackend().someMethod()` instead of importing platform-specific bridge files. The `BackendCapabilities` flags allow UI to gracefully degrade features unavailable on a given backend. + +### Package Structure + +- **`@agor/types`** — Shared type definitions (agent, project, btmsg, bttask, health, settings, protocol, backend, ids). No runtime code. +- **`src/lib/backend/backend.ts`** — Singleton accessor (`getBackend()`, `setBackend()`, `setBackendForTesting()`). +- **`src/lib/backend/TauriAdapter.ts`** — Tauri 2.x implementation. +- **`src/lib/backend/ElectrobunAdapter.ts`** — Electrobun implementation. + +### Canonical SQL + +A single `schema/canonical.sql` (29 tables) is the source of truth for both backends. A `tools/validate-schema.ts` extracts DDL metadata for comparison. A `tools/migrate-db.ts` handles one-way Tauri-to-Electrobun data migration with version fencing. + +## Phase 1 Scope (DONE) + +Implemented 2026-03-22: + +- `@agor/types` package: 10 type files covering all cross-backend contracts +- `BackendAdapter` interface with 15 methods + 5 event subscriptions +- `BackendCapabilities` with 8 boolean flags +- `TauriAdapter` implementing all methods via `invoke()`/`listen()` +- `ElectrobunAdapter` implementing all methods via RPC +- `backend.ts` singleton with test helpers +- Canonical SQL DDL (29 tables, CHECK constraints, FK CASCADE, 13 indexes) +- Schema validator and migration tool +- `pnpm-workspace.yaml` workspace setup +- `docs/SWITCHING.md` migration guide + +## Phase 2 Scope (In Progress) + +Store audit and selective migration to `@agor/stores`: + +- Reactive dependency graph analysis across 13 stores and 23 bridges +- Categorization: CLEAN (4 stores) / BRIDGE-DEPENDENT (9 stores) / PLATFORM-SPECIFIC (0 stores) +- Settings domain migration: first cluster, replacing `settings-bridge.ts` with `getBackend()` +- 8 migration clusters identified, ordered by dependency depth + +### Phase 2 Trigger Checklist (Frozen) + +All items verified as implementable via BackendAdapter: + +- [x] PTY session create/attach/detach/destroy +- [x] Agent dispatch with status tracking +- [x] Settings read/write persistence +- [x] Search index query +- [x] Provider credential management +- [x] Notification dispatch +- [x] Workspace CRUD + +## Phase 3 Direction (Documented, Not Committed) + +**agor-daemon:** A standalone background process that both Tauri and Electrobun connect to, eliminating per-backend reimplementation entirely. This is documented as the long-term direction but explicitly NOT committed to. The hybrid adapter approach is sufficient for the current two-backend scope. + +**Turborepo threshold:** Adopt Turborepo when package count reaches 3. Currently at 2 (`@agor/types`, main app). The third package would likely be `@agor/stores` (migrated pure-state stores). + +## Consequences + +### Enables +- Compile-time guarantee that both backends implement the same API surface +- Incremental migration — bridge files can be replaced one at a time +- Capability-gated UI degradation (features disabled on backends that lack support) +- Testing with mock backends (`setBackendForTesting()`) +- Future backend additions (GPUI, Dioxus) only need a new adapter class +- Canonical SQL prevents schema drift between backends + +### Costs +- Two adapter implementations must be maintained in parallel +- BackendAdapter interface grows as new IPC commands are added +- Bridge files coexist with BackendAdapter during transition (dual access paths) +- One-way migration (Tauri to Electrobun) requires version fencing to prevent data loss + +## Risks + +Identified by tribunal (Claude + Codex consensus) and three Codex audits: + +### Semantic Drift +**Risk:** Shared types enforce shape consistency but not behavioral consistency. Two adapters may handle edge cases differently (null vs undefined, error message format, timing). +**Mitigation:** Integration tests per adapter. Type-narrowing at adapter boundary. + +### Svelte Rune Coupling +**Risk:** Stores using `$state`/`$derived` are tightly coupled to Svelte 5's rune execution model. Moving to `@agor/stores` package requires the consumer to also use Svelte 5. +**Mitigation:** Only move stores that are pure state (no `$derived` chains that reference DOM). Keep reactive-heavy stores in app layer. + +### SQLite Pragma Differences +**Risk:** Tauri uses rusqlite (bundled SQLite, WAL mode, 5s busy_timeout). Electrobun uses better-sqlite3 (system SQLite, different default pragmas). +**Mitigation:** Canonical SQL includes pragma requirements. Migration tool validates pragma state before copying data. + +### Build Toolchain Cache Invalidation +**Risk:** pnpm workspace changes can invalidate build caches unpredictably. `@agor/types` changes rebuild all consumers. +**Mitigation:** Types package is stable (changes are additive). Turborepo deferred until package count warrants it. + +### Version Fencing +**Risk:** One-way migration (Tauri to Electrobun) means Electrobun DB is a snapshot. User switching back to Tauri loses Electrobun-only changes. +**Mitigation:** `tools/migrate-db.ts` writes a version fence marker. Tauri startup checks for fence and warns if Electrobun has newer data. + +### Plugin Bridge Orphaning +**Risk:** Not all bridges map cleanly to BackendAdapter (btmsg, bttask, audit, plugins, anchors, remote). These "orphan bridges" may never migrate. +**Mitigation:** Documented as acceptable. BackendAdapter covers the critical path (settings, groups, agents, PTY, files). Domain-specific bridges can remain as adapters that internally use BackendAdapter or direct IPC. diff --git a/src/App.svelte b/src/App.svelte index 4ed840e..2d3c9e1 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,7 +1,7 @@