feat(custom): model reader-authored games and events

The data layer for PRD F13, with no UI and no behaviour change yet.

src/shared/custom.ts defines the two key spaces, their schemas, and the
projection into what the views read. Two properties are the point of it:

- A reader's event id is random, not derived from their title. They can type a
  scraped event's exact name and date, which under ${game}:${slug}:${date} is a
  byte-identical key — one completion mark and one streak silently shared by two
  events. Randomness also means renaming their own event never moves its id.
- A reader's event carries no sourceUrl, so a hand-entered date can never be
  attributed to a source, and claims no region split, because they entered one
  instant and inventing three would fabricate two of them.

The rest is widening what was GameId-shaped into a lane that may be one of
theirs: clockFor takes the boundary fields structurally so their events run on
the identical countdown rather than a second one, day keys fall back to the
regional default for a lane with no server map, and gameMeta becomes a context
resolver so metaFor stays pure and total — a lane can outlive its game when an
import carries an event whose game did not come with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5 committed 2026-08-17 18:16:21 +02:00
1 parent 4029885833
commit 3702ee7a4c
18 files changed
+640 -68

No files matched your search

+3 -2
View File
@@ -3,7 +3,8 @@ import {
msUntilReset,
type DailySummary,
} from "../../shared/daily.ts";
import type { GameId, Region } from "../../shared/schema.ts";
import type { LaneId } from "../../shared/custom.ts";
import type { Region } from "../../shared/schema.ts";
import { formatRemaining } from "../../shared/time.ts";
/**
@@ -32,7 +33,7 @@ export function DailyChecklist({
endsMs: number | null;
region: Region;
/** Whose reset clock the days are counted on — not every game shares one. */
game: GameId;
game: LaneId;
now: number;
logged: string[];
onToggleDay: (day: string) => void;