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]>
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
import { createContext, useContext } from "react";
|
|
import type { CustomGames, LaneId } from "../../shared/custom.ts";
|
|
import { metaFor, type GameMeta } from "../../shared/games.ts";
|
|
|
|
/**
|
|
* How a component turns a lane id into a name, a short label and a hue.
|
|
*
|
|
* It used to be a direct import of `gameMeta`, which could only answer for the
|
|
* games in `GAMES`. Now a lane can also be one the reader invented (PRD F13),
|
|
* and those live in their browser rather than in a module — so the resolver has
|
|
* to come from somewhere with access to that state.
|
|
*
|
|
* A context rather than module-level mutable state: `metaFor` stays pure and
|
|
* takes the reader's games as an argument, and nothing renders off a registry
|
|
* that some other part of the app has been quietly writing to.
|
|
*/
|
|
export type MetaResolver = (id: LaneId) => GameMeta;
|
|
|
|
const NO_CUSTOM_GAMES: CustomGames = {};
|
|
|
|
const GameMetaContext = createContext<MetaResolver>((id) =>
|
|
metaFor(id, NO_CUSTOM_GAMES),
|
|
);
|
|
|
|
export const GameMetaProvider = GameMetaContext.Provider;
|
|
|
|
/**
|
|
* The lane resolver for this tree.
|
|
*
|
|
* The default answers for tracked games only, which is the correct answer
|
|
* anywhere the reader's own games cannot appear — and a safe one everywhere
|
|
* else, since `metaFor` is total.
|
|
*/
|
|
export function useGameMeta(): MetaResolver {
|
|
return useContext(GameMetaContext);
|
|
}
|