feat: let a day already gone be ticked
People play at midnight and tick at breakfast, or forget for a week and come back. A log that only accepts today goes wrong on its first bad evening and is never trusted again. An event whose end was published already allowed this — its checklist draws the whole run and past pips are clickable. `catchUpDays` answers the two cases `dailyDays` cannot: a standing chore, which has no start or end because it is a routine rather than an event, and an event with `endsAt: null`, where the checklist could draw no run at all and so offered only today. That second one is the worse of the two, because it looks like a working checklist. Never a day past today. A tick claims you did it, and nobody can have done tomorrow, so a future day is absent rather than rendered and disabled — a control for a claim that cannot be true. An event's strip starts when the event did, capped at the same fortnight so a login campaign that opened in March does not become a wall of pips. The window bounds display and never data: a tick older than it stays logged and keeps counting toward the streak. `DayPip` moves to its own module rather than being copied, so there is one answer to what a missed day looks like. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
88d5ea2e43
commit
bf7959c916
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* One game-day, ticked or not.
|
||||
*
|
||||
* Shared by the two places a run of days is drawn: an event's checklist in the
|
||||
* detail sheet, and the catch-up strips on the dailies section and on an event
|
||||
* whose end was never announced. One pip rather than two, because they mean the
|
||||
* same thing to the reader and a second copy is a second set of rules about
|
||||
* what a missed day looks like.
|
||||
*
|
||||
* Future days are dimmed but not disabled-looking, and past misses read as
|
||||
* empty rather than as an error — a missed daily is information, not a telling
|
||||
* off. A catch-up strip never contains a future day at all, so there the
|
||||
* dimming never appears.
|
||||
*/
|
||||
export function DayPip({
|
||||
day,
|
||||
today,
|
||||
done,
|
||||
onToggle,
|
||||
}: {
|
||||
day: string;
|
||||
today: string;
|
||||
done: boolean;
|
||||
onToggle: () => void;
|
||||
}) {
|
||||
const isToday = day === today;
|
||||
const isFuture = day > today;
|
||||
// Rendered in UTC on purpose. A day key is a game-day, not an instant, and
|
||||
// formatting it in the reader's own zone shifts it a day backwards for
|
||||
// everyone west of UTC — so the pip would read "12" while the label a screen
|
||||
// reader announces said "Aug 11".
|
||||
const label = new Date(`${day}T00:00:00Z`).toLocaleDateString(undefined, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggle}
|
||||
disabled={isFuture}
|
||||
aria-pressed={done}
|
||||
aria-label={`${label}${done ? ", done" : ", not done"}`}
|
||||
title={label}
|
||||
className={`tnum size-6 rounded-[5px] border text-[0.625rem] leading-none transition-colors ${
|
||||
done
|
||||
? "border-transparent bg-near/25 text-near"
|
||||
: isFuture
|
||||
? "border-hairline/60 text-faint/50"
|
||||
: "border-hairline text-faint hover:border-faint"
|
||||
} ${isToday ? "ring-1 ring-ink/40" : ""} ${
|
||||
isFuture ? "cursor-default" : "cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
{day.slice(8)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user