docs: record weekly and monthly tasks

The product, storage and design records now cover F17. They say why the
task and amount stores, unlike the custom-event store, never filter on
load, which is the rule a future store would most likely copy wrongly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-13 13:50:08 +02:00
1 parent 45f24cf0cf
commit 54d8424d1e
6 files changed
+65 -6

No files matched your search

+5 -1
View File
@@ -15,7 +15,7 @@ between repairing a rule and rediscovering it the expensive way.
| Doc | What it settles | Read it before |
|---|---|---|
| `docs/PRD.md` | What the product is for, feature by feature (F1–F14), and the quality bar for dates | Changing behaviour a reader can see, or arguing something is out of scope |
| `docs/PRD.md` | What the product is for, feature by feature (F1–F17), and the quality bar for dates | Changing behaviour a reader can see, or arguing something is out of scope |
| `docs/DATA-MODEL.md` | `GachaEvent`, the SQLite tables, every `localStorage` key space, the export format | Touching `src/shared/schema.ts`, an ID scheme, a stored key, or the game/reset table |
| `docs/INGESTION.md` | The six pipeline stages, the parser/adapter/merge layering, date formats, the review gate | Adding a source, writing or repairing a parser, or changing the fetch runner |
| `docs/ARCHITECTURE.md` | Process shape, file layout, request paths, offline and update mechanics | Moving files, adding a route, or changing the service worker |
@@ -975,6 +975,10 @@ timeline, sort, filters, progress, ignore and daily stores as scraped events. Fo
the same reason the parsers are forbidden from guessing one.
- **They are in the export.** These exist in one browser and nowhere else, so an export without them
is a lossy backup. Import merges by id and never removes.
- **Weekly and monthly tasks are theirs too** (`mytask:`, `src/shared/task.ts`, PRD F17), and their
store is the one that does *not* filter on load: `useTasks` and `useAmounts` keep what is on disk
and validate where they read, so a task a newer version wrote survives an older tab. Copy that
pattern for any new reader store rather than `useCustom`'s `readValid`.
A lane may now be a game the reader invented, so `gameMeta` is a context resolver (`metaFor`, pure
and total) rather than a direct lookup — a lane can outlive its game when an import carries an event
+24 -3
View File
@@ -472,6 +472,8 @@ ID spaces, and the reason is the one this document keeps making:
```
mygame:<slug> a game the reader defined "mygame:limbus-company"
myevent:<random> an event the reader entered "myevent:k3f9qa2m"
mytask:<random> a weekly or monthly task "mytask:p0q8n3x2ab"
routine:<game>:<slug> a built-in task (held for phase 2b; nothing writes it yet)
```
**A reader's event is never minted as `${game}:${slug}:${date}`.** That space is derived from source
@@ -485,8 +487,8 @@ ID.** A feed event's ID follows its source title, so a wiki renaming an event mi
reconciliation has to recognise the near-match to preserve marks. A reader's event has no source to
follow, so its ID is assigned once at creation and never derived from anything they can edit.
Three first segments are now reserved and **none of them may ever become a `GameId`**: `dailies`,
`mygame`, `myevent`. A test asserts this against `GameId.options`, because the day that stops being
Five first segments are now reserved and **none of them may ever become a `GameId`**: `dailies`,
`mygame`, `myevent`, `mytask`, `routine`. A test asserts this against `GameId.options`, because the day that stops being
true is the day two key spaces merge silently.
```ts
@@ -494,8 +496,23 @@ true is the day two key spaces merge silently.
"gacha-tracker:v1:customEvents" // { [id]: { id, game, title, type, summary,
// startsAt, startPrecision,
// endsAt, endPrecision, at, updatedAt } }
"gacha-tracker:v1:tasks" // { [mytask id]: { id, game, title,
// period: { unit: "week", startsOn: 1–7 } | { unit: "month", startsOn: 1–31 },
// measure: { kind: "days", required } | { kind: "amount", target, unit? },
// from?, at, updatedAt } }
"gacha-tracker:v1:amounts" // { [itemKey]: { [periodStartKey]: { value, at } } }
```
**Tasks (PRD F17) are kept as stored and validated where they are shown.** Unlike `customEvents`,
whose load filter drops a record that fails its schema and so deletes it on the next write, the task
store keeps every record and `visibleTasks` filters for display: the likeliest unreadable task is
one a newer version wrote, and opening an older tab must not erase it. `amounts` is the same —
`readableAmounts` skips a malformed entry when reading and leaves it on disk. A task's ticks live in
`daily` under its id; its numbers live in `amounts` under its id and the start key of the period
(`periodOf`, `src/shared/period.ts`), so a new week starts with no value and old weeks are never
pruned. An event's points goal stores its numbers the same way, under the event id. Deleting a task
removes the task record only.
Reader-authored events reuse `progress`, `daily` and `ignored` unchanged, keyed by their `myevent:`
id — everything the reader can say about a scraped event they can say about their own, with no
second set of stores to keep in sync.
@@ -615,7 +632,11 @@ restoring a backup taken before an evening's work would roll that evening back,
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. 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
and merge with the record they belong to. `tasks` and `amounts` are additive export keys like
`customGames`: a file without them is from a device that had none. Tasks merge by id with the later
`updatedAt` winning (`mergeTasks`), incoming records validated and current ones kept as stored;
amounts merge per item **and per period** with the later `at` winning (`mergeAmounts`), so restoring
an old backup cannot roll this week's number back, and a week only one side has survives. 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.
+9 -1
View File
@@ -408,9 +408,17 @@ selected tab fills with `raised` and turns ink; unselected tabs are faint and go
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.
- **Amount chip** (a weekly or monthly task, or an event goal, that is a number): the tick chip's
pill with no check — `1,900/2,100 wk` in the count slot and a 2px hue line along the bottom
filling to the same fraction (60% opacity, full when met). It is not a toggle: no
`aria-pressed`, `aria-expanded` instead, and it opens the **amount editor** under the strip — a
label, a small numeric field on `ground`, `/ 2,100 points`, and a near-pill Save. Weekly and
monthly counts sit on their own faint line under the strip's heading, so the reset countdown
never wraps.
- **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.
both on `ground` with a hairline border like the notes field. Once a goal is set, a smaller
Days · Points pill pair picks its measure.
### Event row (signature)
The unit of the checklist. It is one full-bleed target that opens the event and does nothing else.
+22
View File
@@ -538,6 +538,28 @@ placed. It also arrives switched off (F8), so settings is where they meet it, wh
would move it anyway. **A game's dailies stay together** — a repeating event the reader marked sits
with the standing chore of the game it came from, because the reader thinks in games and not in kinds.
**F17 — Weekly and monthly tasks.**
Some chores roll weekly or monthly rather than daily — earning ZZZ's weekly activity points,
finishing Endfield's weekly routine — and no source publishes them, so they were nowhere in the app.
A reader can now set them up per game in *Your own*: a title, every week (from a weekday they pick)
or every month (from a day of the month), and either **tick it off** (N days in the period, one by
default) or **reach a number** (a target such as 2,100, with an optional unit).
- **They roll on the game's clock.** A week starts at the game's daily reset on the chosen day, on
the reader's server — the same clock every daily tick is cut on, so nothing new to get wrong.
- **They sit with their game's dailies, and are counted apart.** A task follows its game's chore and
events in the strip, but the header counts it under *Weekly* or *Monthly*, not "today": a chore
due by Sunday is not waiting on anyone every morning, and only today's dailies celebrate.
- **A number is entered, not ticked.** Its chip opens an editor under the strip — the value as the
game shows it, not an increment — because a chip is one target. Catch up offers last period's
number the way it offers yesterday's tick.
- **An event's check-in goal (F12) can be a number too**, entered in its detail sheet.
- **Theirs, kept and exported.** Deleting a task removes the task only; its ticks and numbers stay.
Both travel in the export, and import never rolls a newer week back.
Built-in tasks per game, offered when a game is switched on, are specified as the next phase
(`docs/superpowers/specs/2026-10-13-periodic-tasks-design.md` § Phase 2b) and are not built yet.
## Out of scope for v1
Accounts and sync; push notifications; in-game resource or pull tracking; native mobile apps (the
@@ -305,6 +305,10 @@ Open questions to settle before starting, not now:
## Phase C — battle passes and weekly task lists
*The reader's side of weekly task lists is specified and built in
`2026-10-13-periodic-tasks-design.md` (reader-authored tasks; built-in ones are its phase 2b). Battle
passes remain here.*
Deliberately last. Both are recurrences whose *content* changes each cycle even though the window
does not, and this app is explicitly "not a wiki" (`docs/PRD.md` § What this app is not) — it says
that something exists and when it ends, not what is in it. So the window is in scope and the reward
@@ -2,7 +2,7 @@
**Date:** 2026-10-13
**Status:** approved; spec 2 of 2. Builds on `2026-10-03-check-in-goals-design.md` (the
**Status:** phase 2a implemented 2026-10-13; phase 2b not started. Spec 2 of 2. Builds on `2026-10-03-check-in-goals-design.md` (the
period engine and goals) and ships after it, in two phases.
## Request and intended outcome