/** * Module-level houseplan config cache shared by every embedded card on a board, * so N cards do NOT issue N identical `houseplan/config/get` requests. The cache * is invalidated when the integration emits `houseplan_config_updated` * (fired on config/set); subscribers are then notified to reload. * * The full `houseplan-card` already persists a `{config, rev, layout}` snapshot in * localStorage (`houseplan_card_cfg_v1`) for its own instant start — we seed from it * so embedded cards paint immediately, then refresh from the server in the background. */ const LS_CFG = 'houseplan_card_cfg_v1'; export interface HpConfigSnapshot { config: any | null; rev: number; layout: Record; } let cache: HpConfigSnapshot | null = null; let inflight: Promise | null = null; let subscribed = false; const listeners = new Set<() => void>(); /** Instant, synchronous best-effort snapshot from the full card's localStorage cache. */ export function cachedSnapshot(): HpConfigSnapshot | null { if (cache) return cache; try { const c = JSON.parse(localStorage.getItem(LS_CFG) || 'null'); if (c && c.config && Array.isArray(c.config.spaces)) { return { config: c.config, rev: c.rev || 0, layout: c.layout || {} }; } } catch { /* ignore */ } return null; } async function fetchFresh(hass: any): Promise { const [cfgResp, layResp] = await Promise.all([ hass.callWS({ type: 'houseplan/config/get' }), hass.callWS({ type: 'houseplan/layout/get' }), ]); cache = { config: cfgResp?.config ?? null, rev: cfgResp?.rev ?? 0, layout: layResp?.layout ?? {}, }; if (!subscribed && hass.connection?.subscribeEvents) { subscribed = true; const invalidate = () => { cache = null; // invalidate; listeners reload listeners.forEach((l) => l()); }; try { await hass.connection.subscribeEvents(invalidate, 'houseplan_config_updated'); // Layout is separate state: dragging an icon on the full card writes only // the layout, so a static card on the same dashboard kept showing the old // position until the config changed or the page was reloaded — possibly // forever on a wall tablet (HP-1454-08). await hass.connection.subscribeEvents(invalidate, 'houseplan_layout_updated'); } catch { subscribed = false; } } return cache; } /** Get the shared config snapshot (cached, deduped across cards). */ export function getConfig(hass: any): Promise { if (cache) return Promise.resolve(cache); if (inflight) return inflight; inflight = fetchFresh(hass).finally(() => { inflight = null; }); return inflight; } /** Subscribe to config-changed notifications; returns an unsubscribe function. */ export function onConfigChange(cb: () => void): () => void { listeners.add(cb); return () => listeners.delete(cb); }