Part 6 #8 — Desk $44.99 is the hero tier. New DeskShowcase leads the
pricing page with the "$1M terminal · $44.99" story + real feature
visuals (alt-line ladder, quarter-Kelly, parlay φ, real-time feed) in a
balanced two-column layout (kills the dead right-half). Desk is the sole
highlighted tier / single primary CTA (color contract #9); Analyst is
secondary. Real prices: Free 5 scans, Analyst $14.99/$19.99, Desk
$34.99/$44.99. ClaimMeter + Stripe checkout wiring untouched.
Part 4 #7 — Ticker → punctuated stillness. The continuous marquee is
retired; the ticker now RESTS ≥4s on each ranked item and pulses only on
change. The EKG heartbeat is a static readout — the header's ONE idle
proof-of-life is the single SIGNAL-LIVE live-dot (the ticker dropped its
competing pulse). All durations tokenized (--motion-*, --ticker-hold);
prefers-reduced-motion kills the motion entirely.
Part 8 #20 — new EmptyState component modeled on the north-star 404
(scanlines + glitch wordmark + amber system voice + CTA hierarchy),
reused at the bare-red "Team not found", "Game not found", and the
ledger empties — one unified voice.
Part 5 — archetype glyph+chip propagated to STREAKS rows + ledger rows
(optional + self-hiding; absent beats fabricated); grade reveal already
carries ArchetypeBlend.
Tests: new tests/unit/ds5PricingStates.test.js (22) locks all four
workstreams; updated teamHubUI + vyndrDesignSystem for the new surfaces.
237 suites / 2864 tests green; next build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DESIGN-SPEC Parts 3 + 6 (audit #1, #13, #14). The founder's named #1 rebuild.
slateAdapter.js — the testable engine:
- selectTopGrades: rank tonight's grades by tier → confidence → edge so the
row varies on a real signal, not identical-weight noise (#13).
- buildHeroReceipts: yesterday's PROVEN A-tier settled HITS (misses excluded),
carrying the real result — the never-empty proof source (#1, Part 6).
- heroFallbackState: tonight wins, else receipts, else empty.
- pendingSummary: collapse an all-awaiting card's six "Grades post …" rows to
ONE line count (#14).
- topReadForCard: the single best live graded read to promote (#2).
GameCard.tsx — ONE bold hero per card (large mono/tabular grade + player, rest
demoted); all-awaiting cards render one "N props pending · grade ~X ET" line via
nextRunLabelET instead of repeated filler. Real team logos + team-colored accent
already lead the card (DS0) — preserved.
dashboard/page.tsx — Top grades tonight ranked via selectTopGrades (+ % CONF the
varying signal); when tonight is empty, fetch /api/ledger/model and fall back to
yesterday's PROVEN A-tier receipts (✓ HIT + actual + CLV) so first paint always
proves the model. Honest nextRunLabelET copy kept for the truly-empty case (QA.22).
Tests: tests/unit/ds2Dashboard.test.js (21) — pure-fn + source assertions,
fail-before / pass-after. Full suite 237 suites / 2863 tests green (+21).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OAuth-only 'sb-token' localStorage key was read by profile, slip,
dashboard (recent-scans), settings, and tracker for their authenticated
fetches. Email/password users never had that key, so those fetches sent
no Authorization header and silently returned nothing.
- web/src/lib/authToken.js — currentAccessToken() reads the REAL Supabase
session (sb-<ref>-auth-token, v2 top-level or v1 currentSession), legacy
fallback. CommonJS so Jest can unit-test it (5 tests).
- Swept all 5 pages to the helper (scan already session-first from DS1).
- lib/api.ts (0 callers) + ParlayTray (unmounted) left as dead code.
236 suites / 2842 tests green, next build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both sessions fixed the negative-edge-green bug; kept DS3's centralized
colorContract.edgeColor helper (removed DS4's local const), updated DS4's
source-assertion test to match. Same semantics, one enforcer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signal-green #00D4A0 now means exactly ONE thing (edge/active/A-tier/CTA),
locked by tests that fail on violation. Part 1 of DESIGN-SPEC v2.
- web/src/lib/colorContract.js — pure CommonJS helpers: edgeColor(value)
colors edge/CLV/delta by SIGN (neg=var(--miss), pos=var(--g-a), 0=neutral);
gradeTierColor() (A/A+ green, B blue, C amber, D/F red, in lockstep with
vyndrTokens.gradeColor); gradeGlows() (A/A+ only); deltaE()/isSignalGreen()
CIE76 gate so no archetype hue dilutes the signal.
- GradeResultCard: edge confidence-strip + EDGE row route through edgeColor
(a -33.3% edge was rendering GREEN — audit #3); grade-hero glow gated to
A/A+ via gradeGlows (a glowing C devalued the cue); VYNDR INTELLIGENCE
panel de-flooded (neutral border, Form/Rest neutral not green — #15).
- LiveHeroProp: negative edge now muted red, not neutral (sign completeness).
- Archetype dedup off signal-green (both archetypes.js + archetypeService.js,
kept matched): DUAL THREAT/MOTOR #00D4A0, MIRROR #34D399, ARTILLERY/RANGE/
GHOST/BLADE #2DD4BF, BRUSH #3DDC84 shifted to distinct non-green hues (all
ΔE>=44 from #00D4A0). Within-sport uniqueness preserved.
- tests/unit/colorContract.test.js — 21 tests: helper units + source-grep
violation locks + archetype-green-dedup. QA.20-22 kept green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Semantics preserved (step-up amber / step-down green); DS4 moved the
coloring into a sev object, so the source-lock regex is updated to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P0 billboards (the timeline is customer #1). Pixel-level craft: one bold hero
among muted context, real entities from DS0.
- STREAKS row: rebuilt from a log line into a Bloomberg alert. Streak LENGTH is
now the mono/tabular HERO (38px, the largest figure in the row); real
PlayerAvatar identity; muted "built vs [opponents]" lens; ONE severity accent
(step-up amber / step-down green); grade badge tier-gated (the READ is paid).
- Grade reveal: edge is now sign-colored — negative edge uses var(--miss),
never green (color contract #3). Fixed in BOTH the confidence strip and the
MODEL/LINE/EDGE row; that row is now mono + tabular. Letter stays the hero.
- CLV reframe: new pure lib/clvDisplay.js (clvMode flat/spread/none). A near-
flat distribution (73/74) now renders a confident VOICE line — "CLV flat — we
grade the outcome, not the close" — instead of a broken-looking histogram;
bars show only on real spread.
- /u profile: hit% is the bold record hero (56px mono tabular) + beat-close
secondary; honest CLV-VERIFIED badge (only when closing value is tracked);
real PlayerAvatar identity on cards; OG image elevated to carry the real
record at 1200x630 social crop (graceful tagline fallback).
Tests: +23 (tests/unit/ds4Billboards.test.js). Full suite green (2732).
web build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the three DESIGN-SPEC Part 4 + #17 audit findings.
1. React #418 hydration mismatch (landing → dashboard entry). The
`maybeSignedIn` value was computed in a useState INITIALIZER that reads
localStorage during render: server (no window) → false → emits the
marketing tree; a signed-in visitor's first CLIENT render → true → emits
the loading placeholder. Whole-subtree server/client mismatch → React
discarded and re-rendered the page. Deferred behind a mounted flag so the
first client render matches the server; the stored-session check flips
post-mount. SSR HTML is no longer discarded.
2. Loading walls → skeletons. New tokenized Skeleton primitive
(.vyndr-skeleton, reduced-motion-safe via the global rule). Swapped into
every text-wall loader: dashboard slate load ("Loading the slate…"), /desk
("Assembling the pack…"), /ledger ("Loading…"), scan ("Loading the model…"),
and the landing redirect placeholder. No bare text loader remains.
3. scan→ledger persistence. Root cause: the scan page read its bearer token
from localStorage['sb-token'] — a key written ONLY by the OAuth callback —
so email/password users posted /api/scan anonymously and the ledger write
(gated on an authed user) was silently skipped. Now uses the authoritative
session.access_token (matching the ledger read path). Extracted the row
builder to web/src/lib/ledgerRow.js (shared, testable).
Tests: +17 (scanLedgerPersistence write→mine round-trip + scope + idempotency;
ds1SpeedTrust hydration/skeleton/persistence source invariants). Full suite
233 suites / 2793 green; web build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Founder's #1 priority. A single cached asset+rendering system, swapped into
every surface, so entities stop being flat gray strings (DESIGN-SPEC Part 2).
- web/src/lib/teamMeta.js: static registry for ALL 4 sports — 30 MLB, 30 NBA,
13 WNBA teams + 48 World Cup national teams, each with real colors + the
ESPN logo/flag CDN abbr. resolveTeam (abbr/full-name/nickname/alias),
teamLogoUrl (statsapi->ESPN mapping: AZ->ari, CWS->chw; soccer via the
countries/ flag CDN), accentColor (picks the VISIBLE color of the pair so a
#000000 primary never renders an invisible accent on #06060B). Colors +
abbrs sourced once from ESPN's team API — stable public facts, zero-latency
static data, no paid dependency.
- TeamLogo: real ESPN-CDN logo with a team-colored MONOGRAM fallback (never a
gray box / bare abbr). PlayerAvatar: real headshot with a team-colored
monogram fallback (kills the gray silhouette). BookWordmark: brand-color
wordmark, proper casing (DraftKings, not 'draftkings').
- Swapped into the class-level shared components so it propagates to ALL
surfaces: GameCard (team logos + team-colored accent edge), StatStrip
(player identity avatar), StreaksPanel (P0 billboard avatars), TeamHub
header (the team's real crest leads its hub). Barrel-exported.
ZERO OUT-OF-POCKET: ESPN logo/flag CDN + league headshot CDNs, all verified
200 image/png across MLB/NBA/WNBA/soccer.
ACCEPTANCE (SSR render proof): /entity-demo harness server-rendered the exact
real asset URLs across all 4 sports — mlb/500/nyy.png, mlb/500/chw.png (White
Sox, correct ESPN abbr), nba/500/lal.png, wnba/500/ny.png, countries/500/
usa.png + bra/eng/arg/jpn flags, real mlbstatic/nba headshots (Judge 592450,
LeBron 2544), DraftKings/FanDuel wordmarks. Each URL curl-verified 200
image/png. Harness removed post-proof (not a product surface). Pixel
screenshot blocked by WSL2<->Windows-Chrome localhost networking, not code.
2757 -> 2776 tests (+13 entityLayer, +6 boot resilience), web build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ROOT CAUSE: the Dockerfile copied src/poller/scripts/supabase but NOT
content/. mediaEngine.js read content/stark-lines.json with an unguarded
module-load readFileSync; ENOENT in the image threw at require time, and
via app.js → routes/desk → deskService → mediaEngine that crashed the
ENTIRE API at boot. The Coolify healthcheck rolled back to the last
healthy image (4d2b27d), so every deploy since 219167e silently served a
14-hour-old build — S11 live tracking, S6 API code, the settlement boot
line, and SNAPSHOT_EXPECTED_INTERVAL were all merged but NOT running.
FIX (one train):
1. Dockerfile COPYs content/ into the runner image.
2. mediaEngine: stark-lines.json is OPTIONAL (garnish, never load-bearing)
— loadStark() try/catch → {} → posts render without the Stark kicker,
never a crash. Belt AND suspenders with #1.
3. src/preflight.js (§A4): boot prints '[preflight] OK' or 'DEGRADED'
naming exactly what content/env is missing — before the healthcheck
can fail silently. Run first in server.js.
4. Full fragility sweep: mediaEngine was the ONLY unguarded module-load
file read; coachSignals (config/coaches.json) was already lazy +
try/catch + copied. No others.
Verified: requiring app.js + deskService + mediaEngine with
stark-lines.json ABSENT now boots clean (reproduced the exact prod
failure). 2757 -> 2763 tests (tests/unit/bootResilience.test.js).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The session-opening doc: 3 trains shipped (2757 tests), live infra map,
pending Coolify env vars + entity placeholders, honest open items.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MLB statsapi + WNBA ESPN live boxscores -> per-player current values
(live:{sport}:{date} TTL 90s, /api/live/:sport + Next proxy). Pure
propState math (HIT / ON PACE / NEEDS N / HOLDS / LINE PASSED — never
red in-progress), attachLiveProgress strip join on nameKey+statType,
proximity-to-hit slate float, StatStrip LiveTracker in the ROW-GRAMMAR
outcome slot (spec amended + lock test updated). Grades never change
in-game — tracking, labeled as such. 2698 -> 2757 tests, web build 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- specs/ROW-GRAMMAR.md: the row grammar law (slot order, color law, mark
law, mobile stacking, no-truncation), locked by tests/unit/rowGrammar.
StatStrip violations fixed: MovementChip before the grade (market
context before model output); ViabilityChips after the archetype
(identity is one contiguous run).
- Line-movement sparklines: intradayRefreshService.trackHistory captures
real {t,line} points per grade (seeded with the lock, deduped when
flat, capped 24) inside the snapshot write-back; StatStrip.LineSparkline
renders at >=3 points (green toward / amber against / dim flat).
- Last-10 dot strips: services/last10Dots (streaksService accessors) ->
/api/snapshot/:sport attaches last10_dots from rosterlogs:{sport};
StatStrip.DotStrip renders vs the LOCKED line, newest first.
- CLV distribution: getModelAggregate emits clv_distribution (7 signed
buckets, outliers clamped) only past the centralized n>=20 gate;
ledger MODEL header renders the bar strip.
- Global search: SearchModal (cmd-K via GlobalHosts + window.__search),
players via /api/players/search per sport + static lib/teams.js
(soccer deliberately absent); Nav search icon + Search first in the
mobile More sheet. Explore tab untouched.
- Landing LCP: fade-up floored at opacity .6 (hero h1 contentful on
first frame, S33 visible-floor rule) + IBM Plex Mono preload:false
(4 decorative font files off the slow-4G critical path).
2654 -> 2698 tests (226 suites) green; web build exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- lineupService: statsapi hydrate=lineups (live shape verified) →
CONFIRMED (batting slot) / NOT_IN (team posted without the player) /
PROJECTED (not posted). 10-min cache, pure parser, injectable.
- NOT_IN visibly KILLS the grade on the slate: struck through + NOT IN
LINEUP chip, parlay/book actions suppressed. The locked ledger read is
untouched — honesty is showing the read is dead, not deleting it.
- injuryService: ESPN injuries feed → OUT/GTD/PROB chips (unknown status
→ no chip, never invented). Chips on slate strips via ViabilityChips.
- Date navigation on the Slate: YESTERDAY (results surface — finals +
THE SETTLE panel of that date's settled reads w/ outcome + CLV chips,
via new ?date= filter on /api/ledger/model) / TODAY / TOMORROW
(schedule until lines post). Odds/grades/pitcher layers are TODAY's
and never fake other dates; 60s poll only refreshes today.
- Routes /api/schedule/:sport/lineups + /injuries + Next proxies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tesseract.js (self-hosted WASM, Apache-2.0) + pure per-book layout
parsers (DK/FD/MGM/Caesars) with per-field confidence and needs_review
honesty — the reader never guesses. POST /api/slips/parse (auth, free
1/day paid 10/day, 4MB cap) + Next proxy. Gated /slip page: upload or
paste, manual-correct UI, per-leg grades through the normal engine
(refusals render honestly), add-all to Parlay Lab, share card. Vision
model upgrade logged post-revenue. 2574 -> 2608 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4a VOICE v1.1 committed (board start); lint is EXECUTABLE — banned list +
no-exclamation law enforced in the engine (throws in test, drops in
prod) and locked by tests. Curly-apostrophe variants covered.
4b mediaEngine: deterministic templates (MORNING WIRE, SIGNAL, STREAK
WATCH, THE SETTLE, RECEIPTS, ARCHETYPE WATCH, LINE DISPATCH) filled
ONLY from snapshot/ledger/streaks JSON. Record percentages never render
under n>=20 (counts + 'Record building' below). Stark layer = curated
committed library (content/stark-lines.json), day-rotated selection —
selected, never generated.
4c /desk (founder-only: requireAuth + DESK_OWNERS email allowlist,
deny-by-default): all formats as text + <=280-char pre-segmented tweets
with per-tweet copy buttons + char counts, wire/numbers-only variants,
DATA BRIEF block (structured day numbers) with copy-for-claude.ai.
ntfy ping after the day's first snapshot: 'Desk pack ready'.
4d ghostPublisher: DRAFTS ONLY (status:'draft' test-locked), env-gated
no-op, HS256 JWT via node crypto (zero new deps). POST
/api/internal/ghost/drafts saves slate preview + settle drafts.
Nothing anywhere auto-posts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Email capture + daily report assembly + operator-triggered Listmonk send.
- NewsletterCapture (dark terminal, mono) on landing (below FAQ) + /welcome
(the real signup success surface); double-opt-in note; 'Signups open soon'
when Listmonk env is unset.
- POST /api/newsletter/subscribe: public, 10/min IP limit, honeypot,
server-side email validation, forwards to Listmonk subscribers API with
preconfirm_subscriptions:false (Listmonk sends the confirmation).
No env -> calm 200 { ok:false, reason:'not configured' }. Next proxy
web/src/app/api/newsletter/subscribe/route.ts (S25 rule).
- newsletterService.buildDailyReport: signals from snapshot:{sport}:latest,
STREAK WATCH via rosterLogs -> streaksService -> streakLens, THE RECORD via
ledgerService.getModelAggregate (percentage only when hit_pct != null —
n>=20 gate — else 'RECORD BUILDING · N pending'). RG footer (21+,
1-800-GAMBLER, Listmonk-native {{ UnsubscribeURL }}) in html + text.
VOICE v1.1 lint locked by tests: no '!', no banned vocabulary, numbers
only from injected pipeline data.
- sendDailyReport: creates + starts a Listmonk campaign; env-gated no-op;
refuses an empty report. Deliberately UNSCHEDULED — only
POST /api/internal/newsletter/send (internal key) triggers it.
- docs/NEWSLETTER.md: box-side Listmonk runbook (install, double-opt-in
list, API user, Coolify env, test-send).
- Spec: specs/feature-a1-s7-newsletter.md.
Tests 2398 -> 2429 (207 suites, all green); next build exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PROMISE-AUDIT.md: every /pricing claim → verified/built/reworded.
BUILT (was vapor): alt line ladder + edge ranking (same-features regrade
at shifted lines, Desk-gated at the API), quarter-Kelly (engine quantile
P(win) x real captured odds — either missing → no sizing), free-tier
kill-condition locked previews. FIXED (was false): analyst 15/day cap vs
the Founder 'Unlimited reads' promise → analyst unlimited; every '40+
factors' claim (real count: 22 named features) reworded truthfully in 7
files. VERIFIED: cascade alerts (real, wired), phi correlation,
leg history, cross-book comparison, WC soccer, real-time feed.
Locked by tests/unit/promiseAudit.test.js. Jest now ignores
.claude/worktrees (parallel agents' suites no longer leak into runs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1. [settlement] armed boot line — settlement WAS scheduled (the tick runs
settleAllOutcomes + settleAllLedgers FIRST at every snapshot slot,
src/snapshotScheduler.js) but was invisible at boot. Now verifiable
from logs forever.
2. Null locked_odds root cause (4 day-one rows, all betmgm SB unders):
genuinely absent in the DB — the first-seen book row carried no juice
for that side. indexOdds/indexProps now PREFER a book row with both
sides priced (still a real row, never synthesized). Display was
correct: absent beats wrong.
3. Verified retry-safe: 'Odds data temporarily unavailable' is the
odds-api quota gate (oddsService:423) — thrown BEFORE any spend or
cache write; runSnapshot errors write nothing; the next cron retries
fresh with PropLine first.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- QA.20: one meaning per color, locked — STEAM amber (caution), VALUE
green (edge), red only at STALE/miss; revisions render the original
grade struck through on slate AND ledger.
- QA.21: every new data surface (PriorReads, PlayerStreaks, ModelRecord)
renders data in mono (tabular-nums via the .mono class).
- QA.22: waiting copy derives from the real cron schedule on every
surface (StatStrip, dashboard, Explore) — no passive mystery anywhere.
- Explore empty state joined the honest-waiting lib.
- Sweep result: no raw brand hexes and no sans-serif data in tonight's
components (sans only on prose/captions/hero letter — sanctioned);
glow confined to A-tier badges + reveal chrome; 390px containment
holds (wrapping chips, scroll-contained tables from Phase 3 rules).
Backend 2352 -> 2396 tests (205 suites), web build exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.1 FIRST-PAINT ROOT CAUSE: the landing blocked its ENTIRE render on
Supabase auth init ('loading || user') — anonymous visitors stared at
'LOADING THE SLATE' for the whole auth roundtrip (~3-4s). Now a
synchronous localStorage session check gates the suppression: only
visitors who actually hold a session (and will redirect) wait;
anonymous traffic paints the hero immediately. Full RSC conversion of
the hero is deferred and logged — the blocker itself is dead.
Proof Strip rules: top-3 by grade whatever they are; 'TONIGHT'S TOP
SIGNALS' only with >=1 A-tier, else 'TONIGHT'S BOARD'; nothing graded
yet → yesterday's SETTLED reads with outcome chips (misses included).
6.2 Content routes: /api/content/top-signals/:sport,
/streak-watch/:sport (the zero-grade daily format off the aggregator),
/daily-report/:sport — built but self-flagging do_not_post until the
record clears n>=20. Flag, don't fake.
6.3 Per-player OG images: app/player/[name]/opengraph-image.tsx (Node
runtime per the S53 rule) + server layout generateMetadata — every
shared player link unfurls as an intelligence card.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.1 Archetype defined on-page: one line from the archetype library under
ARCHETYPE DNA (BOMBER — elite raw power…); expander keeps the long form.
5.2 VYNDR-on-team live: getModelAggregate team scope (migration-020 column)
+ /api/ledger/model?team= + ModelRecord mounted on the Team Hub header.
5.3 WNBA/NBA profile parity: minutes-based usage (+MIN season cell) when
the feed carries minutes — absent beats invented.
5.4 Settings read meter: real rolling-24h usage from the SAME store the
limiter enforces (GET /api/user/scan-meter + proxy). Metered tiers see
'X of N reads today' + bar; unlimited tiers see nothing. Corrects the
stale '5 scans / month' copy.
5.5 Per-tier calibration on the MODEL tab: A+/A/B/C chips with hit% at
n>=20 PER TIER, 'building (n/20)' below — the separation between tiers
is the proof the grades mean something.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.1 ROOT CAUSE of 'Ohtani returns nothing': no backend
/api/players/search existed (MLB 404'd; NBA/WNBA hit the offline
Python service). New Express route + mlbStatsAdapter.matchPlayers —
canonical nameKey fuzzy match (exact > last-name prefix > folded
substring). LIVE-VERIFIED vs the real 1,299-player list: Ohtani /
Aaron Judge / Sánchez / sanchez / Chisholm Jr all resolve; accented
and unaccented return identical results. Non-MLB matches the
platform's cached names (rosterlogs + grades), cache-only.
4.2 Reveal choreography per §7: analyzing steps → DECLASSIFIED stamp →
90ms-staggered context panels (entrance floors visible per the
Phase-0 rule); prefers-reduced-motion skips straight to the card.
4.3 PRIOR READS chips on scan results — the model's public ledger
history for the player (deferred-render, outcomes + pending, never
invented). /api/ledger/model gains ?player= on entries.
4.4 Parlay Lab: humanized stat labels via the ONE shared formatter
(lib/gradeAdapter.statLabel); 1-leg provisional grade ('Leg grade:
B — add a leg for the combined read'); discoverable entry — Nav
'Parlay Lab' item opens the drawer via window.__openParlay, and the
open drawer now renders an honest empty state at 0 legs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Odds-only refresh every 20 min during slate hours (noon–midnight ET),
in-process (INTRADAY_REFRESH=0 kill switch; skips full-snapshot slots):
- signed delta RELATIVE TO THE GRADED SIDE; direction is the signal.
- WITH the grade → STEAM badge, never a re-grade.
- AGAINST >=1.0 → re-grade THAT PROP ONLY at the real current line:
holds/refuses → VALUE (better entry, same read); drops → PUBLIC revision
(grade updates, revised_from_grade preserves the ORIGINAL forever, UI
strikethrough on slate strips + ledger cards).
- Every run recaptures closing_line/odds → the close is now refresh-
fidelity; SYNC goes live by dropping SNAPSHOT_EXPECTED_INTERVAL to 1200.
- Ticker MOVE events feed from the refresh (>=1.0 moves).
- QUOTA MATH: 36 runs/day/sport x 4 sports <= 144 PropLine calls/day vs
9,000/day free capacity (3 keys x 3,000). Re-grades are internal compute.
- POST /api/internal/refresh/all for manual runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- StreaksPanel renders THE LENS (interpreted read + tonight difficulty) +
snapshot grade letters; streaks are FREE for every tier per the product
definition (the picture is free, the read is paid) — only hot lists keep
the free top-3 gate.
- One stat selection now filters ALL layers: card props narrow together
with streaks + hot lists (Slate activeStat → slateGameToCardData).
- /explore = server SEO shell (daily-indexable: 'MLB Hit Streaks, Hot
Hitters & Stat Leaders') + ExploreHub client: leaders + full streaks +
hot lists, real-tier gated, defaults to the in-season sport.
- Landing teaser fixed: it pointed at NBA (off-season → self-hid forever);
now MLB top-3 through the lens — the anonymous-visitor hook actually
shows.
- Player dossier: ACTIVE STREAKS block (free, lens reads) via new
?player= filter on /api/streaks/:sport.
- Schedule layer already the Slate base (mergeSlate) — verified, no change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RESURRECT verdict: the Session-23 engine existed, was pure, tested, and
mounted — it starved because every producer was external and unarmed
(tank01-prefetch via n8n, offline Python flow). The snapshot pipeline is
now the producer: each run merges slate players' real game logs (already
fetched for archetypes — zero extra calls) into rosterlogs:{sport}.
- computeFormHeat: hot hitters (7d AVG), hot sluggers (SLG), hot shooters
(FG%) — correct sum/sum rate math, season baseline else prior stretch,
min-sample refusals, never extrapolated.
- streakLens: no raw streak renders alone — built-vs opponents, tonight's
matchup + opposing SP w/ ERA, step-up/step-down difficulty, one-line
read. Absent context = say less, never invent.
- /api/streaks/:sport: heat merged into the feed, lens applied from cached
schedule + pitchers (time-bounded, can never hang the route), snapshot
grade letters joined.
- ACCEPTANCE (live statsapi, real 2026 logs): 32 rows found — Turang
12-gm on-base, Reynolds 8-gm on-base, Pratt 7-gm on-base + 3-gm
multi-hit, Meidroth 5-gm on-base, Cortes 5-gm on-base.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overnight cron gap (03→14 UTC) is 11h; a 6h snapshot TTL meant the
morning settle pass read an expired snapshot and the accuracy loop
silently settled nothing. Ledger (Postgres) settle was immune.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>