feat(prefs): switch chores and built-in tasks per game, on one rule
Chores had one global switch and built-ins only a per-game opt-in, so the two answered the same question two different ways. perGame.ts is now the one rule for both: a global answer, then the games that differ. Nothing is migrated. routineGames keeps its meaning as the built-ins' on-list and an absent showRoutines still reads as off, so every reader's answers, and an older cached tab, see what they saw before. The strip stops asking about one game once the reader has answered for all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7a68613f3e
commit
47c395379b
8 files changed
+289
-37
No files matched your search
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Whether the app shows something it supplies on a game's behalf — a daily
|
||||
* chore, a built-in weekly task — for one game.
|
||||
*
|
||||
* One global answer, and the games that differ from it. Two lists rather than
|
||||
* one so an exception never changes meaning: a game on `off` is off whatever
|
||||
* `all` says, and flipping `all` rewrites nothing else. That is also what lets
|
||||
* `prefs.routineGames` — a list of games turned *on* since before this existed —
|
||||
* be the built-ins' `on` list unchanged, with no migration and no reader's
|
||||
* answer moved.
|
||||
*/
|
||||
export interface PerGame {
|
||||
all: boolean;
|
||||
on?: readonly string[] | undefined;
|
||||
off?: readonly string[] | undefined;
|
||||
}
|
||||
|
||||
/** The answer for one game. A game in both lists can only come from hand-edited storage; it is shown. */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The lists after the reader answers for one game. An exception is a
|
||||
* difference, not a restatement: an answer matching the global one clears the
|
||||
* game from both lists rather than recording it.
|
||||
*/
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
* The rule as one short line — `Every game`, `All but Genshin Impact`,
|
||||
* `Only 3 games` — counting only the games offered, so it never reports an
|
||||
* exception the reader cannot see beside it.
|
||||
*/
|
||||
export function exceptionLine(
|
||||
rule: PerGame,
|
||||
games: readonly string[],
|
||||
name: (game: 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}`;
|
||||
}
|
||||
+13
-12
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { PeriodSchema } from "./period.ts";
|
||||
import { GameId } from "./schema.ts";
|
||||
import { shownFor, type PerGame } from "./perGame.ts";
|
||||
import { MeasureSchema, type TaskShape } from "./task.ts";
|
||||
|
||||
/**
|
||||
@@ -13,7 +14,8 @@ import { MeasureSchema, type TaskShape } from "./task.ts";
|
||||
* then visibly ten months old rather than presented as current. Nothing here is
|
||||
* inferred; add one only when a page states it.
|
||||
*
|
||||
* Off unless the reader says yes, per game (`prefs.routineGames`). Its id is in
|
||||
* Off unless the reader says yes — for every game (`prefs.showRoutines`) or for
|
||||
* one (`prefs.routineGames`, `prefs.routinesOff`; see `perGame.ts`). Its id is in
|
||||
* the reserved `routine:` space, and its ticks and numbers are stored under that
|
||||
* id exactly as a reader's task's are.
|
||||
*/
|
||||
@@ -65,16 +67,12 @@ export function routinesFor(game: string): Routine[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* The built-ins on screen: those of games the reader said yes to, less any
|
||||
* they made their own (`Task.from`) — the copy replaces the original while it
|
||||
* The built-ins on screen: those of games the rule shows, less any the reader
|
||||
* made their own (`Task.from`) — the copy replaces the original while it
|
||||
* exists, and deleting the copy brings the original back.
|
||||
*/
|
||||
export function activeRoutines(
|
||||
routineGames: readonly string[] | undefined,
|
||||
copiedFrom: ReadonlySet<string>,
|
||||
): Routine[] {
|
||||
const on = new Set(routineGames ?? []);
|
||||
return ROUTINES.filter((r) => on.has(r.game) && !copiedFrom.has(r.id));
|
||||
export function activeRoutines(rule: PerGame, copiedFrom: ReadonlySet<string>): Routine[] {
|
||||
return ROUTINES.filter((r) => shownFor(rule, r.game) && !copiedFrom.has(r.id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,15 +80,18 @@ export function activeRoutines(
|
||||
*
|
||||
* Only a game on screen — asking about a game the reader switched off or is not
|
||||
* focused on would be asking about something they are not looking at — and
|
||||
* only once: an answer either way is recorded in `asked`.
|
||||
* only once: an answer either way is recorded in `asked`. Never once the reader
|
||||
* has `answered` the global switch: that answer covers every game at once.
|
||||
*/
|
||||
export function routineToAsk(
|
||||
visible: readonly string[],
|
||||
routineGames: readonly string[] | undefined,
|
||||
rule: PerGame,
|
||||
asked: readonly string[] | undefined,
|
||||
answered: boolean,
|
||||
): string | null {
|
||||
if (answered) return null;
|
||||
for (const game of visible) {
|
||||
if (routineGames?.includes(game) || asked?.includes(game)) continue;
|
||||
if (shownFor(rule, game) || asked?.includes(game)) continue;
|
||||
if (routinesFor(game).length > 0) return game;
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in new issue
Block a user