Say how often, and say what a reschedule costs

Occurrence ids carry their own start day, so moving the anchor or the
interval re-keys every occurrence and the marks under the old ids stop
being reachable. Nothing is rewritten — removeEvent makes the same trade,
and useMarkSet never removes because nothing else holds a copy — but the
reader is told the count first, the way removeGame reports blockedBy
instead of cascading.

Informs, never blocks. Renaming still costs nothing: the token is random
precisely so fixing a typo never moves an id, and movesOccurrences is what
keeps the warning off a rename and off a bare change of `until`.

cadenceLabel sits beside the form's own vocabulary so the sheet cannot
describe a rule differently from the control that set it.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-28 05:04:39 +02:00
co-authored by Claude Opus 5
parent c161c4a0dc
commit c5c578ae7e
4 changed files with 141 additions and 15 deletions
+14
View File
@@ -30,6 +30,7 @@ import {
} from "./state/lens.ts";
import { clockFor, formatRemaining } from "../shared/time.ts";
import { dailySummary, isDaily, resolveDaily } from "../shared/daily.ts";
import { nextOccurrences } from "../shared/recurrence.ts";
import { orderGames } from "./state/gameOrder.ts";
import { GameMetaProvider, type MetaResolver } from "./state/gameMeta.tsx";
import { metaOnTheme, useTheme } from "./state/theme.ts";
@@ -705,6 +706,19 @@ export function App() {
onSave: (_id: string, draft: EventDraft) =>
custom.editEvent(record.id, draft),
onDelete: () => custom.removeEvent(record.id),
strandedBy: () => {
// What the reader has actually recorded against the occurrences
// this rule generates today, and would no longer reach once the
// ids move. Twelve is a season of a fortnightly rule — enough to
// make the number meaningful without walking a decade of a
// daily one.
if (record.repeat === null) return 0;
return nextOccurrences(record, now, 12).filter(
(o) =>
prog.progress[o.id] !== undefined ||
(daily.logs[o.id]?.days.length ?? 0) > 0,
).length;
},
};
})()}
/>