Files
llm-multiverse-ui/src/lib/stores/theme.svelte.ts
shahondin1624 38f5f31b92 refactor: code review improvements — fix bugs, extract shared utilities, add README
- Fix reactivity bug: use SvelteMap instead of Map in sessions store
- Fix theme listener memory leak: guard against double-init, return cleanup function
- Fix transport singleton ignoring different endpoints
- Fix form/button type mismatch in MessageInput
- Add safer retry validation in chat page
- Extract shared utilities: date formatting, session config check, result source config
- Extract shared components: Backdrop, PageHeader
- Extract orchestration composable from chat page (310→85 lines of script)
- Consolidate AuditTimeline switch functions into config record
- Move sample data to dedicated module
- Add dark mode support for LineageTree SVG colors
- Memoize leaf count computation in LineageTree
- Update README with usage guide and project structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:48:06 +01:00

100 lines
2.5 KiB
TypeScript

export type ThemeMode = 'light' | 'dark' | 'system';
const STORAGE_KEY = 'llm-multiverse-theme';
function createThemeStore() {
let mode: ThemeMode = $state('system');
let resolvedDark = $state(false);
let initialized = false;
let mediaQuery: MediaQueryList | null = null;
let mediaListener: ((e: MediaQueryListEvent) => void) | null = null;
function applyTheme(isDark: boolean) {
if (typeof document === 'undefined') return;
const root = document.documentElement;
// Add transition class for smooth switching
root.classList.add('theme-transition');
if (isDark) {
root.classList.add('dark');
} else {
root.classList.remove('dark');
}
// Remove transition class after animation completes
setTimeout(() => {
root.classList.remove('theme-transition');
}, 300);
}
function getSystemPreference(): boolean {
if (typeof window === 'undefined') return false;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function init(): (() => void) | undefined {
if (typeof window === 'undefined') return;
if (initialized) return;
initialized = true;
// Load saved preference
const saved = localStorage.getItem(STORAGE_KEY) as ThemeMode | null;
if (saved === 'light' || saved === 'dark' || saved === 'system') {
mode = saved;
} else {
mode = 'system';
}
// Listen for system preference changes
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaListener = (e: MediaQueryListEvent) => {
if (mode === 'system') {
resolvedDark = e.matches;
applyTheme(resolvedDark);
}
};
mediaQuery.addEventListener('change', mediaListener);
// Apply initial theme
resolvedDark =
mode === 'dark' ? true : mode === 'light' ? false : getSystemPreference();
applyTheme(resolvedDark);
return () => {
if (mediaQuery && mediaListener) {
mediaQuery.removeEventListener('change', mediaListener);
}
initialized = false;
};
}
function setMode(newMode: ThemeMode) {
mode = newMode;
if (typeof window !== 'undefined') {
localStorage.setItem(STORAGE_KEY, newMode);
}
resolvedDark =
newMode === 'dark' ? true : newMode === 'light' ? false : getSystemPreference();
applyTheme(resolvedDark);
}
function cycle() {
const order: ThemeMode[] = ['system', 'light', 'dark'];
const idx = order.indexOf(mode);
const next = order[(idx + 1) % order.length];
setMode(next);
}
return {
get mode() {
return mode;
},
get isDark() {
return resolvedDark;
},
init,
setMode,
cycle
};
}
export const themeStore = createThemeStore();