diff --git a/AGENTS.md b/AGENTS.md index 5257eb9..fad537e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1055,3 +1055,13 @@ to an open page). Four things hold it up: so one live wiki cannot vouch for a stalled sibling. Given that thirteen sources cannot be fetched from CI at all (§ Scraping conduct), this disclosure is the only thing standing between a reader and a confidently stale calendar — do not let a future change source it from the build clock. + **The notice under it is scoped to the reader's lanes; the line itself is not.** The age is a fact + about the feed, but the list beneath it is an instruction whose only remedy is "check that game's + source page" — unactionable for a game they switched off, and with nineteen lanes and two on it + buries the one they can do something about. So `Colophon` takes `prefs.hiddenGames` and filters + both the named list *and* the count the summarising branch measures against, or that branch would + never fire for a reader with most of the calendar off. Two things must not follow from it: the + credits are attribution and stay whole, and the notice **says** it is scoped — narrowing what the + footer measured while still printing "nothing has refreshed" would turn a claim about four games + into one about eighteen, in the one paragraph on the page whose whole job is being trusted about + age. diff --git a/docs/PRD.md b/docs/PRD.md index 60faa1b..0883ff0 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -447,6 +447,21 @@ The footer shows when the feed was last updated, per game. If a game's data is m stale, its lane carries a warning badge. Never present stale data as current — the whole value proposition is trust in the dates. +**The staleness notice names only the games the reader has switched on, and says that it does.** +The headline age is about the feed and stays whole; the notice under it is an *instruction*, and the +only remedy it offers is "go and check that game's source page" — which a reader cannot act on for a +game they turned off and cannot see a single row of. With nineteen lanes and two switched on, naming +eleven they do not play buries the one they do, and a warning about games nobody reads is a warning +nobody reads (F8: a game we add is a game they never asked for, and arrives switched off, so an +untouched install would be warned about the whole catalogue). + +Both halves of that are load-bearing. Scoping it silently would be the worse half: "nothing has +refreshed" is a claim about the whole calendar, so a reader with fourteen of eighteen lanes off would +read a sentence about four as one about all of them. The notice therefore states whose games it +counted, in the same words the empty list uses — *the games you have switched on*. Credit in the +column beside it is owed to every source we read and is not scoped; nor is the "last refreshed" line, +which reports the feed and feeds the bug form. + **F15 — Light mode, with dark still the default.** The app is a lit instrument panel and that is what it should be on first sight, but it is also read on a train in daylight and by people who find a dark UI harder rather than moodier. A public fork diff --git a/src/client/App.tsx b/src/client/App.tsx index 21bba37..cf45dba 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -684,7 +684,14 @@ export function App() {
)} -
- {stale.length === games.length ? (
- `Nothing has refreshed in over two days, so any end date here may have moved.`
+ {shownStale.length === shownGames.length ? (
+ `None of the games you have switched on have refreshed in over two days, so any end date here may have moved.`
) : (
<>
- {stale.length === 1 ? "This game has" : "These games have"} not
- refreshed in over two days, so some of their end dates may have
- moved:{" "}
- {stale.slice(0, STALE_NAMES).map((s, i, shown) => (
+ Of the games you have switched on,{" "}
+ {shownStale.length === 1 ? "this one has" : "these have"} not
+ refreshed in over two days, so some end dates may have moved:{" "}
+ {shownStale.slice(0, STALE_NAMES).map((s, i, shown) => (
- {i > 0 && (i === shown.length - 1 && stale.length <= STALE_NAMES ? " and " : ", ")}
+ {i > 0 && (i === shown.length - 1 && shownStale.length <= STALE_NAMES ? " and " : ", ")}
{gameMeta(s.game).name}
{s.lastSuccessAt === null
? " (never)"
: ` (${formatRemaining(now - Date.parse(s.lastSuccessAt))} ago)`}
))}
- {stale.length > STALE_NAMES &&
- ` and ${stale.length - STALE_NAMES} other game${
- stale.length - STALE_NAMES > 1 ? "s" : ""
+ {shownStale.length > STALE_NAMES &&
+ ` and ${shownStale.length - STALE_NAMES} other game${
+ shownStale.length - STALE_NAMES > 1 ? "s" : ""
}`}
{"."}
>
diff --git a/test/custom-ui.test.tsx b/test/custom-ui.test.tsx
index b205a8f..984581e 100644
--- a/test/custom-ui.test.tsx
+++ b/test/custom-ui.test.tsx
@@ -286,7 +286,7 @@ describe("Colophon freshness notice (PRD F7)", () => {
lastSuccessAt: new Date(NOW - (80 + i) * HOUR).toISOString(),
}));
const html = renderToStaticMarkup(