feat: Phase 2 — store audit, migration clusters, ADR, settings domain migration
- 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
This commit is contained in:
parent
df83b1df4d
commit
579157f6da
18 changed files with 657 additions and 26 deletions
215
MIGRATION_CLUSTERS.md
Normal file
215
MIGRATION_CLUSTERS.md
Normal file
|
|
@ -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<AgentSession[]>` (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<GroupsFile | null>`, `$state<string>` (activeGroupId), `$state<WorkspaceTab>`, `$state<string | null>` (activeProjectId), `$state<Record<string, TerminalTab[]>>`, `$state<string | null>` (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<Map>` (trackers, stallThresholds), `$state<number>` (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<Map>` (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<Map>` (projectAnchors), `$state<Set>` (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<ThemeId>`, `$state<ThemePalette | null>`
|
||||
- **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<PluginCommand[]>`, `$state<PluginEntry[]>`
|
||||
- **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<Machine[]>`
|
||||
- **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<Map>` (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<Toast[]>`, `$state<HistoryNotification[]>`
|
||||
- **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<Pane[]>`, `$state<LayoutPreset>`, `$state<string | null>` (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<string | null>` (activeProjectId), `$state<Map>` (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<Session[]>`
|
||||
- **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 |
|
||||
267
PHASE1_STORE_AUDIT.md
Normal file
267
PHASE1_STORE_AUDIT.md
Normal file
|
|
@ -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
|
||||
131
docs/architecture/adr-001-dual-stack-binding.md
Normal file
131
docs/architecture/adr-001-dual-stack-binding.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { initTheme } from './lib/stores/theme.svelte';
|
||||
import { getSetting } from './lib/adapters/settings-bridge';
|
||||
import { getSetting } from './lib/stores/settings-store.svelte';
|
||||
import { isDetachedMode, getDetachedConfig } from './lib/utils/detach';
|
||||
import { startAgentDispatcher, stopAgentDispatcher } from './lib/agent-dispatcher';
|
||||
import { startHealthTick, stopHealthTick, clearHealthTracking } from './lib/stores/health.svelte';
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export async function getSetting(key: string): Promise<string | null> {
|
||||
return invoke('settings_get', { key });
|
||||
}
|
||||
|
||||
export async function setSetting(key: string, value: string): Promise<void> {
|
||||
return invoke('settings_set', { key, value });
|
||||
}
|
||||
|
||||
export async function listSettings(): Promise<[string, string][]> {
|
||||
return invoke('settings_list');
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { listDirectoryChildren, readFileContent, writeFileContent, type DirEntry, type FileContent } from '../../adapters/files-bridge';
|
||||
import { getSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting } from '../../stores/settings-store.svelte';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import CodeEditor from './CodeEditor.svelte';
|
||||
import PdfViewer from './PdfViewer.svelte';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
import { deriveIdentifier, type GroupAgentRole, AGENT_ROLE_ICONS } from '../../types/groups';
|
||||
import { ProjectId, GroupId } from '../../types/ids';
|
||||
import { generateAgentPrompt } from '../../utils/agent-prompts';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../../stores/settings-store.svelte';
|
||||
import { getCurrentTheme, setTheme } from '../../stores/theme.svelte';
|
||||
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
|
||||
import { listProfiles, type ClaudeProfile } from '../../adapters/claude-bridge';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../../stores/settings-store.svelte';
|
||||
import { getPluginEntries, setPluginEnabled, reloadAllPlugins } from '../../stores/plugins.svelte';
|
||||
import { checkForUpdates, getCurrentVersion, getLastCheckTimestamp } from '../../utils/updater';
|
||||
import type { UpdateInfo } from '../../utils/updater';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../../stores/settings-store.svelte';
|
||||
import { getProviders } from '../../providers/registry.svelte';
|
||||
import type { ProviderSettings } from '../../providers/types';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { getCurrentTheme, setTheme, setCustomTheme } from '../../stores/theme.svelte';
|
||||
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../../stores/settings-store.svelte';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
import { type CustomTheme, loadCustomThemes, deleteCustomTheme as deleteCustom, saveCustomThemes } from '../../styles/custom-themes';
|
||||
import ThemeEditor from '../ThemeEditor.svelte';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../../stores/settings-store.svelte';
|
||||
import { ANCHOR_BUDGET_SCALES, ANCHOR_BUDGET_SCALE_LABELS, type AnchorBudgetScale } from '../../types/anchors';
|
||||
import { WAKE_STRATEGIES, WAKE_STRATEGY_LABELS, WAKE_STRATEGY_DESCRIPTIONS, type WakeStrategy } from '../../types/wake';
|
||||
import { handleError, handleInfraError } from '../../utils/handle-error';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
addProject, removeProject, addGroup, removeGroup, switchGroup,
|
||||
updateProject,
|
||||
} from '../../stores/workspace.svelte';
|
||||
import { getSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting } from '../../stores/settings-store.svelte';
|
||||
import { setActiveProject } from '../../stores/settings-scope.svelte';
|
||||
|
||||
let newGroupName = $state('');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../../stores/settings-store.svelte';
|
||||
import { storeSecret, getSecret, deleteSecret, listSecrets, hasKeyring, knownSecretKeys, SECRET_KEY_LABELS } from '../../adapters/secrets-bridge';
|
||||
import { handleError, handleInfraError } from '../../utils/handle-error';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import type { PluginMeta } from '../adapters/plugins-bridge';
|
||||
import { discoverPlugins } from '../adapters/plugins-bridge';
|
||||
import { getSetting, setSetting } from '../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from './settings-store.svelte';
|
||||
import { loadPlugin, unloadPlugin, unloadAllPlugins, getLoadedPlugins } from '../plugins/plugin-host';
|
||||
import { handleInfraError } from '../utils/handle-error';
|
||||
import type { GroupId, AgentId } from '../types/ids';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Provides scoped get/set: reads project-level override if exists, falls back to global.
|
||||
// Single source of truth for which scope is active and how overrides cascade.
|
||||
|
||||
import { getSetting, setSetting } from '../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from './settings-store.svelte';
|
||||
|
||||
type Scope = 'global' | 'project';
|
||||
|
||||
|
|
|
|||
31
src/lib/stores/settings-store.svelte.ts
Normal file
31
src/lib/stores/settings-store.svelte.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Settings store — thin accessor over BackendAdapter for settings persistence.
|
||||
// Replaces the old settings-bridge.ts (which imported Tauri invoke directly).
|
||||
// All consumers should import from here instead of settings-bridge.
|
||||
|
||||
import { getBackend } from '../backend/backend';
|
||||
|
||||
/**
|
||||
* Get a setting value by key. Returns null if not found.
|
||||
*/
|
||||
export async function getSetting(key: string): Promise<string | null> {
|
||||
return getBackend().getSetting(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value by key.
|
||||
*/
|
||||
export async function setSetting(key: string, value: string): Promise<void> {
|
||||
return getBackend().setSetting(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings as a key-value map.
|
||||
* Note: returns Record<string, string> (SettingsMap), not [string, string][].
|
||||
* Callers that used listSettings() should adapt to the map format.
|
||||
*/
|
||||
export async function getAllSettings(): Promise<Record<string, string>> {
|
||||
return getBackend().getAllSettings();
|
||||
}
|
||||
|
||||
// Re-export for backward compat with code that imported listSettings
|
||||
export { getAllSettings as listSettings };
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Theme store — persists theme selection via settings bridge
|
||||
|
||||
import { getSetting, setSetting } from '../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from './settings-store.svelte';
|
||||
import { handleInfraError } from '../utils/handle-error';
|
||||
import {
|
||||
type ThemeId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Custom theme persistence — store, load, validate, import/export
|
||||
|
||||
import { getSetting, setSetting } from '../adapters/settings-bridge';
|
||||
import { getSetting, setSetting } from '../stores/settings-store.svelte';
|
||||
import { handleError, handleInfraError } from '../utils/handle-error';
|
||||
import { type ThemePalette, type ThemeId, getPalette, PALETTE_KEYS } from './themes';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue