docs: settle check-in goals and specify weekly and monthly tasks
The goals draft stopped at three open questions; the owner answered them, and widened the scope to weekly and monthly chores with numeric targets. Both share one period engine, so they are specified together and built goals-first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
84ea00c059
commit
1deb0cb4cb
@@ -1,152 +1,204 @@
|
||||
# Event check-in goals
|
||||
|
||||
**Date:** 2026-10-03
|
||||
**Date:** 2026-10-03, revised and approved 2026-10-13
|
||||
|
||||
**Status:** proposed; not implemented
|
||||
**Status:** approved; 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
|
||||
|
||||
Some events require a fixed number of check-ins rather than participation every day.
|
||||
The owner's example is Genshin's “To Temper Thyself and Journey Far”: five completions
|
||||
out of seven days in a week. This example is the requested use case, not a verified
|
||||
source schedule or a rule to hardcode by title.
|
||||
out of seven days in a week. The event must stay in Dailies while unfinished, show how
|
||||
many check-ins the week needs, and distinguish meeting this week's requirement from
|
||||
finishing the event.
|
||||
|
||||
An unfinished event must remain available in Dailies. The tracker should show how many
|
||||
check-ins are required, count the reader's recorded days, and distinguish meeting this
|
||||
week's requirement from completing the whole event.
|
||||
Checked against the feed: the event runs Monday 2026-08-10 to Monday 2026-11-02, twelve
|
||||
whole weeks, and Game8 describes it as “Daily and Weekly Training Goals”. Genshin's week
|
||||
rolls on Monday at the server's daily reset. So weeks that start on a chosen weekday at
|
||||
the game's reset fit it. The required count is not published in our sources, which is
|
||||
one reason a goal is reader input rather than parsed.
|
||||
|
||||
## Current behavior and the gap
|
||||
## Decisions taken
|
||||
|
||||
`src/shared/daily.ts` detects whether an event repeats and counts recorded game-days,
|
||||
remaining opportunities, missed days and streaks. It has no required-count or weekly
|
||||
goal. `DailyChecklist.tsx` reports recorded days against the event's whole window,
|
||||
which cannot express “five of seven this week.”
|
||||
|
||||
`progress.daily` already lets the reader put an event in Dailies when source wording
|
||||
does not identify it. `App.tsx` supplies live, outstanding repeating events to
|
||||
`Dailies.tsx`; explicitly done or ignored events are excluded. A weekly goal must fit
|
||||
this flow without treating a satisfied week as a permanent event completion.
|
||||
|
||||
## Proposed scope
|
||||
|
||||
Let the reader configure an optional check-in goal in the event detail sheet:
|
||||
|
||||
- **Across this event:** a required number of distinct game-days in the event window.
|
||||
- **Each week:** a required number of distinct game-days in each seven-day period,
|
||||
with an explicitly chosen week-start day.
|
||||
|
||||
The weekly mode covers the reported example. The event-wide mode covers login campaigns
|
||||
that require a smaller number of days than the full time available. Neither mode
|
||||
requires consecutive days; streaks remain a separate fact.
|
||||
|
||||
Setting a goal makes the event eligible for Dailies even with experimental detection
|
||||
off. Removing the goal returns to the existing detection and override behavior. The
|
||||
existing “this isn't a daily event” action must offer to remove the goal too, rather
|
||||
than leave a conflicting configuration hidden in storage.
|
||||
|
||||
Goals are reader input in this first version. No parser changes, inferred counts,
|
||||
title-specific exceptions, or changes to `GachaEvent` are proposed.
|
||||
|
||||
## Counting and period boundaries
|
||||
|
||||
Count at most one check-in per game-day, using the existing recorded day keys and the
|
||||
event's game and selected server region. All calculations take `now` explicitly.
|
||||
Future days never count toward a goal.
|
||||
|
||||
A weekly period starts on the chosen weekday at the game's server reset, not browser
|
||||
midnight or UTC midnight. Periods are seven game-days, intersected with the event's
|
||||
actual start and end. An event-wide goal uses the event's resolved window, including
|
||||
the existing handling of day-precision dates and region-specific ends.
|
||||
|
||||
Ticks outside the applicable window do not satisfy its goal, but remain stored and
|
||||
editable through the existing history controls. Catch-up and corrections recalculate
|
||||
the affected period; no week transition clears, rewrites or prunes recorded days.
|
||||
|
||||
A shortened first or last week retains the stated target. If fewer opportunities
|
||||
remain than check-ins needed, show that fact without lowering the target or inventing
|
||||
extra days. An unknown event end remains unknown; a configured weekly rule can still
|
||||
define the current week's boundary without claiming an event end.
|
||||
|
||||
## Dailies and completion behavior
|
||||
|
||||
| State | Dailies behavior |
|
||||
| Question | Answer |
|
||||
|---|---|
|
||||
| Weekly goal unfinished | Keep the event visible; show, for example, `3 / 5 this week` and today's tick |
|
||||
| Weekly goal met | Keep the unfinished event visible as `5 / 5 · Done this week`; stop presenting further check-ins as required |
|
||||
| Next week opens | Recalculate against the new period and offer today's check-in again |
|
||||
| Event-wide goal met | Show `5 / 5 · Goal reached`, with an explicit action to mark the event done |
|
||||
| Event marked done or ignored | Exclude it from Dailies under the existing outstanding rule |
|
||||
| Event expired | Follow the existing live-event rule; retain its goal and recorded history |
|
||||
| Who sets a goal? | The reader, per event, in the detail sheet. Nothing is inferred from a title or source |
|
||||
| Modes | **Each week**: N distinct game-days per week. **Across the event**: N distinct game-days in the whole window |
|
||||
| Week boundary | A chosen ISO weekday, at the game's server reset for the reader's region. Defaults to the weekday of the event's first game-day |
|
||||
| Numeric targets (`1900 / 2100`) | The goal carries `measure` from day one. This spec ships `measure: "days"` only; spec 2 adds `"amount"` and its store |
|
||||
| Does meeting a goal finish the event? | Never. Only the reader marks an event done |
|
||||
| Does a met week stay in Dailies? | Yes, as done-this-week, until the reader marks the event done |
|
||||
| `GachaEvent`, IDs, day-log keys, storage keys | Unchanged |
|
||||
|
||||
A goal never writes `status: "done"` automatically. Reaching a check-in count might
|
||||
not finish other objectives or claim the rewards. Only the reader's explicit event
|
||||
completion removes a live event from Dailies. Undoing completion brings it back with
|
||||
its goal and ticks intact.
|
||||
## The period engine — `src/shared/period.ts`
|
||||
|
||||
The detail checklist keeps past days editable, including after a target is reached.
|
||||
Undoing a tick can make a goal unfinished again. For goals, describe remaining
|
||||
opportunities and check-ins needed; do not label every unticked past day as a failure
|
||||
when participation on every day was never required.
|
||||
Pure, takes `now` explicitly, and works in **day-key space** like `streakOf` and
|
||||
`catchUpDays`: every day is a `YYYY-MM-DD` key already cut on the game's reset clock by
|
||||
`dayKey`, so period boundaries inherit each game's per-region reset with no new clock.
|
||||
|
||||
Dailies' daily completion count and celebration must treat a satisfied goal as having
|
||||
no further work required today. This must not fabricate a tick for today. A real tick
|
||||
and a satisfied period remain separate states in accessible labels and rendering.
|
||||
```ts
|
||||
type Period =
|
||||
| { unit: "week"; startsOn: 1 | 2 | 3 | 4 | 5 | 6 | 7 } // ISO weekday
|
||||
| { unit: "month"; startsOn: number }; // 1–31, used by spec 2
|
||||
|
||||
## Storage and implementation boundaries
|
||||
/** The period containing `today`, as its first key and its day keys, oldest first. */
|
||||
periodOf(period: Period, today: string): { start: string; days: string[] };
|
||||
```
|
||||
|
||||
Store an optional goal alongside the reader's per-event progress. The proposed shape
|
||||
is a discriminated Zod schema with a positive integer `required` and either
|
||||
`period: "event"` or `period: "week"` plus `weekStartsOn` (ISO weekday 1–7). Weekly
|
||||
targets are limited to 1–7; event-wide targets must not exceed a known opportunity
|
||||
count at configuration time. Existing goals remain readable if source dates later
|
||||
change, with any resulting shortfall disclosed rather than silently discarded.
|
||||
- **Week:** the ISO weekday of a key is its UTC weekday (keys are dates, not instants).
|
||||
Step back `(weekday − startsOn + 7) % 7` days from `today`; take seven keys.
|
||||
- **Month** (spec 2's consumer, built and tested here because it is the same function):
|
||||
the most recent key whose day-of-month is `startsOn`, clamped to the month's last day
|
||||
when the month is shorter (31 → 30 Sep, 28/29 Feb). The period runs to the day before
|
||||
the next start.
|
||||
- Callers get `today` from `dayKey(now, region, game)`, so “immediately before reset”
|
||||
and “at reset” fall in different periods exactly where the game's day rolls.
|
||||
|
||||
Missing goal means today's behavior. No existing event ID, daily-log key, game-day
|
||||
format, reset mapping or localStorage key changes. Preserve a progress record that
|
||||
contains only a goal; include the new field in emptiness checks and export/import.
|
||||
Import follows the existing last-edited progress merge and daily-log union rules.
|
||||
Validate the goal without deleting unrelated status, notes or recorded ticks when
|
||||
an imported goal is invalid.
|
||||
## Goals — `src/shared/goal.ts`
|
||||
|
||||
Keep goal calculations pure in shared code. Wire the result into `App.tsx`,
|
||||
`Dailies.tsx`, `DailyChecklist.tsx`, and `EventDetail.tsx`; extend the progress store
|
||||
and backup validation. Reuse the shipped visual tokens and existing detail controls.
|
||||
Custom event occurrence IDs keep their existing meaning; a goal applies to the
|
||||
selected occurrence unless applying it to an entire recurring rule is separately
|
||||
specified.
|
||||
Zod is the source of truth for the type:
|
||||
|
||||
```ts
|
||||
// A plain union, not discriminatedUnion("period"): spec 2 adds amount variants that
|
||||
// share a period with these, so no single field discriminates.
|
||||
const GoalSchema = z.union([
|
||||
z.object({ period: z.literal("week"), measure: z.literal("days"),
|
||||
required: z.number().int().min(1).max(7),
|
||||
weekStartsOn: z.number().int().min(1).max(7) }),
|
||||
z.object({ period: z.literal("event"), measure: z.literal("days"),
|
||||
required: z.number().int().min(1).max(366) }),
|
||||
]);
|
||||
type Goal = z.infer<typeof GoalSchema>;
|
||||
|
||||
/** A stored value as a Goal, or undefined when absent or unreadable. Never throws. */
|
||||
readGoal(stored: unknown): Goal | undefined;
|
||||
|
||||
goalStatus(input: {
|
||||
goal: Goal; startsMs: number; endsMs: number | null;
|
||||
region: Region; game: LaneId; now: number; logged: readonly string[];
|
||||
}): GoalStatus;
|
||||
|
||||
interface GoalStatus {
|
||||
scope: "week" | "event";
|
||||
required: number;
|
||||
count: number; // distinct ticked keys in scope, never later than today
|
||||
met: boolean; // count >= required
|
||||
needed: number; // max(0, required - count)
|
||||
open: number | null; // unticked days in scope from today on; null when unknowable
|
||||
shortfall: boolean; // open !== null && needed > open
|
||||
}
|
||||
```
|
||||
|
||||
Counting rules:
|
||||
|
||||
- **Scope.** Week: `periodOf` for today, intersected with the event's days — `dailyDays`
|
||||
when the end is known, otherwise every key from the event's first game-day. Event:
|
||||
`dailyDays`; with an unknown end, every key from the first game-day to today, and
|
||||
`open` is null rather than a guess.
|
||||
- **One per game-day**, from the existing day log. A key later than today never counts.
|
||||
A tick outside scope never counts and is never removed.
|
||||
- **A short first or last week keeps its target.** If too few days remain, `shortfall`
|
||||
says so; the target is not lowered and no day is invented.
|
||||
- Catch-up and corrections need no special handling: status is recomputed from the log on
|
||||
every render, so backfilling a previous week cannot move this week's count.
|
||||
|
||||
**Validated where used, never stripped from storage.** `readGoal` returns undefined for a
|
||||
missing or unreadable goal and the record is left exactly as stored. Dropping an invalid
|
||||
field on load would delete it from the device on the next write — the hazard AGENTS.md
|
||||
§ Events the reader entered themselves describes — and the likeliest unreadable goal is a
|
||||
newer shape (spec 2's `amount`) read by an older cached bundle. Setting a goal in the UI
|
||||
overwrites whatever was there.
|
||||
|
||||
## Storage
|
||||
|
||||
`Progress` gains `goal?: unknown` in storage, read through `readGoal`. Concretely:
|
||||
|
||||
- `isEmpty` treats a present `goal` as recorded, so a goal-only record survives.
|
||||
- Export and import need no new key: goals ride `progress`, and `mergeProgress` already
|
||||
keeps the later-edited record whole. Old backups lack the field and remain valid.
|
||||
- No ID, day-log key, game-day format, reset mapping or localStorage key changes.
|
||||
|
||||
## Dailies eligibility
|
||||
|
||||
- An event whose `readGoal` is defined is a daily, whatever detection or `progress.daily`
|
||||
says. App computes `daily = goal !== undefined || resolveDaily(...)`.
|
||||
- **Setting a goal** clears a stored `daily: false` (the two would contradict) and leaves
|
||||
any other `daily` value alone, so removing the goal later restores exactly the earlier
|
||||
detection and override behaviour.
|
||||
- **“This isn't a daily event”** clears the goal in the same patch, and its confirmation
|
||||
copy says so when a goal is set. No conflicting state is left hidden in storage.
|
||||
- `outstanding` is unchanged: done or ignored events leave the strip regardless of display
|
||||
preferences; undoing either restores the event with its goal and ticks.
|
||||
|
||||
## What the reader sees
|
||||
|
||||
**Detail sheet — “Check-in goal”** under the daily checklist, and offered on non-daily
|
||||
events too (setting one makes the event a daily). Three choices in the style of the
|
||||
existing status and effort controls: **Off** · **Each week** `[5] days, weeks start
|
||||
[Monday]` · **Across the event** `[5] days`. Weekly `required` is 1–7; event-wide is
|
||||
capped at the known number of days when the end is known.
|
||||
|
||||
**Checklist caption** when a goal is set replaces the “missed” wording, because an
|
||||
unticked past day is not a failure when every day was never required:
|
||||
|
||||
- Weekly: `3 of 5 this week · 2 needed · 4 days left this week`
|
||||
- Event-wide: `3 of 5 · 2 needed · 9 days left`, or `3 of 5 · 2 needed` with an unknown end
|
||||
- Shortfall: `2 needed, 1 day left this week`
|
||||
- Met: `5 of 5 · Done this week` / `5 of 5 · Goal reached`. An event-wide goal reached
|
||||
shows **Mark event done** beside it, which uses the existing status control.
|
||||
|
||||
Past days stay editable after a target is reached; removing a tick can unmeet it.
|
||||
|
||||
**Dailies chip.** Gains a short count: `3/5 wk` or `3/5`. Tapping still ticks today.
|
||||
Three visually distinct states:
|
||||
|
||||
| State | Look | Accessible label suffix |
|
||||
|---|---|---|
|
||||
| Not done today, goal unmet | Outstanding chip as now, plus count | `, not done today, 3 of 5 this week` |
|
||||
| Goal met, not ticked today | Full-strength hue border and check, outstanding background | `, done this week` / `, goal reached` |
|
||||
| Ticked today | Done chip as now, plus count | `, done today` |
|
||||
|
||||
**Header count and celebration.** In `Today's dailies · n/total`, an item is complete when
|
||||
ticked today **or** its goal is met — no tick is fabricated. The burst fires only when the
|
||||
number of items **ticked today** rises and everything is complete, so meeting a week by
|
||||
backfilling an earlier day completes the strip quietly, as backfilling does today.
|
||||
|
||||
## Testing
|
||||
|
||||
- `period.ts`: week starts for every `startsOn`, across month and year ends; month starts
|
||||
with clamping; Genshin keys immediately before and at reset for Asia, America and
|
||||
Europe, and a game with a reset override (`resetHourLocal`).
|
||||
- `goal.ts`: acceptance cases 1–7 below as pure tests, including future ticks, ticks
|
||||
outside scope, partial first and last weeks, shortfall and unknown ends; `readGoal` on
|
||||
missing, malformed and future-shaped values.
|
||||
- Progress: goal-only records survive `patch`; `mergeProgress` keeps the later record's
|
||||
goal; export → import round trip keeps goals; an old export imports unchanged.
|
||||
- Dailies: `dailyGroups`/item completion with goals; the burst rule; labels.
|
||||
- Full offline suite and typecheck; inspect the built page in headless Chrome.
|
||||
|
||||
## Acceptance checks
|
||||
|
||||
1. A configured five-per-week event appears in Dailies with detection off and no
|
||||
explicit completion. At three distinct ticks it reads `3 / 5 this week`.
|
||||
2. Five nonconsecutive days satisfy the week. The event remains visible, labelled
|
||||
done this week, without being marked done or requesting a sixth check-in.
|
||||
3. The next weekly boundary makes the goal available again; previous ticks remain
|
||||
unchanged. Test immediately before and at reset for each supported region.
|
||||
4. Backfilling a previous week never advances this week's count. Removing a current
|
||||
tick below the target restores the unfinished state.
|
||||
5. A five-check-in event-wide goal in a seven-day window reaches its target on the
|
||||
fifth recorded day and waits for explicit event completion.
|
||||
6. Done and ignored events remain excluded regardless of list-display preferences;
|
||||
undoing either restores the recorded goal and days while the event is live.
|
||||
7. Partial weeks, unknown ends, future ticks, region-specific boundaries and games
|
||||
with reset overrides count correctly without invented dates or deleted history.
|
||||
8. Goal-only progress survives reload and export/import. Old backups remain valid;
|
||||
invalid goals do not erase otherwise valid reader data.
|
||||
9. Events with no goal retain current daily behavior, grouping, order and catch-up.
|
||||
10. Accessible labels distinguish done today, done this week, and event completion.
|
||||
1. A five-per-week event with detection off appears in Dailies; three distinct ticks read
|
||||
`3/5 wk`.
|
||||
2. Five non-consecutive days meet the week: still visible, labelled done this week, not
|
||||
marked done, counted complete in the header.
|
||||
3. The next week boundary offers the event again; earlier ticks are unchanged.
|
||||
4. Backfilling a previous week never moves this week's count; removing a current tick
|
||||
below target restores the unmet state.
|
||||
5. A five-day event-wide goal in a seven-day window is reached on the fifth day and waits
|
||||
for an explicit Mark event done.
|
||||
6. Done and ignored events stay excluded; undoing either restores goal and ticks.
|
||||
7. Partial weeks, unknown ends, future ticks, regional resets and reset overrides count
|
||||
correctly with no invented date and no deleted history.
|
||||
8. Goal-only progress survives reload and export/import; old backups import; an
|
||||
unreadable goal leaves the record intact and behaves as no goal.
|
||||
9. Events with no goal behave exactly as before.
|
||||
10. Accessible labels distinguish done today, done this week and goal reached.
|
||||
|
||||
Before shipping, update PRD F12 and the data-model sections for progress, daily
|
||||
checklists and export/import. Update the design record for any new visible controls.
|
||||
Run relevant unit tests, the full offline suite and typecheck, and inspect the built
|
||||
interface in a browser.
|
||||
## Docs that change with it
|
||||
|
||||
## Decisions to confirm before implementation
|
||||
|
||||
- Verify the example's actual weekly reset and whether its requirement is per week
|
||||
or per another in-game training period. Do not infer this from the event title.
|
||||
- Confirm that choosing a week-start weekday covers the desired games. If a source
|
||||
resets on another clock or uses event-anchored seven-day periods, specify that
|
||||
boundary explicitly before supporting it.
|
||||
- Confirm the proposed event-wide mode and the choice to retain satisfied goals in
|
||||
Dailies until the reader marks the whole event done.
|
||||
PRD F12 (check-in goals paragraph); DATA-MODEL § Client-side storage `progress` table and
|
||||
key comment, § Daily checklists, § Export format; AGENTS.md § Events that repeat daily (a
|
||||
goal makes an event a daily; meeting one never marks it done); DESIGN.md for the
|
||||
goal-met chip state and the goal control.
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# Weekly and monthly tasks, with numeric targets
|
||||
|
||||
**Date:** 2026-10-13
|
||||
|
||||
**Status:** approved; 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
|
||||
|
||||
Games have chores that roll weekly or monthly rather than daily — earning ZZZ's weekly
|
||||
activity points, completing Endfield's weekly routine. The reader wants to set these up
|
||||
per game, see them beside the dailies, and track either a plain “done this period” or a
|
||||
number against a target: `1900 / 2100`.
|
||||
|
||||
The owner's direction on where they come from: **reader-authored as the base**; the app
|
||||
can also ship known tasks per game; when the reader turns a game on, the app asks whether
|
||||
to use that game's automatic weekly and monthly tasks, and a toggle governs it; and a
|
||||
built-in task can be copied into the reader's own tasks to edit.
|
||||
|
||||
The recurring-events spec (2026-08-27) deferred “weekly task lists” to its Phase C. This
|
||||
is that phase for the reader's side; it adds no `GachaEvent` field and nothing to ingest.
|
||||
|
||||
## Decisions taken
|
||||
|
||||
| Question | Answer |
|
||||
|---|---|
|
||||
| What is a task? | A per-game chore with a period (week or month) and a measure (days or amount). Not an event: no window, no end, no status |
|
||||
| Measures | `days`: N distinct game-days ticked in the period (N = 1 is “done this period”). `amount`: a number the reader enters against a `target` |
|
||||
| Where amounts live | A new store, one value per item per period |
|
||||
| Where ticks live | The existing day log, keyed by the task id — union merge and export already work |
|
||||
| Reader-authored ids | `mytask:<random>` |
|
||||
| Built-in ids | `routine:<gameId>:<slug>` |
|
||||
| Built-ins on by default? | No. Per-game opt-in, asked once when the game is turned on, and a toggle in settings |
|
||||
| Editing a built-in | Not in place. “Make it mine” copies it to a reader task and hides the original |
|
||||
| Amount goals on events | Yes: spec 1's goal gains `measure: "amount"` variants, using the same store and editor |
|
||||
| Phasing | **2a** reader tasks, amounts, amount goals. **2b** built-in routines, opt-in prompt and toggle, make-it-mine |
|
||||
|
||||
## Model
|
||||
|
||||
`src/shared/task.ts`, Zod as source of truth:
|
||||
|
||||
```ts
|
||||
const MeasureSchema = z.union([
|
||||
z.object({ kind: z.literal("days"), required: z.number().int().min(1).max(31) }),
|
||||
z.object({ kind: z.literal("amount"), target: z.number().positive(),
|
||||
unit: z.string().max(24).optional() }), // "points", "activity"
|
||||
]);
|
||||
const TaskSchema = z.object({
|
||||
id: z.string(), // mytask:<random>
|
||||
game: z.string(), // LaneId; z.string() for the retirement rule
|
||||
title: z.string().min(1).max(80),
|
||||
period: PeriodSchema, // from period.ts: week+startsOn | month+startsOn
|
||||
measure: MeasureSchema,
|
||||
from: z.string().optional(), // routine id this was copied from (2b)
|
||||
at: z.string(), updatedAt: z.string(),
|
||||
});
|
||||
```
|
||||
|
||||
`game` is `z.string()`, not `GameId`, for the reason `CustomEvent.game` is: narrowing it
|
||||
arms every future game removal to delete reader rows on load. `days.required` is capped by
|
||||
the period's length when edited (7 for a week).
|
||||
|
||||
`taskStatus({ task, region, now, logged, amounts })` returns `{ periodStart, count?,
|
||||
value?, met, needed }` from `periodOf` and the stores. Pure; `now` explicit.
|
||||
|
||||
Spec 1's `GoalSchema` gains two variants: `{ period: "week", measure: "amount", target,
|
||||
weekStartsOn }` and `{ period: "event", measure: "amount", target }`. An event-scoped
|
||||
amount uses the event's first game-day as its period key.
|
||||
|
||||
## Storage
|
||||
|
||||
| Key | Shape | Merge on import |
|
||||
|---|---|---|
|
||||
| `gacha-tracker:v1:tasks` | `{ [mytask id]: Task }` | By id, later `updatedAt` wins; never removes |
|
||||
| `gacha-tracker:v1:amounts` | `{ [itemKey]: { [periodStartKey]: { value, at } } }` | Per item **and** period, later `at` wins; never removes |
|
||||
| `daily` (existing) | task ticks under the task id | Existing union of days |
|
||||
| `prefs.routineGames` (2b) | `string[]` — games whose built-ins are on | Prefs rules as today |
|
||||
| `prefs.routinesAsked` (2b) | `string[]` — games already asked about | Prefs rules as today |
|
||||
|
||||
- `itemKey` is a task id or an event id. A new period starts at no value; old periods are
|
||||
never pruned. Deleting a task removes the task record only — its ticks and amounts stay,
|
||||
as every log in this app does.
|
||||
- Loading reads through a `validRecords`-style filter **for display only**; a task that
|
||||
fails its schema is not shown but is not dropped from storage.
|
||||
- `tasks` and `amounts` are additive export keys: older exports lack them and import
|
||||
unchanged; `parseImportData` passes them through for validation by the stores.
|
||||
- `mytask` and `routine` become reserved first segments alongside `dailies`, `mygame`,
|
||||
`myevent`; the existing test that no reserved segment is a `GameId` covers them.
|
||||
|
||||
## What the reader sees
|
||||
|
||||
**Dailies strip.** Weekly and monthly items sit in their game's group, after that game's
|
||||
dailies, keeping the rule that a game's items stay together and that grouping never
|
||||
re-sorts.
|
||||
|
||||
- `days` chip: tap ticks today, like a daily. Label `Weekly boss · 1/1 wk`, or `3/5 wk`.
|
||||
- `amount` chip: `ZZZ activity · 1900/2100`, with a thin progress underline in the hue.
|
||||
A chip is one target, so tapping it **opens an inline editor under the strip** —
|
||||
`Activity points this week: [1900] / 2100 · Save` — rather than ticking. Enter saves,
|
||||
Escape cancels; the value is where the reader is now, not an increment.
|
||||
- Met: the spec 1 met state, `2100/2100 · Done this week` / `· Done this month`.
|
||||
- Header: `Today's dailies · 3/7` keeps counting dailies only; weekly and monthly items get
|
||||
their own count beside it (`Weekly 1/2 · Monthly 0/1`, each shown only when present).
|
||||
The celebration stays about today's dailies.
|
||||
- Catch up: a `days` task shows its current and previous period's days as pips; an
|
||||
`amount` task shows last period's value as an editable field.
|
||||
- Accessible labels: `ZZZ activity points, 1900 of 2100 this week`, `…, done this week`.
|
||||
|
||||
**Amount goals on events.** The detail sheet's goal control gains “Points” beside days:
|
||||
`[2100] points each week` or `in total`; the checklist area shows the value and the same
|
||||
editor.
|
||||
|
||||
**Settings — “Your own”.** A tasks list beside your games and events: add, edit, delete.
|
||||
Fields: game, title, every week (start weekday) or every month (start day), and either
|
||||
“tick N days” or “reach a number” (target, optional unit). Weekly start defaults to
|
||||
Monday; monthly to the 1st.
|
||||
|
||||
### Phase 2b — built-in routines
|
||||
|
||||
- `src/shared/routines.ts`: per-game templates with the same fields plus `source` (URL)
|
||||
and `checked` (date). **Only a task a cited source documents ships**, the bar
|
||||
`dailyTasks` in `games.ts` is held to; the owner's examples (ZZZ weekly activity, 2100;
|
||||
Endfield weekly routine, 10) are the first candidates and are verified, not assumed.
|
||||
- **Asking.** When a game with built-ins becomes visible — first-run picker, or switched on
|
||||
in settings — and is not in `routinesAsked`, a one-line card at the top of the dailies
|
||||
strip asks: `Track Zenless Zone Zero's weekly and monthly tasks? Yes · No thanks`. Either
|
||||
answer records the game in `routinesAsked`; Yes also adds it to `routineGames`. Never a
|
||||
modal, and never asked twice.
|
||||
- **Toggle.** The games settings group shows a “Weekly tasks” switch on each game that has
|
||||
built-ins, writing `routineGames`. Off hides the chips; their ticks and amounts stay.
|
||||
- **Make it mine.** From a built-in chip's editor or the settings list: copies the template
|
||||
into a `mytask:` record with `from` set, and copies the built-in's ticks and amounts to
|
||||
the new id (additive; the originals stay). A built-in whose id is some task's `from` is
|
||||
hidden; deleting that copy brings it back.
|
||||
|
||||
## Testing
|
||||
|
||||
- `task.ts`: status for days and amounts, week and month, at period boundaries per region.
|
||||
- Stores: merge rules per table above; tasks failing schema are hidden, not dropped;
|
||||
export/import round trip including old exports.
|
||||
- Dailies: placement after a game's dailies, separate weekly/monthly count, editor
|
||||
save/cancel, no celebration from weekly items.
|
||||
- 2b: prompt asked once per game, toggle, make-it-mine copying and hiding, routine data has
|
||||
a source on every entry (a test).
|
||||
- Full suite, typecheck, browser inspection.
|
||||
|
||||
## Docs that change with it
|
||||
|
||||
PRD: a new feature entry for weekly and monthly tasks; DATA-MODEL: the two keys, the prefs
|
||||
fields, reserved segments and export keys; AGENTS.md § Events the reader entered
|
||||
themselves (tasks join the reader-authored spaces) and the dailies grouping rule; DESIGN.md
|
||||
for the amount chip and editor; the 2026-08-27 spec's Phase C note pointing here.
|
||||
Reference in New Issue
Block a user