From 1c7df637a4137daec0ca2e6d400dfd14705171d6 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Tue, 13 Oct 2026 14:19:16 +0200 Subject: [PATCH] fix(routines): test the copy, name what Yes agrees to, and true up the docs Make it mine's copying was inline in App and untested; it is now a pure function with a test, so a change to either store can't make the copy silently arrive empty. The question's buttons now say what they answer when heard out of context. The prefs block and the spec now match what shipped. Co-Authored-By: Claude Opus 5.5 --- docs/DATA-MODEL.md | 6 +++++- .../specs/2026-10-13-periodic-tasks-design.md | 10 ++++++---- src/client/App.tsx | 15 +++++++++----- src/client/components/Dailies.tsx | 2 ++ src/shared/routines.ts | 20 +++++++++++++++++++ test/routines.test.ts | 19 ++++++++++++++++++ test/views.test.tsx | 3 +++ 7 files changed, 65 insertions(+), 10 deletions(-) diff --git a/docs/DATA-MODEL.md b/docs/DATA-MODEL.md index e36000f..fda7d63 100644 --- a/docs/DATA-MODEL.md +++ b/docs/DATA-MODEL.md @@ -222,7 +222,11 @@ Namespaced, versioned, and small. Nothing here ever goes to the server. // timelineDayWidth, timelineGroup, showUpcoming, // timelineSplitUpcoming, detectDaily, showChores, // showCompleted, showIgnored, theme, regionConfirmed, - // onboarded } + // onboarded, routineGames?, routinesAsked? } + // routineGames is the games whose built-in weekly tasks + // the reader turned on; routinesAsked the games already + // asked about. Both absent means none — built-ins are + // opt-in for every install. PRD F17. // timelineDayWidth is px per day on the board, stored as the // measurement rather than a step number and read through // snapDayWidth — so a value from an older ladder still opens 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 18acdc4..fae60b5 100644 --- a/docs/superpowers/specs/2026-10-13-periodic-tasks-design.md +++ b/docs/superpowers/specs/2026-10-13-periodic-tasks-design.md @@ -133,12 +133,14 @@ Monday; monthly to the 1st. number is visibly dated rather than presented as current. - **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 + strip asks: `Track Zenless Zone Zero's built-in weekly 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 +- **Toggle.** *What you see* shows a “Built-in weekly tasks for ” switch, beside the daily + chores switch, for each game that has built-ins, writing `routineGames` (and answering the + strip's question). Off hides the chips; their ticks and amounts stay. Each built-in shows the + date its source gives (`asOf`), not the date we read it. +- **Make it mine.** From 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. diff --git a/src/client/App.tsx b/src/client/App.tsx index d865920..e86b361 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -35,7 +35,13 @@ import { clockFor, formatRemaining } from "../shared/time.ts"; import { dailySummary, isDaily, resolveDaily } from "../shared/daily.ts"; import { goalStatus, readGoal, type GoalStatus } from "../shared/goal.ts"; import { taskStatus, type TaskShape } from "../shared/task.ts"; -import { activeRoutines, mineFrom, routineToAsk, type Routine } from "../shared/routines.ts"; +import { + activeRoutines, + copyRoutineState, + mineFrom, + routineToAsk, + type Routine, +} from "../shared/routines.ts"; import { strandedOccurrences } from "../shared/recurrence.ts"; import { orderGames } from "./state/gameOrder.ts"; import { GameMetaProvider, type MetaResolver } from "./state/gameMeta.tsx"; @@ -212,10 +218,9 @@ export function App() { const makeItMine = (routine: Routine) => { if (tasks.tasks.some((t) => t.from === routine.id)) return; const id = tasks.addTask(mineFrom(routine)); - const log = daily.logs[routine.id]; - if (log !== undefined) daily.merge({ [id]: log }); - const numbers = amounts.amounts[routine.id]; - if (numbers !== undefined) amounts.merge({ [id]: numbers }); + const copy = copyRoutineState(daily.logs, amounts.amounts, routine.id, id); + daily.merge(copy.daily); + amounts.merge(copy.amounts); }; const taskStatusOf = (task: TaskShape): GoalStatus => diff --git a/src/client/components/Dailies.tsx b/src/client/components/Dailies.tsx index 6d359ea..c108a4a 100644 --- a/src/client/components/Dailies.tsx +++ b/src/client/components/Dailies.tsx @@ -384,6 +384,7 @@ export function Dailies({