feat(client): state in the footer when event data last refreshed

The footer only spoke up past the two-day threshold, and then only as a
count of stale sources. A page silent about its own age reads as current,
so it now says when the data last refreshed on every load — absolute date
plus relative age, next to where the warning appears. This is the half of
PRD F7 that was never built.

Where the number comes from is the whole point. `freshness()` in
src/shared/feed.ts takes the newest lastSuccessAt and never generatedAt:
the feed is rebuilt on every deploy whether or not anything was refetched,
so a build stamp would report a calendar as minutes old while its events
came from a fixture captured months ago. A game is also only as fresh as
its oldest source, or Endfield's live wiki would vouch for its stalled
Game8 page. That matters concretely here — eight sources cannot be fetched
from CI at all, so this notice is what stands between a reader and a
confidently stale calendar.

The stale warning now names the lagging games and how far behind each is,
because a count is not something a reader can act on while a name tells
them which source page to check. Two exceptions found by rendering it
against the real feed: past four games it summarises the remainder, and
when every game is behind — what a refresh that stopped running looks like
— it collapses to one sentence rather than ten names repeating one age.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-17 22:58:32 +02:00
co-authored by Claude Opus 5
parent 0d81c728ba
commit 3d03775e26
7 changed files with 368 additions and 17 deletions
+8 -4
View File
@@ -92,10 +92,14 @@ first thing to find out.
making a request.
3. If Game8 is being refused, that is a scraping-conduct question before it is a code question —
re-read `AGENTS.md` § Scraping conduct and decide, rather than working around it.
4. Regardless of cause: surface it in the UI. `Colophon.tsx` already receives `staleCount`, and
`App.tsx` computes `staleSources` at a two-day threshold. Verify a reader actually sees that
banner today, because if five of six games are on four-day-old fixtures, the app is currently
claiming more freshness than it has.
4. ~~Regardless of cause: surface it in the UI.~~ **Done.** The footer now states the data's age on
every load rather than only when something is wrong — `freshness()` in `src/shared/feed.ts`, read
by `Colophon.tsx`. Two things it settles, both of which were the "claiming more freshness than it
has" worry in concrete form: the age comes from the newest `lastSuccessAt` and never from
`generatedAt`, which is a build stamp that would call a fixture-backed calendar minutes old; and a
game is only as fresh as its *oldest* source, so Endfield's live wiki cannot vouch for its stalled
Game8 page. Lagging games are named rather than counted, because a count tells a reader nothing
they can act on — except when every game is behind, which collapses to one sentence.
5. Add a CI assertion that fails the build when a source has neither a snapshot nor a fixture newer
than N days. A silent fallback to stale bytes should not be able to deploy.