diff --git a/docs/superpowers/specs/2026-10-13-changelog-design.md b/docs/superpowers/specs/2026-10-13-changelog-design.md index b33dd30..c2dc303 100644 --- a/docs/superpowers/specs/2026-10-13-changelog-design.md +++ b/docs/superpowers/specs/2026-10-13-changelog-design.md @@ -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 diff --git a/src/client/api.ts b/src/client/api.ts index cb43d54..427aac3 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -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; diff --git a/src/client/sw.js b/src/client/sw.js index 4925b50..33646c6 100644 --- a/src/client/sw.js +++ b/src/client/sw.js @@ -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( diff --git a/src/shared/changelog.ts b/src/shared/changelog.ts index 1792e34..10e7480 100644 --- a/src/shared/changelog.ts +++ b/src/shared/changelog.ts @@ -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 diff --git a/test/refresh.test.tsx b/test/refresh.test.tsx index c000583..cd0e8c1 100644 --- a/test/refresh.test.tsx +++ b/test/refresh.test.tsx @@ -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(); + }); +});