/** * Warm the Home snapshot from a secondary page (BUG-1104). * * BUG-1040's warm return only works after Home has cold-started once in this * document. A tab that opens on /chart, /ephemeris, /reports or /people (a * refresh, a shared link, the post-login landing of a deep link) has no * snapshot, so its first 「新建对话」 plays the whole loading ring. * * While such a page sits idle, fetch exactly what `isCompleteHomeWarmSnapshot` * requires — the model catalog and the rectification entry summary — with the * same client helpers the cold start uses, and write it through the same * `writeHomeWarmSnapshot`. Read-only GETs only: no fallback PATCH, no * consultation recovery. Those still run in Home's warm refresh after landing. * The cursor is left to the provider's own boot (`listBoot: null`). * * Pure module state, no React. */ import { fetchModelCatalog, fetchRectificationEntrySummary } from "./home-cloud-sync"; import { isCompleteHomeWarmSnapshot, readHomeWarmSnapshot, writeHomeWarmSnapshot } from "./home-warm-snapshot"; import type { PublicLanguageModelCatalog } from "./public-models"; import type { RectificationEntrySummary } from "./rectification-entry"; export type HomeWarmPrefetchIo = Readonly<{ fetchModelCatalog: (signal?: AbortSignal) => Promise; fetchRectificationEntrySummary: (signal?: AbortSignal) => Promise; }>; const defaultIo: HomeWarmPrefetchIo = { fetchModelCatalog, fetchRectificationEntrySummary }; let inflight: { accountId: string; promise: Promise } | null = null; /** * Fill the snapshot for `accountId` unless it is already complete. Resolves to * true when this call wrote a complete snapshot. Any failure leaves nothing * behind: the next Home simply takes the unchanged cold path. * `isCurrent` is asked again before writing, so an account switch, sign-out * or a Home that mounted meanwhile never receives a late write. */ export function prefetchHomeWarmSnapshot( accountId: string, isCurrent: () => boolean, io: HomeWarmPrefetchIo = defaultIo, signal?: AbortSignal, ): Promise { if (!accountId || isCompleteHomeWarmSnapshot(readHomeWarmSnapshot(accountId))) return Promise.resolve(false); if (inflight && inflight.accountId === accountId) return inflight.promise; const promise = (async () => { try { const [modelCatalog, summary] = await Promise.all([ io.fetchModelCatalog(signal), // Same settle rule as the cold start: a failed summary reads as the default card. io.fetchRectificationEntrySummary(signal).catch(() => null), ]); if (signal?.aborted || !isCurrent()) return false; if (isCompleteHomeWarmSnapshot(readHomeWarmSnapshot(accountId))) return false; const next = { accountId, modelCatalog, entrySummary: summary, entrySummarySettled: true, sessionsCursor: null, listBoot: null }; if (!isCompleteHomeWarmSnapshot(next)) return false; writeHomeWarmSnapshot(next); return true; } catch { return false; } finally { inflight = null; } })(); inflight = { accountId, promise }; return promise; } export function resetHomeWarmPrefetchForTests(): void { inflight = null; }