diff --git a/docs/PRD.md b/docs/PRD.md index 9610438..6aff88c 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -80,6 +80,12 @@ calendar on screen, and nothing left saying which day, whose game, or which even six-week bar starts weeks off-screen, so a name that rides off with its own start date leaves a coloured rectangle behind. +**The board draws at most two months of past.** A standing login campaign can have been running for +half a year, and drawing from the earliest start bought months of empty calendar that nobody scrolls +back through and that pushed every other bar off to the right. An event older than the board keeps +its faded left edge rather than being redrawn as though it started at the edge — the same honesty +the frayed right edge carries for an unannounced end. + **F2 — Ends-soonest list.** A flat list of all *currently running* events sorted ascending by end date, with a relative countdown ("ends in 2 days", "ends in 4 hours"). Under 24 hours, the row is emphasized. This is the diff --git a/src/client/components/Timeline.tsx b/src/client/components/Timeline.tsx index 3f10343..98f2a83 100644 --- a/src/client/components/Timeline.tsx +++ b/src/client/components/Timeline.tsx @@ -28,6 +28,21 @@ const HALF_DAY_LEAD = 12 * 60 * 60 * 1000; */ const PAST_LEAD = 7 * DAY; +/** + * The oldest date the board will draw, however long an event has been running. + * + * A standing login campaign can have started half a year ago, and the window + * was drawn from the earliest start — so one such event bought months of empty + * calendar to the left of everything else, which nobody scrolls back through + * and which the reader pays for in every bar being pushed off to the right. + * Two months is a patch cycle and a half: enough that a running event's start + * is usually still on the board, and past the point where the answer stops + * changing what anyone does today. A bar that begins before this keeps its + * faded left edge, which is the same honesty the mask already carried — the + * event is older than the board, not newly started at its edge. + */ +const PAST_LIMIT = 60 * DAY; + /** Where the now rule sits when the board opens: a little in from the edge. */ const OPEN_INSET = 28; @@ -65,13 +80,7 @@ export function Timeline({ const ends = rows.map((r) => r.clock.endsMs ?? r.clock.startsMs + 14 * DAY); const starts = rows.map((r) => r.clock.startsMs); - - // The window covers the past too, so a reader can scroll back to see when a - // running event began — but it *opens* scrolled to now, because that is what - // they came for. Rendering from the earliest start alone buried today - // off-screen; clamping to now made the past unreachable. This does both. - const min = Math.min(...starts, now) - PAST_LEAD; - const max = Math.max(...ends, now) + 2 * DAY; + const { min, max } = boardWindow(starts, ends, now); const totalDays = Math.ceil((max - min) / DAY); const chartWidth = totalDays * DAY_WIDTH; /** One coordinate space for everything: bars, gridlines and the now rule. */ @@ -183,7 +192,7 @@ export function Timeline({ ))} -
{game.short}
-