Files
Jyotisha/frontend/src/lib/home-warm-prefetch.ts
T

74 lines
3.2 KiB
TypeScript

/**
* 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<PublicLanguageModelCatalog>;
fetchRectificationEntrySummary: (signal?: AbortSignal) => Promise<RectificationEntrySummary | null>;
}>;
const defaultIo: HomeWarmPrefetchIo = { fetchModelCatalog, fetchRectificationEntrySummary };
let inflight: { accountId: string; promise: Promise<boolean> } | 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<boolean> {
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;
}