feat(filter): focus one game at a time

Switching games on and off says which games you play, and is set once.
It's the wrong tool for what a player of four games does while reading —
clear one game, move to the next — which cost two taps per game and left
the settings panel no longer describing what they play.

Focus is a lens over that filter, not a second filter: a bar at the top,
above everything it narrows, with a "next game" control that steps
through and ends by returning to all. It never touches hiddenGames, and
a focus on a game since switched off is ignored rather than obeyed, so
it can't strand you on a blank page whose cause is elsewhere. Each chip
carries that game's outstanding count, so a game with nothing waiting
says so before you visit it.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-16 20:42:42 +02:00
co-authored by Claude Opus 5
parent 2a7a117705
commit d70720e2af
11 changed files with 350 additions and 24 deletions
+4 -3
View File
@@ -88,9 +88,10 @@ src/
Timeline.tsx calendar lanes (F1)
EventDetail.tsx detail sheet, ignore action (F9)
ProgressControls status, effort, note (F12)
Dailies.tsx today's strip of repeating jobs
Dailies.tsx today's strip, per-game reset clocks
DailyChecklist one repeating event's whole run
Fireworks.tsx the burst when the last daily lands
GameFocus.tsx one game at a time (F4a)
Controls.tsx games, region, export/import(F4, F5, F6)
Welcome.tsx first-run game picker (F8)
Toast.tsx undo an ignore
@@ -100,9 +101,9 @@ src/
useMarkSet.ts ignores (and the superseded completions shape)
useProgress.ts status, effort, note, daily override (F12)
useDailyLog.ts which game-days are ticked off
usePrefs.ts region, filters, onboarding flags
usePrefs.ts region, filters, focus, onboarding flags
sort.ts deadline order, or what you're partway through
lens.ts who sees which rows — outstanding, next-to-expire
lens.ts who sees which rows — focus, outstanding, next-to-expire
serve.ts static server + /api/health ✓ built
scripts/
build-feed.ts fixtures → public/data/events.v1.json ✓ built
+2 -2
View File
@@ -199,8 +199,8 @@ Namespaced, versioned, and small. Nothing here ever goes to the server.
"gacha-tracker:v1:progress" // { [eventId]: { status?, effort?, note?, at } }
"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[], sort, detectDaily, showCompleted,
// showIgnored, regionConfirmed, onboarded }
"gacha-tracker:v1:prefs" // { region, hiddenGames[], focusGame, sort, detectDaily,
// showCompleted, showIgnored, regionConfirmed, onboarded }
"gacha-tracker:v1:completions" // SUPERSEDED — read once to migrate, never written
```
+14
View File
@@ -73,6 +73,20 @@ but de-emphasized; a filter toggles them out entirely.
Filter by game (multi-select, persisted) and by event type. Hiding a game hides it from both views.
Preferences persist in `localStorage`.
**F4a — Focus one game at a time.**
Switching games on and off says *which games the reader plays*, and is set once. It is the wrong
tool for the thing a player of four games actually does while reading: clear one game, move to the
next. Doing that with the on/off switches costs two taps per game and leaves the settings panel no
longer describing what they play.
So focus is a **lens over the filter, not a second filter**: a bar at the top of the page, above
everything it affects, narrowing every view — headline, dailies, lists, calendar and counts — to one
game, with a "next game" control that steps through them and ends by returning to all. It never
changes `hiddenGames`, "All" is always one tap away, and a focus on a game that is switched off or
has left the feed is ignored rather than obeyed, so it can never strand the reader on a blank page
whose cause is elsewhere. Each chip carries that game's outstanding count, so a game with nothing
waiting says so before it is visited.
**F5 — Region selection.**
A user picks Asia / America / Europe once. For events where `regionScoped` is true, all displayed
end times resolve to that region's server reset. This is stored in `localStorage` and defaults to a