// In-memory lobby snapshot, the lobby counterpart of gamecache.ts. The lobby re-fetches // its lists on every entry, so without a cache the screen renders blank and "draws in" // during the back-slide from a game. Caching the last lists lets the lobby render // instantly (before/under the transition) and refresh in the background. Process-memory // only; cleared on logout. import type { AccountRef, GameView, Invitation } from './model'; interface LobbySnapshot { games: GameView[]; invitations: Invitation[]; incoming: AccountRef[]; } let snapshot: LobbySnapshot | null = null; /** getLobby returns the last lobby lists, or null before the first load. The unified lobby caches one * snapshot (device-local + server games merged); offline reuses its server games, greyed, and drops * the local ones for a fresh device read. */ export function getLobby(): LobbySnapshot | null { return snapshot; } /** setLobby stores the latest lobby lists. */ export function setLobby(s: LobbySnapshot): void { snapshot = s; } /** * patchLobbyGame upserts the given game into the cached lobby — replacing the matching entry, or * prepending it when absent — so a game-state change seen while the lobby is unmounted (the player's * own move, or any live game event the global stream handler applies from another screen) is already * reflected the next time the lobby renders from the snapshot, instead of showing the stale status * until the background refresh lands. Prepending a freshly started game likewise lets it appear at * once. It is a no-op only when there is no snapshot yet (the lobby cold-loads on its first mount). * The lobby re-groups and re-sorts on render, so the insert position carries no meaning. */ export function patchLobbyGame(g: GameView): void { if (!snapshot) return; const i = snapshot.games.findIndex((x) => x.id === g.id); const games = snapshot.games.slice(); if (i === -1) games.unshift(g); else games[i] = g; snapshot = { ...snapshot, games }; } /** * patchLobbyInvitation applies a live invitation change to the cached lobby's invitations list, so a * change seen while the lobby is unmounted is reflected on its next render instead of a stale card * lingering until the background refresh. A still-pending invitation is upserted (a new one * prepended newest-first, an updated one replaced in place); an invitation that reached any terminal * status (started, declined, cancelled, expired) is removed — the authoritative list holds only * pending invitations. It is a no-op when there is no snapshot yet, or a terminal invitation is not * in the list. The lobby re-renders from the snapshot, so the in-place upsert needs no re-sort. */ export function patchLobbyInvitation(inv: Invitation): void { if (!snapshot) return; const i = snapshot.invitations.findIndex((x) => x.id === inv.id); let invitations: Invitation[]; if (inv.status !== 'pending') { if (i === -1) return; // a terminal invitation already absent — nothing to remove invitations = snapshot.invitations.filter((x) => x.id !== inv.id); } else if (i === -1) { invitations = [inv, ...snapshot.invitations]; } else { invitations = snapshot.invitations.slice(); invitations[i] = inv; } snapshot = { ...snapshot, invitations }; } /** clearLobby drops the cached lobby (called on logout). */ export function clearLobby(): void { snapshot = null; }