/** * GoingsOn - App config cache. * * Settings used to live in localStorage. Step 6 of the portable-config migration * moves the known config keys into the backend `user_config` table (synced by * posture), while keeping a synchronous read API so the rest of the frontend does * not have to become async. * * `GoingsOn.config` is a superset drop-in for the old localStorage calls: * - a KNOWN config key (by its clean store name or its legacy localStorage * name) is read from an in-memory cache and written through to the backend; * - any other key passes through to localStorage unchanged. * * The cache is filled once at startup from `get_all_config`; consumers that read * during app init must `await GoingsOn.config.ready` first. The one paint-critical * key, `ui_mode`, is mirrored back to localStorage on every write so the * pre-stylesheet `bootstrap-uimode.js` can still read it synchronously before the * first paint. */ (function () { 'use strict'; const invoke = window.__TAURI__.core.invoke; // Legacy localStorage key -> clean store key. Callers may pass either. const LEGACY = { 'goingson-theme': 'theme', 'goingson.uiMode': 'ui_mode', 'go-welcomed': 'welcomed', 'go-hint-shortcuts': 'hint_shortcuts', 'goingson-event-lead-minutes': 'event_lead_minutes', 'goingson-plan-nudges': 'plan_nudges', 'goingson-review-nudges': 'review_nudges', 'goingson-work-start-hour': 'work_start_hour', 'goingson-work-end-hour': 'work_end_hour', }; // Store key -> its localStorage mirror key, for paint-critical keys the // pre-paint bootstrap reads synchronously. const MIRROR = { ui_mode: 'goingson.uiMode' }; const STORE_KEYS = new Set(Object.values(LEGACY)); const cache = new Map(); let resolveReady; const ready = new Promise((r) => { resolveReady = r; }); /** The clean store key for a caller key (store name or legacy name), or null * when the key is not a known config key. */ function norm(key) { if (STORE_KEYS.has(key)) return key; return LEGACY[key] || null; } function readLocal(key) { try { return localStorage.getItem(key); } catch (e) { return null; } } function writeLocal(key, value) { try { localStorage.setItem(key, value); } catch (e) { /* storage disabled */ } } function removeLocal(key) { try { localStorage.removeItem(key); } catch (e) { /* storage disabled */ } } async function load() { try { const all = await invoke('get_all_config'); for (const k of Object.keys(all)) cache.set(k, all[k]); // One-time migration: for any known key the backend does not yet have, // copy the old localStorage value up. Idempotent (skipped once the // backend has the key); localStorage is left intact as the ui_mode // mirror and a harmless fallback. for (const legacyKey of Object.keys(LEGACY)) { const storeKey = LEGACY[legacyKey]; if (cache.has(storeKey)) continue; const v = readLocal(legacyKey); if (v === null) continue; cache.set(storeKey, v); try { await invoke('set_config', { key: storeKey, value: v }); } catch (e) { console.error('config migrate failed for', storeKey, e); } } } catch (e) { console.error('Failed to load config:', e); } finally { resolveReady(); } } GoingsOn.config = { /** Resolves once the cache is populated. Await before reading at init. */ ready, /** Synchronous read. Known keys come from the cache (falling back to the * localStorage mirror, then `fallback`); unknown keys pass through to * localStorage. Returns a string or `fallback` (default null). */ get(key, fallback = null) { const storeKey = norm(key); if (storeKey) { if (cache.has(storeKey)) return cache.get(storeKey); const mirrored = MIRROR[storeKey] ? readLocal(MIRROR[storeKey]) : null; return mirrored !== null ? mirrored : fallback; } const v = readLocal(key); return v !== null ? v : fallback; }, /** Write. Known keys go to the cache + backend (+ localStorage mirror for * paint-critical keys); unknown keys pass through to localStorage. */ set(key, value) { value = String(value); const storeKey = norm(key); if (storeKey) { cache.set(storeKey, value); if (MIRROR[storeKey]) writeLocal(MIRROR[storeKey], value); invoke('set_config', { key: storeKey, value }).catch((e) => console.error('set_config failed for', storeKey, e)); return; } writeLocal(key, value); }, /** Remove a key. */ remove(key) { const storeKey = norm(key); if (storeKey) { cache.delete(storeKey); if (MIRROR[storeKey]) removeLocal(MIRROR[storeKey]); invoke('delete_config', { key: storeKey }).catch((e) => console.error('delete_config failed for', storeKey, e)); return; } removeLocal(key); }, }; load(); })();