/** * Home bootstrap reveal policy. * * The loading screen owns every wait before the page appears. Phase "account" * fetches account, model catalog and the session list; phase "prepare" fetches * the starter questions, today's starlanguage and the rectification entry * summary in parallel. The page is revealed once every applicable item has * settled, or when the prepare budget runs out — never with a spinner inside. */ export type BootstrapPhase = "account" | "prepare"; export const BOOTSTRAP_PREPARE_TIMEOUT_MS = 4000; export const SESSION_PREFETCH_COUNT = 5; export type BootstrapPrepareState = Readonly<{ profileComplete: boolean; onboardingSettled: boolean; dailyStarlanguageApplicable: boolean; dailyStarlanguageSettled: boolean; entrySummarySettled: boolean; }>; export function bootstrapPrepareSettled(state: BootstrapPrepareState): boolean { if (!state.entrySummarySettled) return false; if (state.profileComplete && !state.onboardingSettled) return false; if (state.dailyStarlanguageApplicable && !state.dailyStarlanguageSettled) return false; return true; } export function bootstrapRevealDelayMs(now: number, prepareStartedAt: number | null, settled: boolean): number { if (settled) return 0; if (prepareStartedAt === null) return BOOTSTRAP_PREPARE_TIMEOUT_MS; return Math.max(0, BOOTSTRAP_PREPARE_TIMEOUT_MS - (now - prepareStartedAt)); } export function bootstrapLoadingCopy(phase: BootstrapPhase): Readonly<{ title: string; detail: string }> { return phase === "prepare" ? { title: "正在准备对话", detail: "整理推荐问题、今日星语与对话记录" } : { title: "正在载入账户", detail: "同步个人资料与对话记录" }; } export type PrefetchableSession = Readonly<{ id: string; sessionType: string; messagesHydrated?: boolean; }>; export function sessionIdsToPrefetch( sessions: readonly PrefetchableSession[], activeSessionId: string, limit = SESSION_PREFETCH_COUNT, ): string[] { const ids: string[] = []; for (const session of sessions) { if (ids.length >= limit) break; if (session.id === activeSessionId) continue; if (session.messagesHydrated) continue; if (session.sessionType !== "consultation") continue; ids.push(session.id); } return ids; }