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) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4029885833
commit
3702ee7a4c
+19
-3
@@ -1,4 +1,5 @@
|
||||
import type { GachaEvent, Region } from "./schema.ts";
|
||||
import type { DisplayEvent } from "./custom.ts";
|
||||
import type { Region } from "./schema.ts";
|
||||
|
||||
/**
|
||||
* Time is this product's entire subject, so the vocabulary lives in one place:
|
||||
@@ -28,8 +29,23 @@ export function guessRegion(
|
||||
return "europe";
|
||||
}
|
||||
|
||||
/**
|
||||
* The boundary fields these helpers read.
|
||||
*
|
||||
* Structural rather than `GachaEvent` so a reader's own event (PRD F13) runs on
|
||||
* exactly the same clock as a scraped one — there is no second countdown
|
||||
* implementation to keep honest.
|
||||
*/
|
||||
export type EndBearing = Pick<
|
||||
DisplayEvent,
|
||||
"endsAt" | "regionScoped" | "regionEnds"
|
||||
>;
|
||||
|
||||
/** The end instant to show this user, honouring a region-scoped event. */
|
||||
export function effectiveEnd(event: GachaEvent, region: Region): string | null {
|
||||
export function effectiveEnd(
|
||||
event: EndBearing,
|
||||
region: Region,
|
||||
): string | null {
|
||||
if (event.endsAt === null) return null;
|
||||
if (!event.regionScoped || event.regionEnds === null) return event.endsAt;
|
||||
return event.regionEnds[region] ?? event.endsAt;
|
||||
@@ -96,7 +112,7 @@ export interface EventClock {
|
||||
}
|
||||
|
||||
export function clockFor(
|
||||
event: GachaEvent,
|
||||
event: EndBearing & Pick<DisplayEvent, "startsAt">,
|
||||
region: Region,
|
||||
now: number,
|
||||
): EventClock {
|
||||
|
||||
Reference in New Issue
Block a user