// Cosmetic user-controllable preferences for the operator shell — theme // (dark/light) and density (comfy/compact). Persisted in localStorage so // the choices survive page reloads. The values are applied to as // data-* attributes; tokens.css picks them up via selector overrides. // // NOTE: `env` used to live here but is now derived from the hostname via // `useEnv()` so it's a real environment signal, not a sticker the operator // can flip. export type ThemeMode = 'dark' | 'light' export type Density = 'comfy' | 'compact' interface TweakState { theme: ThemeMode density: Density } const STORAGE_KEY = 'dezky-operator-tweaks' const DEFAULTS: TweakState = { theme: 'dark', density: 'comfy' } const state = ref({ ...DEFAULTS }) const hydrated = ref(false) function apply() { if (!import.meta.client) return const root = document.documentElement root.setAttribute('data-theme', state.value.theme) root.setAttribute('data-density', state.value.density) } function persist() { if (!import.meta.client) return try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state.value)) } catch { // localStorage can throw in private mode; tweaks are cosmetic so swallow. } } function hydrate() { if (!import.meta.client || hydrated.value) return try { const raw = localStorage.getItem(STORAGE_KEY) if (raw) { // Old payloads carried an `env` field — pick fields we still care about // and ignore the rest so a stale localStorage entry doesn't break. const parsed = JSON.parse(raw) as Partial state.value = { theme: parsed.theme ?? DEFAULTS.theme, density: parsed.density ?? DEFAULTS.density, } } } catch { // ignore corrupt JSON } apply() hydrated.value = true } export const useTweaks = () => { if (import.meta.client) hydrate() function set(key: K, value: TweakState[K]) { state.value = { ...state.value, [key]: value } apply() persist() } return { state, setTheme: (v: ThemeMode) => set('theme', v), setDensity: (v: Density) => set('density', v), } }