Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
74 lines
3.2 KiB
TypeScript
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;
|
|
}
|