Session 38: Design system Phase G — living layer, i18n/odds, a11y, paywall, parlay math (1890 tests)
VYNDR 2.0 conversion, Phase G (the systems that make the design alive). All 5 wired. Frontend-only; zero backend changes. - lib/parlayMath.js: correlation model (0.62/0.34/0.06/0) + parlayGrade penalty + grade->odds + combined odds (frontend; backend parlayService unchanged). - lib/oddsFormat.js: fmtOdds across american/decimal/fractional/implied with the totals-pass-through rule (safer than the prototype's parseAm, which would mis-convert 228.5) + region presets. - lib/prefs.js: applyPrefs sets <html data-*> (the S33 a11y CSS layer) + load/save. - lib/liveTick.js: single tick engine (SSR/test-safe, no auto-start, fresh state). - lib/checkout.js: checkoutUrl(plan). - LiveLayer (useLive/LiveNumber/HeartbeatBar) under the Nav ticker; GlobalHosts in layout applies prefs + registers __prefs/__goPaywall/__checkout + hosts the Preferences and Paywall modals. Nav read-meter is now a paywall trigger. Gotchas: useEffect can't return a Set.delete unsub directly (boolean != cleanup); header grew to 124px so layout paddingTop + Slate sticky-top updated to match. 18 new tests. Backend 1872 -> 1890, 146 suites, zero regressions. Web build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -254,6 +254,28 @@ The frame every page sits in. Frontend-only.
|
||||
Use a shared interface. And the build worker exits code 1 on type errors —
|
||||
check the build EXIT CODE, not just a `| tail` of its output.
|
||||
|
||||
## VYNDR 2.0 Systems (Session 38 — Phase G)
|
||||
Testable CommonJS modules in `lib/` + thin React glue:
|
||||
- **`lib/parlayMath.js`** — frontend correlation model (player 0.62 / team 0.34 /
|
||||
league 0.06 / cross-sport 0), `parlayGrade` penalty, grade→odds. Backend
|
||||
parlayService (S28) still owns server combined odds.
|
||||
- **`lib/oddsFormat.js`** — `fmtOdds(value, format)`. CRITICAL: only signed-int
|
||||
strings + integer numbers are odds; totals/lines/spreads pass through UNCHANGED
|
||||
(`parseMoneyline`). This is intentionally stricter than the prototype's parseAm
|
||||
(which would mis-convert 228.5) — keep it that way.
|
||||
- **`lib/prefs.js`** — `applyPrefs` sets `<html data-*>` (the S33 CSS layer keys
|
||||
off these), load/save to `localStorage('vyndr_prefs')`.
|
||||
- **`lib/liveTick.js`** — single tick store; never auto-starts (SSR/test-safe),
|
||||
`start()` runs the unref'd 1s interval, `tick()` emits a fresh state object.
|
||||
- **`lib/checkout.js`** — `checkoutUrl(plan)`.
|
||||
- **`components/vyndr/LiveLayer.tsx`** (`useLive`/`LiveNumber`/`HeartbeatBar`) +
|
||||
**`GlobalHosts.tsx`** (mounted in layout — applies prefs, registers
|
||||
`window.__prefs`/`__goPaywall`/`__checkout`, hosts Prefs + Paywall modals).
|
||||
- Header is now 124px tall (nav 60 + ticker 32 + heartbeat 30): layout `main`
|
||||
paddingTop = 124, Slate sticky `top` = 122. Keep them in sync if header changes.
|
||||
- GOTCHA: don't return a `Set.delete`-based unsub directly from `useEffect`
|
||||
(returns boolean ≠ valid cleanup) — wrap as `() => { unsub(); }`.
|
||||
|
||||
## Active Skills
|
||||
- vyndr-voice (all user-facing output)
|
||||
- prop-analysis (grading methodology)
|
||||
|
||||
Reference in New Issue
Block a user