docs: cover progress, effort and the completions migration

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-15 01:49:53 +02:00
co-authored by Claude Opus 5
parent 89df140c00
commit 2b9338a8b0
3 changed files with 53 additions and 7 deletions
+2 -1
View File
@@ -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
+38 -6
View File
@@ -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 }
}
+13
View File
@@ -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