Project an occurrence into the shape every view reads

The rule supplies what the thing is; the occurrence supplies which time
round. Nothing downstream is told which it is looking at, which is what
lets sort, focus, lanes, filters, progress, ignores and the daily
checklist work with no narrowing at any call site.

The end is always resolved here and never null. A row still carrying the
unresolved form would render live-with-unknown-end forever, which is the
failure the whole design exists to avoid.

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 dfb00aaa86
commit c76a0b4e70
2 changed files with 103 additions and 1 deletions
+29
View File
@@ -1,5 +1,6 @@
import { z } from "zod";
import { comesRoundEarly, Repeat } from "./recurrence.ts";
import type { Occurrence } from "./recurrence.ts";
import { EventType, GachaEvent, Precision, slugify } from "./schema.ts";
/**
@@ -242,3 +243,31 @@ export function precisionOf(hasTime: boolean): Extract<Precision, "exact" | "day
export function knownLane(id: LaneId, games: CustomGames): boolean {
return !isCustomGameId(id) || games[id] !== undefined;
}
/**
* Project one occurrence of a rule into the shape the views read.
*
* Everything that identifies the *thing* comes from the rule; everything that
* identifies *which time round* comes from the occurrence. Nothing downstream
* is told which it is looking at, which is what lets sort, focus, lanes,
* filters, progress, ignores and the daily checklist work with no narrowing at
* any call site.
*
* The end is always resolved here, never `null`. A rule with no stated end
* stores `null` and means "until the next one opens"; a row that reached a view
* still carrying the unresolved form would render as live-with-unknown-end
* forever, which is the failure this whole design exists to avoid.
*/
export function asOccurrenceEvent(
rule: CustomEvent,
occurrence: Occurrence,
): DisplayEvent {
return {
...asDisplayEvent(rule),
id: occurrence.id,
startsAt: occurrence.startsAt,
startPrecision: occurrence.startPrecision,
endsAt: occurrence.endsAt,
endPrecision: occurrence.endPrecision,
};
}