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:
1 parent
45f24cf0cf
commit
54d8424d1e
6 files changed
+65
-6
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user