/** * `localStorage`-backed {@link UiPreferencesGateway} (ticket #29). * * The only place that touches `window.localStorage`; features go through the * port via DI. Everything is best-effort: a missing / private-mode / quota-full * storage, or a corrupt JSON entry, degrades silently to "no preference" rather * than throwing into the UI (persisted UI filters must never break the app). */ import type { UiPreferencesGateway } from "@/ports"; /** Returns the `Storage` if usable in this environment, else `null`. */ function storage(): Storage | null { try { return typeof window !== "undefined" ? window.localStorage : null; } catch { // Accessing `localStorage` can throw (sandboxed / disabled cookies). return null; } } export class LocalStorageUiPreferencesGateway implements UiPreferencesGateway { read(key: string): unknown { const store = storage(); if (!store) return null; let raw: string | null; try { raw = store.getItem(key); } catch { return null; } if (raw === null) return null; try { return JSON.parse(raw); } catch { // Corrupt entry — drop it so we stop tripping over it, return default. try { store.removeItem(key); } catch { /* ignore */ } return null; } } write(key: string, value: unknown): void { const store = storage(); if (!store) return; try { store.setItem(key, JSON.stringify(value)); } catch { /* quota / serialisation failure — persistence is best-effort */ } } remove(key: string): void { const store = storage(); if (!store) return; try { store.removeItem(key); } catch { /* ignore */ } } }