const STORAGE_KEY = 'nitro.housekeeping.recent'; const MAX_ENTRIES = 8; export interface RecentLookupEntry { kind: 'user' | 'room'; id: number; label: string; at: number; } const isEntry = (value: unknown): value is RecentLookupEntry => { if(!value || typeof value !== 'object') return false; const obj = value as Record; return ( (obj.kind === 'user' || obj.kind === 'room') && Number.isFinite(obj.id) && typeof obj.label === 'string' && Number.isFinite(obj.at) ); }; const readStore = (): RecentLookupEntry[] => { try { const raw = window.localStorage.getItem(STORAGE_KEY); if(!raw) return []; const parsed = JSON.parse(raw); if(!Array.isArray(parsed)) return []; return parsed.filter(isEntry); } catch { return []; } }; const writeStore = (entries: RecentLookupEntry[]): void => { try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(entries)); } catch {} }; export const loadRecentLookups = (): RecentLookupEntry[] => readStore(); /** * Push an entry to the front of the recent-lookups stack. Existing * entries with the same kind+id are deduped (so reopening the same * user doesn't bury fresher entries), and the list is trimmed to * MAX_ENTRIES. Pure for the in-memory transform — the persistence is * a side effect on top. */ export const pushRecentLookup = (current: RecentLookupEntry[], entry: RecentLookupEntry): RecentLookupEntry[] => { const filtered = current.filter(item => !(item.kind === entry.kind && item.id === entry.id)); const next = [ entry, ...filtered ].slice(0, MAX_ENTRIES); return next; }; export const persistRecentLookups = (entries: RecentLookupEntry[]): void => writeStore(entries); export const clearRecentLookups = (): void => writeStore([]); export const RECENT_LOOKUPS_LIMIT = MAX_ENTRIES;