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:
co-authored by
Claude Opus 5
parent
4029885833
commit
3702ee7a4c
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import type { GameId, Region } from "../../shared/schema.ts";
|
||||
import type { LaneId } from "../../shared/custom.ts";
|
||||
import type { Region } from "../../shared/schema.ts";
|
||||
import { guessRegion } from "../../shared/time.ts";
|
||||
import type { SortMode } from "./sort.ts";
|
||||
import { KEYS, readJson, writeJson } from "./storage.ts";
|
||||
@@ -7,7 +8,7 @@ import { KEYS, readJson, writeJson } from "./storage.ts";
|
||||
export interface Prefs {
|
||||
region: Region;
|
||||
/** Games the reader has switched off. Stored as hidden so a newly added game shows up by default. */
|
||||
hiddenGames: GameId[];
|
||||
hiddenGames: LaneId[];
|
||||
/**
|
||||
* One game to look at right now, or null for all of them.
|
||||
*
|
||||
@@ -16,7 +17,7 @@ export interface Prefs {
|
||||
* obeyed (`resolveFocus`), so it can never leave the reader on a blank page
|
||||
* with no visible cause.
|
||||
*/
|
||||
focusGame: GameId | null;
|
||||
focusGame: LaneId | null;
|
||||
/** How the list is ordered. Deadline order is the default and the fallback. */
|
||||
sort: SortMode;
|
||||
/**
|
||||
@@ -66,7 +67,7 @@ export function usePrefs() {
|
||||
setPrefs((prev) => ({ ...prev, ...patch }));
|
||||
}, []);
|
||||
|
||||
const toggleGame = useCallback((game: GameId) => {
|
||||
const toggleGame = useCallback((game: LaneId) => {
|
||||
setPrefs((prev) => ({
|
||||
...prev,
|
||||
hiddenGames: prev.hiddenGames.includes(game)
|
||||
|
||||
Reference in New Issue
Block a user