fix(home): early boot reads load as an async data: script so stylesheets no longer hold them back (BUG-1127)

An inline classic script after the stylesheets waited for them to download;
on staging the reads left at 2.4-3.1 s. Measurement script gains
--css-delay and --ua iphone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-10-01 01:36:35 +08:00
co-authored by Claude Opus 5.5
parent 6e2f0af5a2
commit c2265dabb2
4 changed files with 47 additions and 6 deletions
+22 -1
View File
@@ -14,6 +14,11 @@
*
* /exec-daemon/node scripts/measure-home-first-load.mjs --base http://127.0.0.1:3920 --delay 600 [--runs 3] [--port 9340] [--throttle slow]
*
* `--css-delay 1500` holds every stylesheet response that long (TASK-home-first-load-fix
* -20261001): an inline script after the stylesheets waits for them, which a
* local server hides because CSS arrives instantly.
* `--ua iphone` uses an iPhone Safari user agent (headless Chrome engine, not WebKit).
*
* `--throttle slow` emulates 1.6 Mbps down / 150 ms latency so JS and font
* downloads cost something against a local server. "First-screen JS" is the
* scripts the HTML itself references (parser-initiated), not chunks loaded later.
@@ -30,6 +35,9 @@ const BASE = (args.base ?? "http://127.0.0.1:3000").replace(/\/$/, "");
const DELAY = Number(args.delay ?? 600);
const RUNS = Number(args.runs ?? 1);
const PORT = Number(args.port ?? 9340);
const CSS_DELAY = Number(args["css-delay"] ?? 0);
const IPHONE_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1";
const UA = args.ua === "iphone" ? IPHONE_UA : null;
const THROTTLE = args.throttle === "slow" ? { offline: false, latency: 150, downloadThroughput: 1_600_000 / 8, uploadThroughput: 750_000 / 8 } : null;
const ACCOUNT = JSON.stringify({
@@ -72,6 +80,7 @@ async function measureOnce() {
const urls = new Map();
const jsDone = [];
const parserJs = new Set();
const cssReleased = [];
const apiStarts = [];
ws.onmessage = (message) => {
const m = JSON.parse(message.data);
@@ -86,6 +95,12 @@ async function measureOnce() {
events.push([at, `api ${m.params.request.method} ${url.replace(/^https?:\/\/[^/]+/, "").slice(0, 60)} → ${status} (answered ${now()}ms)`]);
void send("Fetch.fulfillRequest", { requestId: m.params.requestId, responseCode: status, responseHeaders: [{ name: "content-type", value: "application/json" }], body: Buffer.from(body).toString("base64") });
}, DELAY);
} else if (CSS_DELAY > 0 && /\.css(\?|$)/.test(url)) {
setTimeout(() => {
events.push([now(), `stylesheet released ${url.split("/").pop().split("?")[0]}`]);
cssReleased.push(now());
void send("Fetch.continueRequest", { requestId: m.params.requestId });
}, CSS_DELAY);
} else {
void send("Fetch.continueRequest", { requestId: m.params.requestId });
}
@@ -103,6 +118,10 @@ async function measureOnce() {
await send("Fetch.enable", { patterns: [{ urlPattern: "*" }] });
await send("Network.enable");
if (THROTTLE) await send("Network.emulateNetworkConditions", THROTTLE);
if (UA) {
await send("Network.setUserAgentOverride", { userAgent: UA, platform: "iPhone" });
await send("Emulation.setDeviceMetricsOverride", { width: 390, height: 844, deviceScaleFactor: 3, mobile: true });
}
await send("Page.enable");
t0 = performance.now();
await send("Page.navigate", { url: `${BASE}/` });
@@ -129,8 +148,10 @@ async function measureOnce() {
if (revealed !== null && at > revealed) break;
if (!waves.length || at - waves[waves.length - 1] > DELAY / 2) waves.push(at);
}
const bootReads = await send("Runtime.evaluate", { expression: `(() => { const r = window.__jyotishaBootReads; return r === undefined ? "absent" : JSON.stringify(Object.keys(r)); })()`, returnByValue: true });
const firstCss = cssReleased.length ? Math.min(...cssReleased) : null;
chrome.kill();
return { events, summary: { revealed, lastJs, firstApi, firstApiVsLastJs: firstApi !== null && lastJs !== null ? firstApi - lastJs : null, apiRounds: waves.length, serifFontRequestsBeforeLoad: fontsBeforeLoad } };
return { events, summary: { firstStylesheetReleased: firstCss, firstApiBeforeCss: firstCss !== null && firstApi !== null ? firstApi < firstCss : null, bootReadsAfterReveal: bootReads?.result?.value ?? null, revealed, lastJs, firstApi, firstApiVsLastJs: firstApi !== null && lastJs !== null ? firstApi - lastJs : null, apiRounds: waves.length, serifFontRequestsBeforeLoad: fontsBeforeLoad } };
}
const summaries = [];