feat(routines): say "weekly and monthly" for built-ins
Built-ins now include monthly items, so the strip's question, Settings and Your own say "weekly and monthly". The per-game tooltip quotes the oldest source date when a game has several. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3fd2b3c2ed
commit
f322061c88
7 files changed
+22
-19
No files matched your search
@@ -281,14 +281,17 @@ export function Controls({
|
||||
<PerGameSwitch
|
||||
checked={routines.all}
|
||||
onChange={(showRoutines) => onUpdate({ showRoutines })}
|
||||
label="Show built-in weekly tasks"
|
||||
hint="Weekly tasks the app knows for some games, each from a cited page and dated by it. Off hides them; your ticks and numbers are kept."
|
||||
label="Show built-in weekly and monthly tasks"
|
||||
hint="Weekly and monthly tasks the app knows for some games, each from a cited page and dated by it. Off hides them; your ticks and numbers are kept."
|
||||
rule={routines}
|
||||
games={routineGames}
|
||||
chipsLabel="Built-in weekly tasks per game"
|
||||
chipsLabel="Built-in weekly and monthly tasks per game"
|
||||
titleFor={(game) => {
|
||||
const list = routinesFor(game);
|
||||
return `${list.map((r) => r.title).join(", ")} — from a source as of ${asOfLabel(list[0]!.asOf)}`;
|
||||
// Several pages per game now: quote the oldest, so the
|
||||
// tooltip never makes a year-old figure look current.
|
||||
const oldest = list.map((r) => r.asOf).sort()[0]!;
|
||||
return `${list.map((r) => r.title).join(", ")} — ${list.length > 1 ? "from sources, the oldest" : "from a source"} as of ${asOfLabel(oldest)}`;
|
||||
}}
|
||||
onSet={(game, value) => {
|
||||
const { on, off } = setFor(routines, game, value);
|
||||
|
||||
@@ -334,7 +334,7 @@ export function Dailies({
|
||||
tasks?: readonly StripTask[] | undefined;
|
||||
taskStatusFor?: ((task: StripTask) => GoalStatus | undefined) | undefined;
|
||||
/**
|
||||
* A game whose built-in weekly tasks the reader has not been asked about yet,
|
||||
* A game whose built-in weekly and monthly tasks the reader has not been asked about yet,
|
||||
* and how to record the answer. Asked here because this is where the tasks
|
||||
* would appear; one line, never a modal, never twice.
|
||||
*/
|
||||
@@ -427,13 +427,13 @@ export function Dailies({
|
||||
ask === undefined ? null : (
|
||||
<div className="relative flex flex-wrap items-baseline gap-x-3 gap-y-1 text-xs">
|
||||
<p className="text-muted">
|
||||
Track {gameMeta(ask.game).name}'s built-in weekly tasks?
|
||||
Track {gameMeta(ask.game).name}'s built-in weekly and monthly tasks?
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => ask.onAnswer(true)}
|
||||
aria-label={`Yes, track ${gameMeta(ask.game).name}'s built-in weekly tasks`}
|
||||
aria-label={`Yes, track ${gameMeta(ask.game).name}'s built-in weekly and monthly tasks`}
|
||||
className="font-medium text-near transition-colors hover:text-ink"
|
||||
>
|
||||
Yes
|
||||
@@ -441,7 +441,7 @@ export function Dailies({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => ask.onAnswer(false)}
|
||||
aria-label={`No thanks to ${gameMeta(ask.game).name}'s built-in weekly tasks`}
|
||||
aria-label={`No thanks to ${gameMeta(ask.game).name}'s built-in weekly and monthly tasks`}
|
||||
className="text-faint transition-colors hover:text-muted"
|
||||
>
|
||||
No thanks
|
||||
|
||||
@@ -332,7 +332,7 @@ export function YourOwn({
|
||||
a copy that replaces it while it exists, numbers and ticks included. */}
|
||||
{routines.length > 0 && onMakeItMine !== undefined && (
|
||||
<>
|
||||
<p className="mt-4 text-xs text-faint">Built-in weekly tasks</p>
|
||||
<p className="mt-4 text-xs text-faint">Built-in weekly and monthly tasks</p>
|
||||
<ul className="mt-1.5 flex flex-col gap-1">
|
||||
{routines.map((routine) => {
|
||||
const tracked = !(routinesHidden?.has(routine.id) ?? false);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { shownFor, type PerGame } from "./perGame.ts";
|
||||
import { MeasureSchema, type TaskShape } from "./task.ts";
|
||||
|
||||
/**
|
||||
* Weekly tasks the app ships for a game — the built-in counterpart of a
|
||||
* Weekly and monthly tasks the app ships for a game — the built-in counterpart of a
|
||||
* reader's own `mytask:` (PRD F17).
|
||||
*
|
||||
* Each is a claim about a game we did not write, so it is held to the bar the
|
||||
|
||||
@@ -304,17 +304,17 @@ describe("Controls: backup and export actions", () => {
|
||||
});
|
||||
|
||||
|
||||
describe("Controls: built-in weekly tasks", () => {
|
||||
describe("Controls: built-in weekly and monthly tasks", () => {
|
||||
test("one global switch, not a checkbox per game", () => {
|
||||
const html = render(PREFS, 0, ["genshin", "zzz", "endfield"]);
|
||||
expect(html).toContain("Show built-in weekly tasks");
|
||||
expect(html).toContain("Show built-in weekly and monthly tasks");
|
||||
expect(html).not.toContain("Built-in weekly tasks for");
|
||||
});
|
||||
|
||||
test("it reads showRoutines, and an unanswered one is off", () => {
|
||||
const games = ["genshin", "zzz"];
|
||||
const box = (html: string) =>
|
||||
html.match(/<input type="checkbox"[^>]*>(?=[^<]*<span>Show built-in weekly tasks)/)?.[0];
|
||||
html.match(/<input type="checkbox"[^>]*>(?=[^<]*<span>Show built-in weekly and monthly tasks)/)?.[0];
|
||||
expect(box(render(PREFS, 0, games))).not.toContain('checked=""');
|
||||
expect(box(render({ ...PREFS, showRoutines: true }, 0, games))).toContain('checked=""');
|
||||
});
|
||||
@@ -325,7 +325,7 @@ describe("Controls: built-in weekly tasks", () => {
|
||||
});
|
||||
|
||||
test("no switch when no game on the list has any", () => {
|
||||
expect(render(PREFS)).not.toContain("built-in weekly tasks");
|
||||
expect(render(PREFS)).not.toContain("built-in weekly and monthly tasks");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1322,7 +1322,7 @@ describe("review fixes: the month-day field", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("built-in weekly tasks in Your own", () => {
|
||||
describe("built-in weekly and monthly tasks in Your own", () => {
|
||||
test("listed with their source's date, and a way to make one yours", async () => {
|
||||
const { routinesFor } = await import("../src/shared/routines.ts");
|
||||
const noop = () => {};
|
||||
|
||||
+4
-4
@@ -1495,7 +1495,7 @@ describe("review fixes: an event's points goal is not a daily", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("built-in weekly tasks in the strip", () => {
|
||||
describe("built-in weekly and monthly tasks in the strip", () => {
|
||||
const NOW = Date.parse("2026-10-15T12:00:00.000Z");
|
||||
const ridu = routinesFor("zzz")[0]!;
|
||||
const status: GoalStatus = {
|
||||
@@ -1516,11 +1516,11 @@ describe("built-in weekly tasks in the strip", () => {
|
||||
daysFor={() => []} onToggleDay={() => {}} ask={{ game: "zzz", onAnswer: () => {} }} />,
|
||||
);
|
||||
expect(html).toContain("Track Zenless Zone Zero");
|
||||
expect(html).toContain("weekly tasks?");
|
||||
expect(html).toContain("weekly and monthly tasks?");
|
||||
expect(html).toContain("No thanks");
|
||||
// Heard out of context, "Yes" has to say what it agrees to.
|
||||
expect(html).toContain(`aria-label="Yes, track Zenless Zone Zero's built-in weekly tasks"`.replace("'", "'"));
|
||||
expect(html).toContain(`aria-label="No thanks to Zenless Zone Zero's built-in weekly tasks"`.replace("'", "'"));
|
||||
expect(html).toContain(`aria-label="Yes, track Zenless Zone Zero's built-in weekly and monthly tasks"`.replace("'", "'"));
|
||||
expect(html).toContain(`aria-label="No thanks to Zenless Zone Zero's built-in weekly and monthly tasks"`.replace("'", "'"));
|
||||
});
|
||||
|
||||
test("with nothing to show and nothing to ask, the strip is absent", () => {
|
||||
|
||||
Reference in new issue
Block a user