feat: @agor/stores package + Electrobun hardening (WIP)
- packages/stores/: theme, notifications, health stores extracted - Electrobun hardening: durable event sequencing, file conflict detection, push-based updates, backpressure guards (partial, agents still running)
This commit is contained in:
parent
5836fb7d80
commit
5e1fd62ed9
13 changed files with 855 additions and 665 deletions
151
packages/stores/theme.svelte.ts
Normal file
151
packages/stores/theme.svelte.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// Theme store — persists theme selection via settings bridge
|
||||
|
||||
import { getSetting, setSetting } from '../../src/lib/stores/settings-store.svelte';
|
||||
import { handleInfraError } from '../../src/lib/utils/handle-error';
|
||||
import {
|
||||
type ThemeId,
|
||||
type ThemePalette,
|
||||
type CatppuccinFlavor,
|
||||
ALL_THEME_IDS,
|
||||
buildXtermTheme,
|
||||
buildXtermThemeFromPalette,
|
||||
applyCssVariables,
|
||||
applyPaletteDirect,
|
||||
type XtermTheme,
|
||||
} from '../../src/lib/styles/themes';
|
||||
|
||||
let currentTheme = $state<ThemeId>('mocha');
|
||||
let customPalette = $state<ThemePalette | null>(null);
|
||||
|
||||
/** Registered theme-change listeners */
|
||||
const themeChangeCallbacks = new Set<() => void>();
|
||||
|
||||
/** Register a callback invoked after every theme change. Returns an unsubscribe function. */
|
||||
export function onThemeChange(callback: () => void): () => void {
|
||||
themeChangeCallbacks.add(callback);
|
||||
return () => {
|
||||
themeChangeCallbacks.delete(callback);
|
||||
};
|
||||
}
|
||||
|
||||
export function getCurrentTheme(): ThemeId {
|
||||
return currentTheme;
|
||||
}
|
||||
|
||||
/** @deprecated Use getCurrentTheme() */
|
||||
export function getCurrentFlavor(): CatppuccinFlavor {
|
||||
// Return valid CatppuccinFlavor or default to 'mocha'
|
||||
const catFlavors: string[] = ['latte', 'frappe', 'macchiato', 'mocha'];
|
||||
return catFlavors.includes(currentTheme) ? currentTheme as CatppuccinFlavor : 'mocha';
|
||||
}
|
||||
|
||||
export function getXtermTheme(): XtermTheme {
|
||||
if (customPalette) return buildXtermThemeFromPalette(customPalette);
|
||||
return buildXtermTheme(currentTheme);
|
||||
}
|
||||
|
||||
/** Apply an arbitrary palette for live preview (does NOT persist) */
|
||||
export function previewPalette(palette: ThemePalette): void {
|
||||
customPalette = palette;
|
||||
applyPaletteDirect(palette);
|
||||
for (const cb of themeChangeCallbacks) {
|
||||
try { cb(); } catch (e) { handleInfraError(e, 'theme.previewCallback'); }
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear custom palette preview, revert to current built-in theme */
|
||||
export function clearPreview(): void {
|
||||
customPalette = null;
|
||||
applyCssVariables(currentTheme);
|
||||
for (const cb of themeChangeCallbacks) {
|
||||
try { cb(); } catch (e) { handleInfraError(e, 'theme.clearPreviewCallback'); }
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a custom theme as active (persists the custom theme ID) */
|
||||
export async function setCustomTheme(id: string, palette: ThemePalette): Promise<void> {
|
||||
customPalette = palette;
|
||||
applyPaletteDirect(palette);
|
||||
for (const cb of themeChangeCallbacks) {
|
||||
try { cb(); } catch (e) { handleInfraError(e, 'theme.customCallback'); }
|
||||
}
|
||||
try {
|
||||
await setSetting('theme', id);
|
||||
} catch (e) {
|
||||
handleInfraError(e, 'theme.persistCustom');
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if current theme is a custom theme */
|
||||
export function isCustomThemeActive(): boolean {
|
||||
return customPalette !== null;
|
||||
}
|
||||
|
||||
/** Change theme, apply CSS variables, and persist to settings DB */
|
||||
export async function setTheme(theme: ThemeId): Promise<void> {
|
||||
currentTheme = theme;
|
||||
applyCssVariables(theme);
|
||||
// Notify all listeners (e.g. open xterm.js terminals)
|
||||
for (const cb of themeChangeCallbacks) {
|
||||
try {
|
||||
cb();
|
||||
} catch (e) {
|
||||
handleInfraError(e, 'theme.changeCallback');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await setSetting('theme', theme);
|
||||
} catch (e) {
|
||||
handleInfraError(e, 'theme.persistSetting');
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use setTheme() */
|
||||
export async function setFlavor(flavor: CatppuccinFlavor): Promise<void> {
|
||||
return setTheme(flavor);
|
||||
}
|
||||
|
||||
/** Load saved theme from settings DB and apply. Call once on app startup. */
|
||||
export async function initTheme(): Promise<void> {
|
||||
try {
|
||||
const saved = await getSetting('theme');
|
||||
if (saved) {
|
||||
if (saved.startsWith('custom:')) {
|
||||
// Custom theme — load palette from custom_themes storage
|
||||
const { loadCustomThemes } = await import('../../src/lib/styles/custom-themes');
|
||||
const customs = await loadCustomThemes();
|
||||
const match = customs.find(c => c.id === saved);
|
||||
if (match) {
|
||||
customPalette = match.palette;
|
||||
applyPaletteDirect(match.palette);
|
||||
}
|
||||
} else if (ALL_THEME_IDS.includes(saved as ThemeId)) {
|
||||
currentTheme = saved as ThemeId;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fall back to default (mocha) — catppuccin.css provides Mocha defaults
|
||||
}
|
||||
// Always apply to sync CSS vars with current theme (skip if custom already applied)
|
||||
if (!customPalette && currentTheme !== 'mocha') {
|
||||
applyCssVariables(currentTheme);
|
||||
}
|
||||
|
||||
// Apply saved font settings
|
||||
try {
|
||||
const [uiFont, uiSize, termFont, termSize] = await Promise.all([
|
||||
getSetting('ui_font_family'),
|
||||
getSetting('ui_font_size'),
|
||||
getSetting('term_font_family'),
|
||||
getSetting('term_font_size'),
|
||||
]);
|
||||
const root = document.documentElement.style;
|
||||
if (uiFont) root.setProperty('--ui-font-family', `'${uiFont}', sans-serif`);
|
||||
if (uiSize) root.setProperty('--ui-font-size', `${uiSize}px`);
|
||||
if (termFont) root.setProperty('--term-font-family', `'${termFont}', monospace`);
|
||||
if (termSize) root.setProperty('--term-font-size', `${termSize}px`);
|
||||
} catch {
|
||||
// Font settings are optional — defaults from catppuccin.css apply
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue