docs: record check-in goals

The product, storage and design records now say what a goal is, why it
never marks an event done, and why an unreadable goal is kept rather than
stripped. That last rule is the one a future cleanup would most likely
undo by mistake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas Winther
2026-10-13 13:18:00 +02:00
co-authored by Claude Opus 5.5
parent 3c668ab5c1
commit d5ffad38c5
5 changed files with 47 additions and 5 deletions
+4
View File
@@ -952,6 +952,10 @@ event's length. It adds **no schema field**, so the feed contract is untouched.
on or off. Store an override only when it *disagrees* with detection —
recording agreement would freeze today's guess and stop a better parser from ever reaching that
event. Neither control ever deletes a mark or a logged day, so both are reversible.
- **A check-in goal makes an event a daily, and meeting it never makes the event done.** Some events
want five days of seven (`progress.goal`, `src/shared/goal.ts`, PRD F12). A met week stays in the
strip as done this week without a tick being invented; only the reader marks the event done. A
goal this build cannot read is kept in storage and treated as none — never stripped on load.
## Events the reader entered themselves
+21 -4
View File
@@ -214,7 +214,7 @@ 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:progress" // { [eventId]: { status?, effort?, note?, at } }
"gacha-tracker:v1:progress" // { [eventId]: { status?, effort?, daily?, note?, goal?, 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[], knownGames[]?, gameOrder[]?,
@@ -280,9 +280,19 @@ Namespaced, versioned, and small. Nothing here ever goes to the server.
| `effort` | `"quick"` \| `"short"` \| `"long"` \| `"grind"` \| absent | How much work they reckon it is |
| `note` | free text | Anything worth remembering |
| `daily` | `true` \| `false` \| absent | Whether it repeats daily, overruling detection |
| `goal` | `{ period: "week", measure: "days", required: 1–7, weekStartsOn: 1–7 }` \| `{ period: "event", measure: "days", required }` \| absent | A check-in goal: N distinct game-days per week or across the event (PRD F12) |
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.
An entry with none of these set is deleted rather than kept, so the store stays a set of things the
reader actually said something about. `isEmpty` in `useProgress.ts` must name every field: one left
out is a record marked only with that field, silently dropped on the next write.
**`goal` is read where it is used and never stripped.** It is stored typed `unknown` and read through
`readGoal` (`src/shared/goal.ts`), which returns undefined for anything that does not parse. Nothing
removes an unreadable goal from the record — the likeliest one is a newer shape (weekly tasks add
`measure: "amount"`) met by an older cached bundle, and dropping it on load would delete it on the
next write. A goal makes the event a daily regardless of `daily` and detection; setting one clears a
stored `daily: false`, and any `daily` answer clears the goal, so the two never contradict in
storage.
**`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
@@ -324,6 +334,12 @@ counting toward `streakOf` and `dailySummary`, and is simply not drawn — the s
outside the window the feed now claims. Nothing prunes the log against either window, because a
fortnight's streak exists on that device and nowhere else.
**A check-in goal counts days in this same log; it stores no days of its own.** `goalStatus`
recomputes from `daily` on every read, so a backfill, a removed tick or a week rolling over needs no
bookkeeping and clears nothing. Weeks are cut in day-key space by `periodOf`
(`src/shared/period.ts`): the seven keys from the chosen weekday, so a week rolls exactly where the
game's day does, per region and per reset override. A day key later than today never counts.
**Not every game has a server per region.** `GameMeta.resetOffsets` records the regions where a
game's server clock differs from `REGION_RESET_UTC_OFFSET`. Endfield is the case this exists for: it
has two server groups rather than three, and Europe is served off the Americas machine on a fixed
@@ -596,7 +612,8 @@ the earlier copy there discards every edit made after it, in both directions an
restoring a backup taken before an evening's work would roll that evening back, and importing an old
file onto a device with newer progress would roll the device back. Taking the maximum of the two
timestamps keeps the merge order-independent and idempotent either way, and removes nothing.
`mergeProgress` is pure and `test/progress.test.ts` pins it. An export written before daily checklists existed
`mergeProgress` is pure and `test/progress.test.ts` pins it. Check-in goals ride `progress`, so they need no export key of their own
and merge with the record they belong to. An export written before daily checklists existed
simply has no `daily` key, which is not an error. Losing a user's marks to a bad import is unrecoverable, so the merge is
deliberately one-directional. A file whose `format` is unrecognised is refused outright rather than
partly applied.
+7
View File
@@ -404,6 +404,13 @@ selected tab fills with `raised` and turns ink; unselected tabs are faint and go
- **State:** `aria-pressed` carries the state. On hover a chip lifts 1px and gains a 1px hue ring,
and an unpressed chip's label goes to ink. On press it scales to 0.96. A chip with nothing waiting
sits at 50% opacity: still reachable, but not competing for attention.
- **Goal met, not ticked today** (a dailies chip whose event has a check-in goal): full-strength hue
border, label and check — the check drawn dashed — on the *outstanding* ground, with no wash. The
wash belongs to a real tick only, so "done this week" never reads as "done today". A goal chip's
count is the goal's (`3/5 wk`) and replaces the streak; one counter per pill.
- **Check-in goal control** (detail sheet, under the daily checklist): the status pills' style —
Off / Each week / Across the event — then a small number field and, for weeks, a weekday select,
both on `ground` with a hairline border like the notes field.
### Event row (signature)
The unit of the checklist. It is one full-bleed target that opens the event and does nothing else.
+14
View File
@@ -297,6 +297,20 @@ Three rules hold it honest:
logged, keeps counting toward the streak and the totals, and is simply off-screen. Nothing removes a
day the reader did not remove themselves.
**Some events want a number of days, not every day.** Genshin's “To Temper Thyself and Journey
Far” asks for five check-ins out of seven each week. Ticking it as a daily calls two unticked days a
failure and keeps it outstanding when the week is already done. So a repeating event can carry a
**check-in goal**: N days each week (weeks starting on a weekday the reader picks, at the game's own
reset), or N days across the whole event. It is always the reader's input — nothing is read off a
title — and a goal makes an event a daily even with detection off.
- **A met week stays in the strip** as done this week, counted complete in the header, and comes
back when the next week opens. No tick is invented for it, and only a real tick celebrates.
- **Meeting a goal never marks the event done.** A count may not have claimed the rewards. An
event-wide goal reached offers *Mark event done*; the reader decides.
- **The target is never lowered.** A short first or last week keeps it and says when too few days
are left; an unknown end leaves the days left unknown.
**F13 — Your own games and your own events.**
No feasible adapter set covers everyone. Fourteen games were named in the first release thread and
the reader with the largest collection asked for exactly one thing — *"can you add a custom game
@@ -2,7 +2,7 @@
**Date:** 2026-10-03, revised and approved 2026-10-13
**Status:** approved; spec 1 of 2. Spec 2 is
**Status:** implemented 2026-10-13; spec 1 of 2. Spec 2 is
`2026-10-13-periodic-tasks-design.md`, which reuses the period engine defined here.
## Request and intended outcome