import { useSyncExternalStore } from "react"; // The account a browser TAB is acting as (per-tab, in sessionStorage — see api.ts), plus its reactive // wrapper. Its OWN module (imports nothing internal) so it can be shared by both `storage.ts` (which // keys per-account localStorage on it) and `useAccountRehydrate.ts` without a circular import — the // account id is a lower-level primitive than the storage helpers that consume it. // // ACTIVE_ACCOUNT_KEY is duplicated here rather than imported from api.ts so this stays dependency-free. export const ACTIVE_ACCOUNT_KEY = "siftlode.activeAccount"; export function activeAccountId(): number | null { try { const raw = sessionStorage.getItem(ACTIVE_ACCOUNT_KEY); if (!raw) return null; const n = Number(raw); return Number.isFinite(n) ? n : null; } catch { return null; } } // Append `?account=` to a top-level navigation URL (SEC-11). XHRs carry the active account in // the X-Siftlode-Account header, but a full-page navigation (window.location) can't, so the OAuth // link/upgrade GETs would resolve to the SESSION DEFAULT account instead of the tab's active one — the // identity/token would then land on the wrong account. This threads the active account through the URL // so resolved_user_id picks it. No-op when there's no distinct active account pinned yet. export function withActiveAccount(path: string): string { const id = activeAccountId(); if (id == null) return path; return `${path}${path.includes("?") ? "&" : "?"}account=${id}`; } // Reactive active-account. setActiveAccount/clearActiveAccount write sessionStorage — a silent op that // triggers no React render — so this tab's per-account state (sort/filter/view/layout, all keyed by the // account) would never re-read once the account is finally known. That bites exactly once, on first // load: the provider tree above mounts with NO account, then pins it after /api/me resolves. // The account setters call notifyActiveAccountChange() so useActiveAccountId subscribers can re-hydrate. const accountListeners = new Set<() => void>(); function subscribeActiveAccount(cb: () => void): () => void { accountListeners.add(cb); return () => accountListeners.delete(cb); } export function notifyActiveAccountChange(): void { for (const cb of [...accountListeners]) cb(); } /** The current tab's active account id, as reactive state — re-renders when it is (re)pinned. The * snapshot is a primitive (number | null), so useSyncExternalStore's Object.is check is stable. */ export function useActiveAccountId(): number | null { return useSyncExternalStore(subscribeActiveAccount, activeAccountId, activeAccountId); }