diff --git a/AGENTS.md b/AGENTS.md index f57e6a1..23e4c42 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,9 +113,10 @@ src/client/ React app, service worker, manifest useCustom.ts — the reader's own games and events (PRD F13) lens.ts — who sees which rows (focus, outstanding, next-to-expire); pure zoom.ts — the timeline's scale ladder; pure + lanes.ts — how the timeline stacks: a lane per game, or one deadline queue; pure scripts/ build-feed.ts, build-static.ts, parse-fixture.ts (offline), refresh-sources.ts (fetches) serve.ts static server + /api/health -test/ 599 tests +test/ 606 tests fixtures// raw HTML + .expected.json per source — pinned, kept forever snapshots/ current page per source, rewritten by refresh — see its README ``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 4cf40b4..395cfe0 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -126,6 +126,7 @@ src/ sort.ts deadline order, or what you're partway through lens.ts who sees which rows — focus, outstanding, next-to-expire zoom.ts the timeline's scale ladder; pure + lanes.ts how the timeline stacks — lanes, or one deadline queue; pure useAppUpdate.ts is a newer build waiting, and taking it (F14) serve.ts static server + /api/health ✓ built scripts/ diff --git a/docs/DATA-MODEL.md b/docs/DATA-MODEL.md index bb91ff1..07e7e95 100644 --- a/docs/DATA-MODEL.md +++ b/docs/DATA-MODEL.md @@ -218,12 +218,15 @@ Namespaced, versioned, and small. Nothing here ever goes to the server. "gacha-tracker:v1:daily" // { [id]: { days: ["2026-08-15", ...], at } } "gacha-tracker:v1:ignored" // { [eventId]: { at } } — "stop showing me this" "gacha-tracker:v1:prefs" // { region, hiddenGames[], knownGames[]?, focusGame, sort, view, - // timelineDayWidth, detectDaily, showCompleted, showIgnored, - // regionConfirmed, onboarded } + // timelineDayWidth, timelineGroup, detectDaily, showCompleted, + // showIgnored, regionConfirmed, onboarded } // timelineDayWidth is px per day on the board, stored as the // measurement rather than a step number and read through // snapDayWidth — so a value from an older ladder still opens // on something renderable. + // timelineGroup is "game" (a lane each) or "ending" (every game + // in one deadline queue). Defaults to "game", so shipping it + // moved nobody's board; see PRD F1. // knownGames is every lane the reader has been offered. Absent // means unrecorded, not "offered nothing" — see PRD F8; a lane // missing from it is new to them and arrives switched off. diff --git a/docs/PRD.md b/docs/PRD.md index d6bbe42..920cc2f 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -66,8 +66,8 @@ no stored key. A game may have several sources; see `docs/INGESTION.md` § Three ### Features **F1 — Timeline view.** -A horizontal timeline, one lane per game, spanning a scrollable date range with "today" pinned as a -vertical marker. Each event is a bar from `startsAt` to `endsAt`. Bars are colored by game, and +A horizontal timeline, by default one lane per game, spanning a scrollable date range with "today" +pinned as a vertical marker. Each event is a bar from `startsAt` to `endsAt`. Bars are colored by game, and completed events render at reduced opacity with a check. Clicking a bar opens a detail panel with title, type, exact start/end in the user's local timezone, source link, and a completion toggle. @@ -81,6 +81,20 @@ 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 reader chooses how it stacks.** Lanes keep a game's events adjacent and comparable, which is +what makes the board readable for someone playing four of them — but that reader also has one queue +of deadlines, and lanes scatter it: what ends tonight sits three lanes below what ends next month, +and no scroll position puts them side by side. So a pair of pills in the board's header switches +between **By game** and **Ending soonest**, and the choice is remembered (`prefs.timelineGroup`, +default `"game"` — the board every existing reader already has). Merged, the order is +`endingSoonestFirst`, the same comparator behind the list's "Ending soonest" (F2), so the two views +cannot mean different things by the same words and an unannounced end still sorts behind every dated +one rather than claiming a place in the queue. Merged there is also no lane heading to say whose +event a bar is, and hue alone cannot answer that once thirteen games share a stack — so each bar +carries its game's short name, with the full name in its tooltip. A bar too narrow for a tag and a +title both keeps the title: a chopped game name reads as a broken word, and the hue and the tooltip +still answer it. + **The reader sets the scale.** A patch cycle is six weeks and a login campaign can run for months, so no single density answers both "what am I in the middle of this week?" and "how do the next three months line up?". A pair of controls steps through a ladder of day widths, and the choice is