/** * Whether the desktop sidebar is expanded or collapsed (ADR 0058). * * A preference of **this browser on this device**, not of the person: a narrow laptop and a wide * office monitor reasonably want different answers, and DESIGN.md's synced personal preferences * (appearance, motion) belong to the account area, which does not exist yet. The shell reads and * writes it only through this interface, so a later store — synced, or per person — replaces the * implementation without touching a component. */ export type SidebarMode = "expanded" | "collapsed"; export interface SidebarPreference { /** The remembered mode, or `undefined` when nothing usable was ever stored. */ read(): SidebarMode | undefined; write(mode: SidebarMode): void; } /** One namespaced key. Names no person and holds nothing but the mode. */ export const SIDEBAR_PREFERENCE_KEY = "ekklesia.dashboard.sidebar"; function isSidebarMode(value: unknown): value is SidebarMode { return value === "expanded" || value === "collapsed"; } /** * The browser's `localStorage`, handed in by the entry point (the only file that touches `window`). * * Storage can be missing, blocked, full, or throw on every access (a private window, a sandboxed * frame, site data cleared or disabled), and none of that may break the Dashboard. Every access is * guarded, and the last mode chosen on this page is still remembered in memory, so the toggle keeps * working and only persistence is lost. A stored value that is not exactly one of the two modes is * ignored rather than trusted. */ export function createLocalSidebarPreference( storage: Pick | undefined, ): SidebarPreference { let remembered: SidebarMode | undefined; return { read() { if (remembered !== undefined) { return remembered; } try { const stored = storage?.getItem(SIDEBAR_PREFERENCE_KEY); return isSidebarMode(stored) ? stored : undefined; } catch { return undefined; } }, write(mode) { remembered = mode; try { storage?.setItem(SIDEBAR_PREFERENCE_KEY, mode); } catch { // Persistence is a convenience; the in-memory mode above still holds for this page. } }, }; } /** Nothing persisted: for tests, and for a host composed without browser storage. */ export function createMemorySidebarPreference(initial?: SidebarMode): SidebarPreference { let mode = initial; return { read: () => mode, write(next) { mode = next; }, }; }