Files
Jyotisha/frontend/src/lib/boot-early-reads.ts
T
Jesse_ChenandClaude Opus 5.5 2fd1e2b05d perf(home): the HTML sends the cold-boot reads before the bundle runs (BUG-1127)
An inline classic script on / starts account, people list, sessions and
models; the boot takes each once from one global, falls back to the normal
request on failure, drops another person's or another account's list, and
trusts nothing older than 30s. Paths and init come from one shared module.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-01 00:19:56 +08:00

124 lines
4.8 KiB
TypeScript

/**
* Cold-boot reads sent by the HTML itself (TASK-home-first-load-20260930 T8,
* BUG-1127).
*
* The home loading screen used to wait for every first-screen chunk to download
* and run before asking for the account. The reads do not depend on the bundle,
* so an inline classic script in `<head>` sends them as soon as the HTML
* arrives, on `/` only, and parks the promises on one global. The boot code
* takes each one once (`takeBootRead`); after that, and on every other page,
* the normal requests run unchanged.
*
* Both sides are built from the constants below, so the early request and the
* normal one cannot drift apart (same path, query, `cache: "no-store"` and
* default same-origin credentials). Nothing here is written to any storage:
* the script only reads the person choice `current-subject.ts` already keeps.
*
* No React, no "use client": the root layout (a server component) imports the
* script string.
*/
import { SESSION_PAGE_SIZE } from "./session-cursor.ts";
export const BOOT_READS_GLOBAL = "__jyotishaBootReads";
export const BOOT_ACCOUNT_PATH = "/api/account";
export const BOOT_CHART_PROFILES_PATH = "/api/chart-profiles";
export const BOOT_MODELS_PATH = "/api/models";
export const BOOT_READ_INIT: RequestInit = { cache: "no-store" };
/** Where `current-subject.ts` keeps the last signed-in account and its chosen person. */
export const LAST_SUBJECT_ACCOUNT_KEY = "jyotisha_current_subject_account";
export const CURRENT_SUBJECT_KEY_PREFIX = "jyotisha_current_subject:";
/** An early read older than this is not trusted; the boot reads fresh instead. */
export const BOOT_READ_MAX_AGE_MS = 30_000;
export function bootSessionsPath(subjectId: string): string {
return `/api/sessions?limit=${SESSION_PAGE_SIZE}&subject=${encodeURIComponent(subjectId)}`;
}
type BootReads = {
startedAt: number;
account?: Promise<Response>;
chartProfiles?: Promise<Response>;
models?: Promise<Response>;
sessions?: Promise<Response>;
sessionsSubject?: string;
};
type BootReadKey = "account" | "chartProfiles" | "models";
function js(value: string): string {
return JSON.stringify(value).replace(/</g, "\\u003c");
}
/** Classic ES5, no modules: it runs before and independently of the bundle. */
export function bootEarlyReadsScript(): string {
const init = js(BOOT_READ_INIT.cache as string);
return `(function () {
try {
if (window.location.pathname !== "/" || typeof window.fetch !== "function" || typeof window.Promise !== "function") return;
var subject = "self";
try {
var account = window.localStorage.getItem(${js(LAST_SUBJECT_ACCOUNT_KEY)}) || "";
if (account) subject = window.localStorage.getItem(${js(CURRENT_SUBJECT_KEY_PREFIX)} + account) || "self";
} catch (storageError) { subject = "self"; }
var read = function (path) {
var request = window.fetch(path, { cache: ${init} });
request["catch"](function () {});
return request;
};
window[${js(BOOT_READS_GLOBAL)}] = {
startedAt: Date.now(),
account: read(${js(BOOT_ACCOUNT_PATH)}),
chartProfiles: read(${js(BOOT_CHART_PROFILES_PATH)}),
models: read(${js(BOOT_MODELS_PATH)}),
sessions: read(${js(`/api/sessions?limit=${SESSION_PAGE_SIZE}&subject=`)} + encodeURIComponent(subject)),
sessionsSubject: subject
};
} catch (error) {}
})();`;
}
function bootReads(): BootReads | null {
if (typeof window === "undefined") return null;
const reads = (window as unknown as Record<string, BootReads | undefined>)[BOOT_READS_GLOBAL];
if (!reads || typeof reads !== "object") return null;
if (typeof reads.startedAt !== "number" || Date.now() - reads.startedAt > BOOT_READ_MAX_AGE_MS) {
clearBootReads();
return null;
}
return reads;
}
export function clearBootReads(): void {
if (typeof window === "undefined") return;
delete (window as unknown as Record<string, unknown>)[BOOT_READS_GLOBAL];
}
/** The early read for `key`, once; later calls get null. */
export function takeBootRead(key: BootReadKey): Promise<Response> | null {
const reads = bootReads();
const read = reads?.[key];
if (!reads || !read) return null;
delete reads[key];
return read;
}
/**
* The early sessions read, once, and only if it asked for `subjectId`. A read
* for another person is dropped, never returned (task red line 3).
*/
export function takeBootSessionsRead(subjectId: string): Promise<Response> | null {
const reads = bootReads();
if (!reads?.sessions) return null;
const read = reads.sessions;
const asked = reads.sessionsSubject;
delete reads.sessions;
delete reads.sessionsSubject;
return asked === subjectId ? read : null;
}
/** An early read that failed on the network falls back to the normal request. */
export function withBootFallback(early: Promise<Response> | null, fresh: () => Promise<Response>): Promise<Response> {
return early ? early.catch(() => fresh()) : fresh();
}