Landing stats band takes the versioned stats file from the browser cache again; the build log names the baked file

Since e537a4b the backend writes a new versioned file per publish, so its
immutable header holds again. The browser revalidates only the manifest
(no-cache) and serves the file it names from its cache (default mode),
instead of revalidating both, the workaround in 762fef4 for the per-day name.

The build log names the baked file ("Platform stats baked into the landing
page from platform-stats.<date>T<time>Z.json (as of ...)"), so every web
deploy shows which publish it carries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4ddfosgdeCwXo7NvB9xd2
This commit is contained in:
Claude
2026-10-03 04:57:38 +00:00
parent e537a4b074
commit ca10f36624
2 changed files with 32 additions and 18 deletions
@@ -8,10 +8,10 @@ import {
const CONTAINER = "https://stats.example.test/publicstats";
const MANIFEST_URL = `${CONTAINER}/platform-stats.json`;
const PAYLOAD_URL = `${CONTAINER}/platform-stats.2026-10-02.json`;
const PAYLOAD_URL = `${CONTAINER}/platform-stats.2026-10-02T140005Z.json`;
/** What the backend publishes (MaintenanceService.TryPublishPlatformStatsJsonAsync). */
const MANIFEST = { latest: "platform-stats.2026-10-02.json", generatedAtUtc: "2026-10-02T14:00:05.1234567Z" };
const MANIFEST = { latest: "platform-stats.2026-10-02T140005Z.json", generatedAtUtc: "2026-10-02T14:00:05.1234567Z" };
const PAYLOAD = {
totalEnrollments: 12481,
totalUsers: 310,
@@ -91,13 +91,13 @@ describe("toStatItems", () => {
});
describe("fetchLivePlatformStats", () => {
it("reads the manifest, then the file it names, revalidating both with the blob", async () => {
it("revalidates the manifest and takes the immutable file it names from the browser cache", async () => {
const fetchMock = serve(BOTH_FILES);
expect(await fetchLivePlatformStats(MANIFEST_URL)).toEqual(PAYLOAD);
expect(fetchMock.mock.calls).toEqual([
[MANIFEST_URL, { cache: "no-cache" }],
[PAYLOAD_URL, { cache: "no-cache" }],
[PAYLOAD_URL, { cache: "default" }],
]);
});
@@ -106,7 +106,7 @@ describe("fetchLivePlatformStats", () => {
Object.defineProperty(redirected, "url", { value: "https://stats.example.test/v2/platform-stats.json" });
const fetchMock = serve({
[MANIFEST_URL]: () => redirected,
"https://stats.example.test/v2/platform-stats.2026-10-02.json": () => json(PAYLOAD),
"https://stats.example.test/v2/platform-stats.2026-10-02T140005Z.json": () => json(PAYLOAD),
});
expect(await fetchLivePlatformStats(MANIFEST_URL)).toEqual(PAYLOAD);
@@ -161,7 +161,7 @@ describe("loadPlatformStatsSnapshot", () => {
expect(init?.signal).toBeInstanceOf(AbortSignal);
}
expect(inits[0]?.signal).toBe(inits[1]?.signal);
expect(log).toHaveBeenCalledWith(expect.stringContaining(PAYLOAD.lastUpdated));
expect(log).toHaveBeenCalledWith(expect.stringContaining(`from ${MANIFEST.latest} (as of ${PAYLOAD.lastUpdated})`));
});
it("fails soft with the reason in the build log when the blob does not answer", async () => {
@@ -95,9 +95,23 @@ export function toStatItems(payload: PlatformStatsPayload): StatItem[] {
});
}
/** Manifest, then the file it names. Throws with the reason; the callers decide what to report. */
async function readPlatformStats(manifestUrl: string, init: RequestInit): Promise<PlatformStatsPayload> {
const manifestResponse = await fetch(manifestUrl, init);
/** One read: the numbers and the versioned file they came from (named in the build log). */
interface PlatformStatsRead {
payload: PlatformStatsPayload;
file: string;
}
/**
* Manifest, then the file it names. Throws with the reason; the callers decide what to report.
* Each hop takes its own request options: the manifest changes with every publish, a versioned
* file never does.
*/
async function readPlatformStats(
manifestUrl: string,
manifestInit: RequestInit,
payloadInit: RequestInit = manifestInit,
): Promise<PlatformStatsRead> {
const manifestResponse = await fetch(manifestUrl, manifestInit);
if (!manifestResponse.ok) throw new Error(`${manifestUrl} answered HTTP ${manifestResponse.status}`);
const manifest = (await manifestResponse.json()) as Partial<PlatformStatsManifest> | null;
if (typeof manifest?.latest !== "string" || !manifest.latest) {
@@ -111,23 +125,23 @@ async function readPlatformStats(manifestUrl: string, init: RequestInit): Promis
throw new Error(`${manifestUrl} names a file on another origin`);
}
const payloadResponse = await fetch(payloadUrl.toString(), init);
const payloadResponse = await fetch(payloadUrl.toString(), payloadInit);
if (!payloadResponse.ok) throw new Error(`${payloadUrl} answered HTTP ${payloadResponse.status}`);
const payload: unknown = await payloadResponse.json();
if (typeof payload !== "object" || payload === null) throw new Error(`${payloadUrl} holds no stats object`);
return payload as PlatformStatsPayload;
return { payload: payload as PlatformStatsPayload, file: manifest.latest };
}
/**
* The browser's read. `no-cache` revalidates both files with the blob: the versioned file is
* named per day but rewritten every 2 hours under `Cache-Control: immutable`, so a copy cached
* earlier that day would replace the newer baked numbers with older ones.
* The browser's read. The manifest is revalidated on every load; the versioned file it names
* comes from the browser cache once fetched: the backend writes a new name per publish and serves
* it immutable (MaintenanceService.PlatformStatsVersionedFileName).
*/
export async function fetchLivePlatformStats(
manifestUrl: string = PLATFORM_STATS_MANIFEST_URL,
): Promise<PlatformStatsPayload | null> {
try {
return await readPlatformStats(manifestUrl, { cache: "no-cache" });
return (await readPlatformStats(manifestUrl, { cache: "no-cache" }, { cache: "default" })).payload;
} catch {
// The band is decorative: a failed read only means no live numbers.
return null;
@@ -147,14 +161,14 @@ export async function loadPlatformStatsSnapshot(
): Promise<PlatformStatsSnapshot | null> {
if (!/^https?:\/\//i.test(manifestUrl)) return null;
try {
const payload = await readPlatformStats(manifestUrl, {
const { payload, file } = await readPlatformStats(manifestUrl, {
cache: "no-store",
signal: AbortSignal.timeout(BUILD_FETCH_TIMEOUT_MS),
});
const items = toStatItems(payload);
if (items.length === 0) throw new Error("the stats file holds no positive figure");
if (items.length === 0) throw new Error(`${file} holds no positive figure`);
const asOf = typeof payload.lastUpdated === "string" ? payload.lastUpdated : null;
console.log(`Platform stats baked into the landing page (as of ${asOf ?? "unknown"}).`);
console.log(`Platform stats baked into the landing page from ${file} (as of ${asOf ?? "unknown"}).`);
return { items, asOf };
} catch (err) {
// Node's fetch reports every network failure as "fetch failed" and keeps the reason in `cause`.