fix(refresh): don't call a stored copy "nothing newer", and point an entry at its feature
CI / Typecheck, test, feed (push) Successful in 15s
CI / Build site (push) Successful in 8s
CI / Container image (push) Failing after 29s

When the network failed behind the service worker, it answered from its
own store. Check for new data then said nothing newer was published when
the server was never reached. The worker now marks that copy, and a fresh
check treats it as unreachable; ordinary loads still use it offline.

The built-in tasks entry linked to a follow-up fix instead of the
feature, and the changelog spec's details now match what shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-13 14:31:04 +02:00
1 parent 1fc47e2864
commit ab3173adf0
5 files changed
+37 -8

No files matched your search

@@ -88,19 +88,17 @@ launch window rather than replaying everything.
**What's new card** — top of the main column, above the lists; never in the rail, which holds
instructions. Eyebrow `What's new`, up to five entries (`New` / `Fixed` / `Games & sources` tag,
title, date), `See all` (opens the changelog) and `Got it`. More than five shows `+ 3 more` inside
See all. Dismissible, never modal, never shown on first run.
title, date), `See all` (opens the changelog) and `Got it`. More than five reads `See all (3 more)`. Dismissible, never modal, never shown on first run.
**Changelog sheet** — a `What's new` link in the footer beside the existing credits opens a sheet
**Changelog sheet** — a `Changelog` link in the footer beside *Source code* opens a sheet
in the detail sheet's style. Release notes, newest first, each with title, date, summary
paragraphs, `commits from…to` (compare link) and spec links; the short entries a note covers listed
under it with commit links. Entries with no note are listed by month at the end.
## Conventions this adds
AGENTS.md § Conventions gains: **a change a reader can see adds a short entry in the same commit**,
with the ref filled in by a follow-up `chore(changelog)` commit when the change's own hash cannot
be known in advance (a commit cannot contain its own hash). A feature that spans several commits
AGENTS.md § Conventions gains: **a change a reader can see adds a short entry**, in a follow-up
`chore(changelog)` commit naming the change's commit — a commit cannot contain its own hash. A feature that spans several commits
gets a release note when it lands, summarising its spec and range.
## Backfill
+5
View File
@@ -29,6 +29,11 @@ export async function fetchFeed(
if (!res.ok) {
throw new Error(`Feed request failed (${res.status}).`);
}
// Asked for the newest data, a copy the service worker served from its own
// store is not an answer from the server: the network failed behind it.
if (options.fresh === true && res.headers.get("x-served-from") === "sw-cache") {
throw new Error("The server could not be reached; this is the stored copy.");
}
const json: unknown = await res.json();
const version = (json as { schemaVersion?: number }).schemaVersion;
+8 -1
View File
@@ -146,7 +146,14 @@ async function feedFirst(request) {
return response;
} catch {
const cached = await cache.match(request);
if (cached !== undefined) return cached;
// Marked as the stored copy, so a reader who asked for the newest data
// ("Check for new data") is told the server was not reached rather than
// that nothing newer was published. An ordinary load ignores the mark.
if (cached !== undefined) {
const headers = new Headers(cached.headers);
headers.set("x-served-from", "sw-cache");
return new Response(cached.body, { status: cached.status, headers });
}
// No network and nothing cached: say so in the feed's own shape, so the
// client renders its error state rather than failing to parse.
return new Response(
+1 -1
View File
@@ -72,7 +72,7 @@ export const ENTRIES: Entry[] = [
kind: "feature",
title: "Built-in weekly tasks for Zenless Zone Zero and Arknights: Endfield",
body: "Ridu Weekly (1,300 points) and the Weekly Routine (10 points), each dated by the page it comes from. You're asked once per game; Make it mine copies one into your own tasks to edit.",
ref: "1c7df63",
ref: "624c725",
note: "weekly-and-monthly-tasks",
},
// 2026-10-13 — weekly and monthly tasks
+19
View File
@@ -107,3 +107,22 @@ describe("the age right after a refresh", () => {
expect(html).not.toContain("ended ago");
});
});
describe("review fix: a stored copy is not an answer from the server", () => {
const original = globalThis.fetch;
afterEach(() => {
globalThis.fetch = original;
});
test("asked fresh, the service worker's fallback copy counts as not reaching the server", async () => {
globalThis.fetch = (async () =>
new Response(JSON.stringify(feed()), {
status: 200,
headers: { "x-served-from": "sw-cache" },
})) as unknown as typeof fetch;
(globalThis as { document?: unknown }).document ??= { baseURI: "https://example.test/" };
await expect(fetchFeed(undefined, { fresh: true })).rejects.toThrow();
// An ordinary load still takes it: offline, the stored copy is the point.
await expect(fetchFeed()).resolves.toBeDefined();
});
});