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 <[email protected]>
56 lines
2.0 KiB
TypeScript
56 lines
2.0 KiB
TypeScript
/**
|
|
* 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}`;
|
|
}
|