feat: let readers record where they are and how much work it is

The done checkbox now cycles untouched → doing → done, in the order people
actually move through. Effort, status and a note are set from the detail
sheet.

Rows carry chips for what the reader said, and a "tight" or "running out of
time" flag when the remaining time no longer covers the effort they recorded.
The detail sheet shows the arithmetic behind that rather than just asserting
it, and calls it a rough guide.

Imports from before this change are mapped forward rather than dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5 committed 2026-08-15 01:49:53 +02:00
1 parent 2f7e5bdf79
commit 89df140c00
4 files changed
+218 -18

No files matched your search

+42
View File
@@ -2,26 +2,42 @@ import { useEffect } from "react";
import { gameMeta } from "../../shared/games.ts";
import { formatAbsolute, formatRemaining } from "../../shared/time.ts";
import type { RowEvent } from "./EventRow.tsx";
import { pressure, pressureReason, type Effort } from "../../shared/effort.ts";
import type { Status } from "../state/useProgress.ts";
import { ProgressControls } from "./ProgressControls.tsx";
import { Meter, URGENCY_COLOR } from "./Meter.tsx";
export function EventDetail({
row,
completed,
ignored,
status,
effort,
note,
onToggle,
onIgnore,
onStatus,
onEffort,
onNote,
onClose,
}: {
row: RowEvent;
completed: boolean;
ignored: boolean;
status: Status | undefined;
effort: Effort | undefined;
note: string;
onToggle: (id: string) => void;
onIgnore: (id: string) => void;
onStatus: (id: string, s: Status | undefined) => void;
onEffort: (id: string, e: Effort | undefined) => void;
onNote: (id: string, n: string) => void;
onClose: () => void;
}) {
const { event, clock } = row;
const game = gameMeta(event.game);
const heat = URGENCY_COLOR[clock.urgency];
const risk = status === "done" ? "fine" : pressure(effort, clock.msRemaining);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
@@ -85,6 +101,32 @@ export function EventDetail({
<Field label="Type">{event.type}</Field>
</dl>
{risk !== "fine" && effort !== undefined && clock.msRemaining !== null && (
<p
className="mt-4 rounded-lg border px-3 py-2 text-xs leading-relaxed"
style={{
borderColor:
risk === "unlikely"
? "color-mix(in srgb, var(--color-critical) 40%, transparent)"
: "color-mix(in srgb, var(--color-soon) 40%, transparent)",
color:
risk === "unlikely" ? "var(--color-critical)" : "var(--color-soon)",
}}
>
{pressureReason(effort, clock.msRemaining)} It is a rough guide, not a
verdict — you know your own schedule.
</p>
)}
<ProgressControls
status={status}
effort={effort}
note={note}
onStatus={(s) => onStatus(event.id, s)}
onEffort={(e) => onEffort(event.id, e)}
onNote={(n) => onNote(event.id, n)}
/>
{event.endPrecision === "day" && event.endsAt !== null && (
<p className="mt-3 text-xs leading-relaxed text-faint">
The source gave a date but no time of day, so this end is accurate to