diff --git a/README.md b/README.md index 3aa7362..3d7b230 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ You play three or four gacha games. Each has its own calendar, none of them talk the only question that actually matters — *what runs out first?* — takes four browser tabs to answer. This does it in one screen. -No account. No login. Your completed events are saved in your browser and never leave your device. +No account. No login. What you've finished, what you're partway through, and how much work you +reckon each event is are saved in your browser and never leave your device. ## Status diff --git a/docs/DATA-MODEL.md b/docs/DATA-MODEL.md index 04da3a6..8f654c5 100644 --- a/docs/DATA-MODEL.md +++ b/docs/DATA-MODEL.md @@ -196,15 +196,44 @@ malformed row surfaces at the boundary rather than deep in the UI. Namespaced, versioned, and small. Nothing here ever goes to the server. ```ts -"gacha-tracker:v1:completions" // { [eventId]: { at: string } } — "I finished this" -"gacha-tracker:v1:ignored" // { [eventId]: { at: string } } — "stop showing me this" +"gacha-tracker:v1:progress" // { [eventId]: { status?, effort?, note?, at } } +"gacha-tracker:v1:ignored" // { [eventId]: { at } } — "stop showing me this" "gacha-tracker:v1:prefs" // { region, hiddenGames[], showCompleted, showIgnored, // regionConfirmed, onboarded } +"gacha-tracker:v1:completions" // SUPERSEDED — read once to migrate, never written ``` -Completions and ignores are the same shape and share one implementation -(`useMarkSet`), but stay in separate stores because they mean different things: a completed event is -dimmed and still counted, an ignored one disappears from both views. +`progress` is everything the reader says about an event themselves: + +| Field | Values | Meaning | +|---|---|---| +| `status` | `"doing"` \| `"done"` \| absent | Where they are with it | +| `effort` | `"quick"` \| `"short"` \| `"long"` \| `"grind"` \| absent | How much work they reckon it is | +| `note` | free text | Anything worth remembering | + +An entry with none of the three set is deleted rather than kept, so the store stays a set of things +the reader actually said something about. + +**`effort` is load-bearing, not decorative.** Combined with the time remaining it answers "can I +still finish this?" — the same two days is comfortable for a `quick` event and hopeless for a +`grind`. See `src/shared/effort.ts`; the runway heuristic assumes about an hour of play a day, is +stated as a guess in the UI, and never hides or reorders anything. + +**An event with no recorded effort never gets a warning.** Inferring an estimate in order to warn +about it would be fabricating the reader's own input. + +Ignores stay in a separate store because they mean something different: a done event is dimmed and +still counted, an ignored one disappears from both views. + +### Migration from `completions` + +`completions` used membership to mean "done", which cannot express "started". `progress` replaces it +and is seeded from it once, on first load, mapping each entry to `status: "done"`. + +**The old key is never written to and never deleted.** Someone who last opened the app six months +ago still has their marks under it, these live only in the browser, and nothing else holds a copy to +restore from. Exports produced before the change are still accepted on import and mapped forward the +same way. Offline caching is the service worker's job, not localStorage's — it caches the feed response itself, so there is no second copy of the events to keep in sync. @@ -221,7 +250,10 @@ old key. "format": "gacha-tracker-export", "version": 1, "exportedAt": "2026-08-14T12:00:00.000Z", - "completions": { "genshin:windblume-festival:2026-03-14": { "at": "..." } }, + "progress": { + "genshin:windblume-festival:2026-03-14": { "status": "done", "at": "..." }, + "hsr:garden-of-plenty:2026-08-14": { "status": "doing", "effort": "grind", "at": "..." } + }, "ignored": { "zzz:some-event-i-skip:2026-08-19": { "at": "..." } }, "prefs": { "region": "europe", "hiddenGames": [], "onboarded": true } } diff --git a/docs/PRD.md b/docs/PRD.md index 8634678..836282c 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -83,6 +83,19 @@ Because there are no accounts, moving between devices is manual: download a JSON IDs and preferences, upload it elsewhere. Import merges rather than replaces, and never removes a completion the user already has. +**F12 — Record your own progress and effort.** +Three states, not two: untouched, doing it, done. Plus an optional effort estimate — quick, short, +long, grind — and a free-text note. + +Effort is not decoration. Combined with the time remaining it answers the question the calendar +can't: *can I still finish this?* The same two days is comfortable for a quick event and hopeless +for a grind, so an event carrying an effort estimate gets a "tight" or "running out of time" flag +when the remaining time no longer covers it. + +The heuristic assumes about an hour of play a day and says so. It never hides or reorders anything — +it adds a flag the reader can ignore. **An event with no recorded effort never gets a warning**, +because inferring an estimate in order to warn about it would be fabricating their input. + **F8 — First-run game picker.** Before any events are shown, the reader picks which games they play. A calendar full of games they don't play is worse than an empty one — it buries the thing they came for. Nothing is preselected