docs: record per-game switches for chores and built-ins
The data model, PRD F17, README and DESIGN described one global chores switch and a checkbox per game for built-ins. Adds the spec and plan, and ticks the TODO item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a39c6a8398
commit
094e67d985
7 files changed
+257
-11
No files matched your search
@@ -197,7 +197,8 @@ No wiki publishes those, so they are a fixed list in the app rather than scraped
|
|||||||
the only thing on the page that expires tonight rather than next patch.
|
the only thing on the page that expires tonight rather than next patch.
|
||||||
|
|
||||||
Because nobody publishes them, they are the app's guess at your routine rather than anything you
|
Because nobody publishes them, they are the app's guess at your routine rather than anything you
|
||||||
asked for — so **Show each game's own daily chores** in the settings turns them off. Only the
|
asked for — so **Show each game's own daily chores** in the settings turns them off, for every game
|
||||||
|
or, under *Choose games*, for only the ones you pick. Only the
|
||||||
invented chores go: anything with a checklist is something you engaged with, including events you
|
invented chores go: anything with a checklist is something you engaged with, including events you
|
||||||
added yourself and marked daily, and those stay where they are. Nothing is discarded, so every tick
|
added yourself and marked daily, and those stay where they are. Nothing is discarded, so every tick
|
||||||
and streak is waiting if you turn it back on.
|
and streak is waiting if you turn it back on.
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
(for example, five check-ins per week). Keep unfinished events in Dailies, show progress
|
(for example, five check-ins per week). Keep unfinished events in Dailies, show progress
|
||||||
against the required count, and distinguish finishing a week from finishing the event.
|
against the required count, and distinguish finishing a week from finishing the event.
|
||||||
Shipped 2026-10-13 — see the [spec](docs/superpowers/specs/2026-10-03-check-in-goals-design.md).
|
Shipped 2026-10-13 — see the [spec](docs/superpowers/specs/2026-10-03-check-in-goals-design.md).
|
||||||
- [ ] Rework how the *What you see* settings group handles its toggles, so that the daily chores
|
- [x] Rework how the *What you see* settings group handles its toggles, so that the daily chores
|
||||||
and the built-in weekly tasks can each be switched **globally and per game**, without adding a
|
and the built-in weekly tasks can each be switched **globally and per game**, without adding a
|
||||||
new checkbox for every game.
|
new checkbox for every game.
|
||||||
|
Shipped 2026-10-14 — see the [spec](docs/superpowers/specs/2026-10-14-per-game-switches-design.md).
|
||||||
- Today the two are inconsistent: the daily chores have one global switch
|
- Today the two are inconsistent: the daily chores have one global switch
|
||||||
(`prefs.showChores`) and no per-game control, while the built-in weekly tasks have only
|
(`prefs.showChores`) and no per-game control, while the built-in weekly tasks have only
|
||||||
per-game switches (`prefs.routineGames`), one checkbox per game that has any. With more
|
per-game switches (`prefs.routineGames`), one checkbox per game that has any. With more
|
||||||
|
|||||||
+20
-7
@@ -221,11 +221,21 @@ Namespaced, versioned, and small. Nothing here ever goes to the server.
|
|||||||
// focusGame, sort, view,
|
// focusGame, sort, view,
|
||||||
// timelineDayWidth, timelineGroup, showUpcoming,
|
// timelineDayWidth, timelineGroup, showUpcoming,
|
||||||
// timelineSplitUpcoming, detectDaily, showChores,
|
// timelineSplitUpcoming, detectDaily, showChores,
|
||||||
|
// choresOn?, choresOff?,
|
||||||
// showWeeklyTasks, showMonthlyTasks, showCompleted, showIgnored, theme, regionConfirmed,
|
// showWeeklyTasks, showMonthlyTasks, showCompleted, showIgnored, theme, regionConfirmed,
|
||||||
// onboarded, routineGames?, routinesAsked? }
|
// onboarded, showRoutines?, routineGames?, routinesOff?,
|
||||||
// routineGames is the games whose built-in weekly tasks
|
// routinesAsked? }
|
||||||
// the reader turned on; routinesAsked the games already
|
// Chores and built-in weekly tasks are each one global
|
||||||
// asked about. Both absent means none — built-ins are
|
// answer plus the games that differ (src/shared/perGame.ts):
|
||||||
|
// an on-list wins, then an off-list, then the global answer.
|
||||||
|
// showChores is the chores' global answer (default true);
|
||||||
|
// choresOn / choresOff the exceptions. showRoutines is the
|
||||||
|
// built-ins' — absent means never answered, read as off,
|
||||||
|
// and while absent the strip may ask about one game.
|
||||||
|
// routineGames is the games whose built-ins are on (the
|
||||||
|
// on-list, unchanged from before showRoutines existed),
|
||||||
|
// routinesOff the off-list, routinesAsked the games already
|
||||||
|
// asked about. Every list absent means none — built-ins are
|
||||||
// opt-in for every install. PRD F17.
|
// opt-in for every install. PRD F17.
|
||||||
// showWeeklyTasks / showMonthlyTasks say whether tasks of
|
// showWeeklyTasks / showMonthlyTasks say whether tasks of
|
||||||
// that period sit in the strip's row under the dailies.
|
// that period sit in the strip's row under the dailies.
|
||||||
@@ -529,9 +539,12 @@ removes the task record only.
|
|||||||
|
|
||||||
**Built-in tasks** (`src/shared/routines.ts`) are not stored: they ship in the bundle, each with a
|
**Built-in tasks** (`src/shared/routines.ts`) are not stored: they ship in the bundle, each with a
|
||||||
`source` URL and the `asOf` date that page gives, and their ticks and numbers are stored under their
|
`source` URL and the `asOf` date that page gives, and their ticks and numbers are stored under their
|
||||||
`routine:` id exactly as a reader's task's are. Two prefs govern them: `routineGames` (games whose
|
`routine:` id exactly as a reader's task's are. Four prefs govern them: `showRoutines` (the answer
|
||||||
built-ins are on) and `routinesAsked` (games already asked), both **absent meaning none** so they
|
for every game; **absent means never answered**, read as off), `routineGames` and `routinesOff`
|
||||||
are opt-in for every install. *Make it mine* adds a `mytask:` with `from: <routine id>` and copies
|
(games that differ from it, on and off), and `routinesAsked` (games the strip already asked about).
|
||||||
|
The lists are **absent meaning none**, so built-ins are opt-in for every install. `routineGames`
|
||||||
|
predates `showRoutines` and kept its meaning when it became the on-list — nothing was migrated, and
|
||||||
|
a tab still running an older shell reads it the same way. *Make it mine* adds a `mytask:` with `from: <routine id>` and copies
|
||||||
the routine's day log and amounts to it — additively, so deleting the copy shows the original
|
the routine's day log and amounts to it — additively, so deleting the copy shows the original
|
||||||
with its own numbers again.
|
with its own numbers again.
|
||||||
|
|
||||||
|
|||||||
@@ -425,6 +425,11 @@ selected tab fills with `raised` and turns ink; unselected tabs are faint and go
|
|||||||
- **Built-in question** (top of the dailies strip, once per game): one line of muted text —
|
- **Built-in question** (top of the dailies strip, once per game): one line of muted text —
|
||||||
`Track Zenless Zone Zero's built-in weekly tasks?` — with a near-coloured **Yes** and a faint **No
|
`Track Zenless Zone Zero's built-in weekly tasks?` — with a near-coloured **Yes** and a faint **No
|
||||||
thanks** as plain text buttons. Never a card, never a modal; the strip renders for it alone.
|
thanks** as plain text buttons. Never a card, never a modal; the strip renders for it alone.
|
||||||
|
- **Per-game switch** (*What you see*, under the chores and built-in switches): a faint 12px line
|
||||||
|
stating the exceptions — `Every game`, `All but Genshin Impact`, `Only 3 games` — then a muted,
|
||||||
|
underlined **Choose games** text button (**Done** while open). Open, a wrap of game chips in the
|
||||||
|
game-focus chip style: the game's hue with a 12% wash when on, a 30% hue border and muted ink when off,
|
||||||
|
`aria-pressed`, in the reader's game order. Closed every visit; never a checkbox per game.
|
||||||
- **Check-in goal control** (detail sheet, under the daily checklist): the status pills' style —
|
- **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,
|
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. Once a goal is set, a smaller
|
both on `ground` with a hairline border like the notes field. Once a goal is set, a smaller
|
||||||
|
|||||||
+4
-2
@@ -580,8 +580,10 @@ default) or **reach a number** (a target such as 2,100, with an optional unit).
|
|||||||
**Some games' weekly tasks are built in** — ZZZ's Ridu Weekly (1,300 points) and Endfield's Weekly
|
**Some games' weekly tasks are built in** — ZZZ's Ridu Weekly (1,300 points) and Endfield's Weekly
|
||||||
Routine (10 points) so far, each from a cited page and shown with the date that page gives, so a
|
Routine (10 points) so far, each from a cited page and shown with the date that page gives, so a
|
||||||
figure from an old guide is visibly old. They are **off until the reader says yes**: the strip asks
|
figure from an old guide is visibly old. They are **off until the reader says yes**: the strip asks
|
||||||
once, in one line, about a game on screen that has them, and a switch per game in *What you see*
|
once, in one line, about a game on screen that has them — until the reader answers for every game —
|
||||||
governs them after. Built-ins are not edited in place; **Make it mine** copies one into the reader's
|
and *What you see* governs them after: one switch for all of them, and *Choose games* for the games
|
||||||
|
that differ. The standing daily chores take the same shape, so a reader who wants Genshin's chore
|
||||||
|
and not Star Rail's can say so without losing either game's events. Built-ins are not edited in place; **Make it mine** copies one into the reader's
|
||||||
own tasks with its ticks and numbers, and hides the original while the copy exists.
|
own tasks with its ticks and numbers, and hides the original while the copy exists.
|
||||||
|
|
||||||
**F18 — What changed.**
|
**F18 — What changed.**
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# Per-game switches Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Daily chores and built-in weekly tasks each get a global switch plus per-game exceptions, set from a *Choose games* disclosure in *What you see*.
|
||||||
|
|
||||||
|
**Architecture:** A pure `PerGame` rule (`src/shared/perGame.ts`) — global answer, on-list, off-list — is the one place visibility is decided. Prefs gain optional exception lists; `routineGames` becomes the built-ins' on-list unchanged. Consumers (`dailyGroups`, `activeRoutines`, `routineToAsk`, `Controls`) read the rule through `choreRule` / `routineRule`.
|
||||||
|
|
||||||
|
**Tech Stack:** Bun 1.3, React 19, TypeScript strict, Tailwind, `bun test`.
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-10-14-per-game-switches-design.md`
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- No migration: every existing stored pref keeps its meaning; absent means unanswered.
|
||||||
|
- `showRoutines` absent is treated as off (built-ins stay opt-in).
|
||||||
|
- No new dependency; colours are tokens or game hues only (AGENTS.md § Conventions).
|
||||||
|
- Commits wait to be asked (AGENTS.md § Conventions); each task ends green and stage-ready, split
|
||||||
|
model → store → UI → docs.
|
||||||
|
|
||||||
|
## Review Focus
|
||||||
|
|
||||||
|
- Reader with `routineGames: ["zzz"]`, nothing else → ZZZ built-ins still shown (Task 2 carry-over test).
|
||||||
|
- Reader flips global chores off with an exception on Genshin → Genshin's chore stays (Task 1, Task 3).
|
||||||
|
- Global built-ins answered → strip stops asking (Task 2).
|
||||||
|
- Hand-edited storage with a game in both lists → shown (Task 1).
|
||||||
|
- Garbage in new prefs (`choresOff: "x"`, `showRoutines: 1`) → sanitised to absent (Task 2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: The `PerGame` rule
|
||||||
|
|
||||||
|
**Files:** Create `src/shared/perGame.ts`; Test `test/per-game.test.ts`
|
||||||
|
|
||||||
|
**Produces:** `type PerGame`, `shownFor(rule, game): boolean`, `setFor(rule, game, value): { on: string[]; off: string[] }`, `exceptionLine(rule, games, name): string`.
|
||||||
|
|
||||||
|
- [ ] Write tests: truth table (no lists → `all`; on-list → true under `all:false`; off-list → false under `all:true`; both lists → true); `setFor` adds only differences, removes from the other list, removes when value equals `all`; `exceptionLine` cases `Every game`, `All but Genshin Impact`, `All but 3 games`, `No game`, `Only Zenless Zone Zero`, `Only 3 games`, counting only games in the given list.
|
||||||
|
- [ ] Run `bun test test/per-game.test.ts` → FAIL (module missing).
|
||||||
|
- [ ] Implement:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface PerGame {
|
||||||
|
all: boolean;
|
||||||
|
on?: readonly string[] | undefined;
|
||||||
|
off?: readonly string[] | undefined;
|
||||||
|
}
|
||||||
|
export function shownFor(rule: PerGame, game: string): boolean {
|
||||||
|
if (rule.on?.includes(game)) return true;
|
||||||
|
if (rule.off?.includes(game)) return false;
|
||||||
|
return rule.all;
|
||||||
|
}
|
||||||
|
export function setFor(rule: PerGame, game: string, value: boolean): { on: string[]; off: string[] } {
|
||||||
|
const on = (rule.on ?? []).filter((g) => g !== game);
|
||||||
|
const off = (rule.off ?? []).filter((g) => g !== game);
|
||||||
|
if (value !== rule.all) (value ? on : off).push(game);
|
||||||
|
return { on, off };
|
||||||
|
}
|
||||||
|
export function exceptionLine(rule: PerGame, games: readonly string[], name: (g: string) => string): string {
|
||||||
|
const odd = games.filter((g) => shownFor(rule, g) !== rule.all);
|
||||||
|
if (odd.length === 0) return rule.all ? "Every game" : "No game";
|
||||||
|
const which = odd.length === 1 ? name(odd[0]!) : `${odd.length} games`;
|
||||||
|
return rule.all ? `All but ${which}` : `Only ${which}`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run → PASS; `bun run typecheck`.
|
||||||
|
|
||||||
|
### Task 2: Prefs and routines read the rule
|
||||||
|
|
||||||
|
**Files:** Modify `src/client/state/usePrefs.ts` (Prefs interface, `restorePrefsValue`), `src/shared/routines.ts` (`activeRoutines`, `routineToAsk`), `src/client/App.tsx` (call sites); Test `test/routines.test.ts`, `test/prefs.test.ts`
|
||||||
|
|
||||||
|
**Consumes:** Task 1. **Produces:** `Prefs.choresOn?`, `choresOff?`, `showRoutines?`, `routinesOff?`; `choreRule(p): PerGame`, `routineRule(p): PerGame` (in `usePrefs.ts`); `activeRoutines(rule: PerGame, copiedFrom)`; `routineToAsk(visible, rule: PerGame, asked, answered: boolean)`.
|
||||||
|
|
||||||
|
- [ ] Tests: carry-over (`routineRule({...defaults, routineGames:["zzz"]})` → `activeRoutines` gives ZZZ only; `showRoutines: true` gives both; `routinesOff:["zzz"]` under true gives Endfield only); `routineToAsk` returns null when `answered`, skips effectively-on games; restore sanitises the three new arrays and keeps `showRoutines` only when boolean.
|
||||||
|
- [ ] Run → FAIL.
|
||||||
|
- [ ] Implement:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function choreRule(p: Prefs): PerGame {
|
||||||
|
return { all: p.showChores, on: p.choresOn, off: p.choresOff };
|
||||||
|
}
|
||||||
|
export function routineRule(p: Prefs): PerGame {
|
||||||
|
return { all: p.showRoutines ?? false, on: p.routineGames, off: p.routinesOff };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Extend the array-sanitising loop to `choresOn`, `choresOff`, `routinesOff`; `showRoutines` → `undefined` unless boolean. `activeRoutines` filters `shownFor(rule, r.game)`; `routineToAsk` returns null if `answered`, skips `shownFor(rule, game)` or asked. App: `activeRoutines(routineRule(prefs), copiedFrom)`, `routineToAsk(visible, routineRule(prefs), prefs.routinesAsked, prefs.showRoutines !== undefined)`; Yes writes `routineGames: setFor(rule, game, true).on, routinesOff: …off`.
|
||||||
|
- [ ] Run full suite and typecheck → PASS.
|
||||||
|
|
||||||
|
### Task 3: Chores per game in the strip
|
||||||
|
|
||||||
|
**Files:** Modify `src/client/components/Dailies.tsx` (`dailyGroups`, `Dailies` prop), `src/client/App.tsx`; Test `test/views.test.tsx`
|
||||||
|
|
||||||
|
- [ ] Test: `dailyGroups(["genshin","hsr"], [], NOW, "europe", meta, startOf, (lane) => lane === "genshin")` → only `dailies:genshin`.
|
||||||
|
- [ ] Run → FAIL (type/behaviour).
|
||||||
|
- [ ] Implement: param `showChores: boolean | ((lane: LaneId) => boolean) = true`; check `typeof showChores === "function" ? showChores(lane) : showChores`. Same type on the `Dailies` prop. App passes `(lane) => shownFor(choreRule(prefs), lane)`.
|
||||||
|
- [ ] Run → PASS.
|
||||||
|
|
||||||
|
### Task 4: The disclosure in *What you see*
|
||||||
|
|
||||||
|
**Files:** Modify `src/client/components/Controls.tsx`; Test `test/controls.test.tsx`
|
||||||
|
|
||||||
|
- [ ] Tests: chores line `Every game` by default and `All but Honkai: Star Rail` with `choresOff:["hsr"]`; `Choose games` button present, chips absent until opened (static render); global `Show built-in weekly tasks` checkbox offered when a listed game has built-ins, reads `showRoutines`, line `Only Zenless Zone Zero` for `routineGames:["zzz"]`; no `Built-in weekly tasks for` text anywhere; weekly-task switch offered when routines effectively on via `showRoutines: true`.
|
||||||
|
- [ ] Run → FAIL.
|
||||||
|
- [ ] Implement `PerGameSwitch` component: the `Check`, then a faint line `exceptionLine(...) · <button aria-expanded>Choose games</button>`; when open, a `flex flex-wrap gap-1.5` of chip buttons (`aria-pressed`, hue border/text when on, `title` passed per chip). Chores: games = enabled, non-custom lanes; writes `{choresOn, choresOff}` from `setFor`. Built-ins: games with routines; writes `{routineGames: on, routinesOff: off, routinesAsked: [...asked minus g, g]}`; global writes `{showRoutines}`. Replace the per-game routine checkboxes; base `taskPeriods` on `shownFor(routineRule(prefs), g)` across `games`.
|
||||||
|
- [ ] Run full suite + typecheck → PASS.
|
||||||
|
- [ ] Browser check (AGENTS.md § Commands): build shell, serve on a fresh port, open settings → *What you see*, open both disclosures; screenshot at 390px and 1280px in dark and light.
|
||||||
|
|
||||||
|
### Task 5: Docs and changelog
|
||||||
|
|
||||||
|
**Files:** `docs/DATA-MODEL.md`, `docs/PRD.md` F17, `README.md`, `docs/DESIGN.md`, `TODO.md`, `src/shared/changelog.ts` (in a follow-up commit once the feature commit's hash exists).
|
||||||
|
|
||||||
|
- [ ] Update each sentence the change made false; tick the TODO with a spec link.
|
||||||
|
- [ ] `bun test`, `bun run typecheck`.
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Per-game switches for chores and built-in tasks — design
|
||||||
|
|
||||||
|
Date: 2026-10-14. Status: approved (repository owner, 2026-10-14). TODO.md item 2.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
*What you see* governs two things the app supplies on a game's behalf, and governs them two
|
||||||
|
different ways:
|
||||||
|
|
||||||
|
- **Daily chores** — one global switch, `prefs.showChores` (default on). No per-game control: a
|
||||||
|
reader who plays Genshin daily and HSR casually cannot drop only HSR's chore.
|
||||||
|
- **Built-in weekly tasks** — per-game opt-in only, `prefs.routineGames` (absent = none), drawn as
|
||||||
|
one checkbox per game that has any. No global answer, and the checkbox list grows with every game
|
||||||
|
and every built-in we add.
|
||||||
|
|
||||||
|
Wanted: one pattern for both — a global switch, and per-game exceptions reachable without a
|
||||||
|
checkbox per game in the group.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. **One rule: a global answer and two exception lists.** For each game,
|
||||||
|
on-list → shown; off-list → hidden; otherwise the global answer. Pure, in
|
||||||
|
`src/shared/perGame.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type PerGame = { all: boolean; on?: readonly string[] | undefined; off?: readonly string[] | undefined };
|
||||||
|
shownFor(rule: PerGame, game: string): boolean
|
||||||
|
setFor(rule: PerGame, game: string, value: boolean): { on: string[]; off: string[] }
|
||||||
|
```
|
||||||
|
|
||||||
|
`setFor` removes the game from both lists, then adds it to the one matching `value` **only if
|
||||||
|
`value` differs from `all`** — an exception is a difference, not a restatement. A game in both
|
||||||
|
lists (only reachable by hand-edited storage) resolves to the on-list.
|
||||||
|
|
||||||
|
2. **Storage — nothing migrated, every current answer keeps its meaning.**
|
||||||
|
|
||||||
|
| Pref | Meaning | Absent |
|
||||||
|
|---|---|---|
|
||||||
|
| `showChores` (exists) | global chores answer | `true`, as today |
|
||||||
|
| `choresOn?`, `choresOff?` (new) | games that differ | none |
|
||||||
|
| `showRoutines?` (new) | global built-ins answer | unanswered → treated as **off** (opt-in kept) |
|
||||||
|
| `routineGames?` (exists) | built-ins **on** for these games — now the on-exception list | none |
|
||||||
|
| `routinesOff?` (new) | built-ins off for these games | none |
|
||||||
|
| `routinesAsked?` (exists) | games the strip has asked about | none |
|
||||||
|
|
||||||
|
A reader with `routineGames: ["zzz"]` and nothing else sees exactly what they saw before: global
|
||||||
|
off, ZZZ on. An older tab still running the cached shell reads `routineGames` and shows the
|
||||||
|
same games. Rejected: one override map per feature with a load-time migration (an older tab would
|
||||||
|
lose the reader's built-ins); one exception list whose meaning inverts when the global flips
|
||||||
|
(flipping one switch would silently redefine every exception).
|
||||||
|
|
||||||
|
3. **Exceptions survive a global flip.** Changing the global switch rewrites nothing else; a game
|
||||||
|
the reader singled out stays singled out. Discards nothing, like every switch in this group.
|
||||||
|
|
||||||
|
4. **The strip's question still answers the same setting.** `routineToAsk` asks only while
|
||||||
|
`showRoutines` is absent (the reader has never answered globally), about a game on screen that is
|
||||||
|
effectively off and not yet in `routinesAsked`. **Yes** writes `routineGames` via `setFor` (which,
|
||||||
|
with the global off, adds the game) and records `routinesAsked`; **No thanks** records
|
||||||
|
`routinesAsked` only. Once the reader answers the global switch, the strip stops asking — they
|
||||||
|
have stated an answer for every game at once. A chip flip in settings also records the game in
|
||||||
|
`routinesAsked`, as the per-game checkbox did.
|
||||||
|
|
||||||
|
5. **UI — a disclosure under each global switch** (owner's choice of the three placements offered).
|
||||||
|
In *What you see*:
|
||||||
|
- `Show each game's own daily chores` keeps its checkbox. Under it, a faint line states the
|
||||||
|
exceptions and a **Choose games** text button opens a wrap of game chips (closed by default,
|
||||||
|
per-visit state like *show all N*).
|
||||||
|
- `Show built-in weekly tasks` is a new global checkbox, offered only when some game on the list
|
||||||
|
has built-ins, replacing the per-game checkboxes. Same line and disclosure; its hint keeps the
|
||||||
|
source-and-date disclosure per game in each chip's `title`.
|
||||||
|
- The line reads off the rule: global on → `Every game` / `All but Genshin Impact` /
|
||||||
|
`All but 3 games`; global off → `No game` / `Only Zenless Zone Zero` / `Only 3 games`.
|
||||||
|
Derived at render, never stored.
|
||||||
|
- Chips are `<button aria-pressed>` in the game-focus chip style (hue when on, faint when off),
|
||||||
|
in the reader's game order, wrapping at phone width. Chore chips list the reader's enabled
|
||||||
|
tracked games (a hidden game draws no chore anyway; a reader's own `mygame:` lanes have none).
|
||||||
|
Built-in chips list every game on the list that has built-ins, as the checkboxes did.
|
||||||
|
- The strip's Edit panel keeps its single `Daily chores` switch, bound to the global answer.
|
||||||
|
|
||||||
|
6. **Consumers read the rule, not the raw pref.**
|
||||||
|
- `dailyGroups`' `showChores` parameter accepts `boolean | ((lane) => boolean)`; App passes
|
||||||
|
`(lane) => shownFor(choreRule(prefs), lane)`. `Dailies` takes the same.
|
||||||
|
- `activeRoutines(rule, copiedFrom)` and `routineToAsk(visible, rule, asked, answered)` take the
|
||||||
|
`PerGame` rule. `Controls`' weekly/monthly switch availability uses the effective rule.
|
||||||
|
- `usePrefs.ts` exports `choreRule(prefs)` and `routineRule(prefs)` so nothing assembles a rule
|
||||||
|
from fields by hand.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Monthly built-ins (none exist); per-game control of the reader's own tasks (they are already per
|
||||||
|
game by construction); changing the Edit panel.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- `perGame`: the truth table of `shownFor`; `setFor` writes only differences and clears the other
|
||||||
|
list; both-lists resolves on.
|
||||||
|
- Carry-over: prefs with only `showChores` / `routineGames` give the same chores and built-ins as
|
||||||
|
before.
|
||||||
|
- `restorePrefsValue`: new arrays sanitised like `routineGames`, `showRoutines` kept only if
|
||||||
|
boolean, absent stays absent.
|
||||||
|
- `routineToAsk`: no question once `showRoutines` is set; skips effectively-on games.
|
||||||
|
- `dailyGroups`: a per-lane predicate drops one game's chore and keeps the other's.
|
||||||
|
- `Controls`: the exception line for each case; no per-game checkboxes; the built-in switch reads
|
||||||
|
`showRoutines`; chips render only when opened (static render: closed).
|
||||||
|
- Browser: the group opened, both disclosures expanded, at 390px and desktop width, both themes.
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
`docs/DATA-MODEL.md` prefs keys, `docs/PRD.md` F17 built-ins paragraph, `README.md` chores
|
||||||
|
sentence, `docs/DESIGN.md` component entry for the disclosure, `TODO.md` ticked, changelog entry.
|
||||||
Reference in new issue
Block a user