diff --git a/docs/PRD.md b/docs/PRD.md index 0883ff0..2941c41 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -462,6 +462,11 @@ counted, in the same words the empty list uses — *the games you have switched 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. +**The staleness notice differentiates crawler checks from site updates.** If our crawler successfully +checked the wiki recently (e.g. confirming HTTP 304) but the site's content has not changed in over +two days, the notice explicitly distinguishes the two: "data pulled X ago, site updated Y ago", with +an expandable breakdown detailing each source's check and content update times. + **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/components/Colophon.tsx b/src/client/components/Colophon.tsx index f5db915..0e31dc2 100644 --- a/src/client/components/Colophon.tsx +++ b/src/client/components/Colophon.tsx @@ -1,6 +1,6 @@ import { useGameMeta } from "../state/gameMeta.tsx"; import type { LaneId } from "../../shared/custom.ts"; -import { freshness, type SourceHealth } from "../../shared/feed.ts"; +import { freshness, STALE_AFTER_MS, type SourceHealth, type StaleGame } from "../../shared/feed.ts"; import { formatAbsolute, formatRemaining } from "../../shared/time.ts"; export const REPO_URL = "https://github.com/StereotypicalCat/gacha-event-tracker"; @@ -124,6 +124,29 @@ function siteFor(url: string): { name: string; url: string } { } } +function formatStaleAge(s: StaleGame, now: number): string { + if (s.lastSuccessAt === null && s.contentChangedAt === null) { + return " (never)"; + } + + const contentDate = s.contentChangedAt ?? s.lastSuccessAt; + const contentAgo = contentDate + ? `${formatRemaining(now - Date.parse(contentDate))} ago` + : null; + + // Differentiate blame when crawler confirmed the source recently (<= STALE_AFTER_MS) + // but the site content has not changed in over 2 days. + if (s.lastConfirmedAt !== null) { + const confirmedAge = now - Date.parse(s.lastConfirmedAt); + if (confirmedAge <= STALE_AFTER_MS) { + const confirmedAgo = `${formatRemaining(confirmedAge)} ago`; + return ` (data pulled ${confirmedAgo}, site updated ${contentAgo ?? "unknown"})`; + } + } + + return contentAgo ? ` (${contentAgo})` : " (never)"; +} + /** * Credit, disclaimer, and where the code lives. * @@ -253,9 +276,7 @@ export function Colophon({ {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)`} + {formatStaleAge(s, now)} ))} {shownStale.length > STALE_NAMES && @@ -267,6 +288,57 @@ export function Colophon({ )}
)} + + {shownStale.some((s) => s.sources && s.sources.length > 0) && ( +