agent-orchestrator/ui-electrobun/src/mainview/theme-store.svelte.ts
Hibryda 6002a379e4 feat(electrobun): wire persistence — SQLite, 17 themes, font system
Persistence:
- bun:sqlite at ~/.config/agor/settings.db (WAL mode, 500ms busy_timeout)
- 4 tables: schema_version, settings, projects, custom_themes
- 5 RPC handlers: settings.get/set/getAll, projects get/set

Theme system (LIVE switching):
- All 17 themes ported from Tauri (4 Catppuccin + 7 Editor + 6 Deep Dark)
- applyCssVars() sets 26 --ctp-* vars on document.documentElement
- Parallel xterm ITheme mapping per theme
- theme-store.svelte.ts: Svelte 5 rune store, persists to SQLite

Font system:
- font-store.svelte.ts: UI/terminal font family + size
- Live CSS var application (--ui-font-family/size, --term-font-family/size)
- onTermFontChange() callback registry for terminal instances
- Persists all 4 font settings to SQLite

AppearanceSettings wired: 17-theme grouped dropdown, font steppers
Init on startup: restores saved theme + fonts from SQLite
2026-03-20 05:29:03 +01:00

86 lines
2.9 KiB
TypeScript

/**
* Svelte 5 rune-based theme store.
* Applies all 26 --ctp-* CSS vars to document.documentElement instantly.
* Persists selection via settings RPC.
*
* Usage:
* import { themeStore } from './theme-store.svelte';
* themeStore.setTheme('tokyo-night');
* await themeStore.initTheme(rpc);
*/
import { applyCssVars, THEME_LIST, type ThemeId } from "./themes.ts";
const SETTING_KEY = "theme";
const DEFAULT_THEME: ThemeId = "mocha";
// ── Minimal RPC interface ─────────────────────────────────────────────────────
// Avoids importing the full Electroview class — just the subset we need.
interface SettingsRpc {
request: {
"settings.get"(p: { key: string }): Promise<{ value: string | null }>;
"settings.set"(p: { key: string; value: string }): Promise<{ ok: boolean }>;
};
}
// ── Helpers ───────────────────────────────────────────────────────────────────
function isValidThemeId(id: string): id is ThemeId {
return THEME_LIST.some((t) => t.id === id);
}
// ── Store ─────────────────────────────────────────────────────────────────────
function createThemeStore() {
let currentThemeId = $state<ThemeId>(DEFAULT_THEME);
let rpc: SettingsRpc | null = null;
/** Apply CSS vars immediately — synchronous, no flash. */
function applyToDocument(id: ThemeId): void {
applyCssVars(id);
currentThemeId = id;
}
/**
* Change the active theme.
* Applies CSS vars immediately and persists asynchronously.
*/
function setTheme(id: ThemeId): void {
applyToDocument(id);
if (rpc) {
rpc.request["settings.set"]({ key: SETTING_KEY, value: id }).catch((err) => {
console.error("[theme-store] Failed to persist theme:", err);
});
}
}
/**
* Load persisted theme from settings on startup.
* Call once in App.svelte onMount.
*/
async function initTheme(rpcInstance: SettingsRpc): Promise<void> {
rpc = rpcInstance;
try {
const result = await rpc.request["settings.get"]({ key: SETTING_KEY });
const saved = result.value;
if (saved && isValidThemeId(saved)) {
applyToDocument(saved);
} else {
// Apply default to ensure vars are set even when nothing was persisted.
applyToDocument(DEFAULT_THEME);
}
} catch (err) {
console.error("[theme-store] Failed to load theme from settings:", err);
applyToDocument(DEFAULT_THEME);
}
}
return {
get currentTheme() { return currentThemeId; },
setTheme,
initTheme,
};
}
export const themeStore = createThemeStore();