Live data & mock fallback
When the app serves real solves, when it fabricates, and how you always know which.
The app has two data sources and one rule: fabricated numbers are always labeled.
The switch
OLLIN_STUDY_API_URL decides:
- Set and reachable → live fetches, server-side, cached per the API's immutable-stamp model.
- Unset, unreachable, or any non-2xx → the deterministic local mock
dataset. The fetch layer returns
nullon any failure and each fetcher falls back per-request, so a flaky API degrades gracefully rather than erroring.
Every fetch result carries source: "live" | "mock"; a page computes its
overall source and renders the ribbon if anything on it is mock.
The demo ribbon
Amber, at the top of the affected screen: "Demo data — fabricated locally. Set OLLIN_STUDY_API_URL (or check that the API is reachable) to serve real solves." If you see it unexpectedly, the usual cause is the localhost/127.0.0.1 gotcha or an expired GCS token API-side (see the runbook).
One deliberate non-fallback: in live mode, a preflop line the live pack cannot walk is treated as an illegal line (redirect to the depth root), not a mock case — live and mock trees never mix on one screen.
What the mock dataset is
src/lib/api/mock/ fabricates a complete, deterministic product experience
(seeded RNG — stable across reloads):
- 21 postflop families across the 10–100bb ladder and all four line
types (limp, SRP, 3-bet, 4-bet), each with the real 184-board canonical
subset (
src/lib/poker/boards.ts, drawn from the 1,755 iso-distinct flops) and per-combo root strategies generated from plausible policy shapes. - A full 8-max preflop state machine (
mock/preflop.ts): blinds + 0.125bb antes, raise ladders, all-in closure — serving every depth of the ladder lazily in the exact pack-v1 node shape. Tight UTG, wide BTN, SB limp/raise mix, jam-or-fold at 10bb. Arriving ranges are exact products of taken-action frequencies, and HU boundaries bridge to the mock postflop families.
The mock exists so the product is fully walkable — every screen, every interaction — with zero infrastructure. It is not a strategy reference, and the ribbon makes sure it cannot be mistaken for one.
Testing
The mock layer is also the test substrate: the Vitest suite exercises view
models and components against it deterministically, with no network. Run
pnpm test (56 tests) and pnpm typecheck before any PR.