From 54d8424d1e24ab2ac70cab821777776d015829cf Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Tue, 13 Oct 2026 13:50:08 +0200 Subject: [PATCH] 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 --- AGENTS.md | 6 ++++- docs/DATA-MODEL.md | 27 ++++++++++++++++--- docs/DESIGN.md | 10 ++++++- docs/PRD.md | 22 +++++++++++++++ .../2026-08-27-recurring-events-design.md | 4 +++ .../specs/2026-10-13-periodic-tasks-design.md | 2 +- 6 files changed, 65 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b89b4b4..dd4614d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/docs/DATA-MODEL.md b/docs/DATA-MODEL.md index 8a6b079..3e165c3 100644 --- a/docs/DATA-MODEL.md +++ b/docs/DATA-MODEL.md @@ -472,6 +472,8 @@ ID spaces, and the reason is the one this document keeps making: ``` mygame: a game the reader defined "mygame:limbus-company" myevent: an event the reader entered "myevent:k3f9qa2m" +mytask: a weekly or monthly task "mytask:p0q8n3x2ab" +routine:: 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. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 4b889c8..1d33d3f 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -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. diff --git a/docs/PRD.md b/docs/PRD.md index f038c89..a7e888b 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -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 diff --git a/docs/superpowers/specs/2026-08-27-recurring-events-design.md b/docs/superpowers/specs/2026-08-27-recurring-events-design.md index eedfb41..bd1020a 100644 --- a/docs/superpowers/specs/2026-08-27-recurring-events-design.md +++ b/docs/superpowers/specs/2026-08-27-recurring-events-design.md @@ -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 diff --git a/docs/superpowers/specs/2026-10-13-periodic-tasks-design.md b/docs/superpowers/specs/2026-10-13-periodic-tasks-design.md index f809978..ae71e4a 100644 --- a/docs/superpowers/specs/2026-10-13-periodic-tasks-design.md +++ b/docs/superpowers/specs/2026-10-13-periodic-tasks-design.md @@ -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