Final phase of the VYNDR 2.0 conversion (Sessions 33-39). Verify -> fix -> lock.
Frontend-only; zero backend changes.
§13 automated checklist: all PASS or FIXED.
- QA.1 token resolution FIXED: ProcessingGrade #00ffb8 -> var(--g-ap);
game/[id] sport literals -> var(--s-*). Remaining hex documented as intentional
(var-with-fallback, Next metadata, bespoke intel-surface shades).
- QA.6 glitch discipline: ZERO glitch on data components.
- QA.4/5/8/11/16/18 verified; QA.9 (cmd palette) documented deferred;
QA.17 (AI slop) flagged for Kev's manual browser review.
De-flake: soccerFeatureExtractorCascade hit Jest's 5s default under full-suite
load (falls through to live adapters on cache miss) -> jest.setTimeout(20000),
same family as the S32 pipeline test. Verified stable across 3 full-suite runs.
New: tests/unit/vyndrParityQA.test.js (17 tests locking the parity invariants).
Backend 1890 -> 1907, 146 suites, zero regressions (stable x3). Web build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
VYNDR 2.0 conversion, Phase F (mobile is the PWA we launch first). Frontend-only;
zero backend changes.
- BottomTabBar rewritten to the §6 5-tab spec: Slate/Terminal/Scan/Ledger/More,
with Scan as the prominent raised grade-green action. Shown for anon too (only
mobile nav). Integrated More bottom sheet (sheet-up, backdrop dismiss, 48px mono
rows). iOS safe-area + 44px touch targets.
- Nav hamburger retired on mobile (tab bar owns nav).
- globals.css mobile section: tab-bar hidden >=768, main bottom padding,
grade-hero 80px, terminal-grid stacks, game-lines horizontal scroll.
- PWA: manifest shortcuts (Slate/Scan/Terminal) + categories; viewport-fit=cover.
Gotcha: `as const` on the TABS array broke type-check (distinct literal types);
fixed with a shared TabDef interface.
19 new tests. Backend 1853 -> 1872, 145 suites, zero regressions. Web build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VYNDR 2.0 conversion, Phase E. Frontend-only; zero backend changes.
- lib/slateAdapter.js: parseAmericanOdds, detectBestLines, mapScheduleToGameCards
(best/worst line detection — the Bloomberg pattern).
- Reskinned the LEGACY GameCard's game-lines grid with best/worst highlighting +
SportBadge, keeping inline grading intact (a wholesale swap to the display-only
vyndr/GameCard would have deleted the slate's grading interaction).
- compare/invite/help/about: RouteStubs -> real design-system pages.
- login reskinned (scanlines, system voice, new Wordmark); pricing + ClaimMeter.
Honest scope: the full GameCard swap needs inline grading ported into the new
component first; profile/settings/blog/game-detail reskins are light/deferred.
18 new tests. Backend 1839 -> 1853, 144 suites, zero regressions. Web build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VYNDR 2.0 conversion, Phase D (the screens users touch). Frontend-only; zero
backend changes.
- GradeResultCard + ProcessingGrade (the core product moment): intel-surface
grade hero, signal breakdown, kill conditions, best-book strip, alt ladder;
sections self-hide when empty.
- lib/gradeAdapter.js maps engine output -> §7 contract and tier-gates content
(free teaser / analyst kill-conditions / desk alt ladder) so the new card
doesn't give paid content away.
- Scan result wired to ProcessingGrade->GradeResultCard, preserving scan limits,
parlay add, reads tracking, and noopener sportsbook deep-links.
- GameCard (Bloomberg best/worst line cells) built + tested.
- Terminal page replaces its stub with a real league-intelligence screen.
- Landing gets the founder-seat ClaimMeter.
Honest scope: live dashboard/Slate swap onto GameCard, scan input -> TerminalInput,
full landing rebuild, and the blurred-paywall polish (Phase G) are deferred to
keep working flows stable.
22 new tests. Backend 1818 -> 1839, 143 suites, zero regressions. Web build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VYNDR 2.0 conversion, Phase C (the frame every page sits inside). Frontend-only;
zero backend changes.
- Nav rewritten: new .wm Wordmark, mono uppercase links, More dropdown, search/
bell/read-meter/avatar, Ticker under the bar. layout main paddingTop 64 -> 96.
- Routing: web/src/lib/routes.js (GATED/OPEN/HASH_ALIASES, isGatedRoute,
resolveHashAlias). Client AuthGate bounces signed-out users off personal
routes to /login?next=. HashRedirect maps #scan/#terminal to real routes.
- Footer rewritten to system voice + Detroit signature; mounted globally in
layout (removed per-page dup).
- 404 converted to the north star (scanlines, crt-sweep, glitch wordmark, amber).
- Stub pages for terminal/compare/invite/help/about/notifications via RouteStub.
Honest reconciliations: auth gate is client-side (no auth-helpers pkg; session is
client-side Supabase); GATED narrowed to protect the free-scan funnel; did not
stub over existing real pages; redirect param is ?next= (what /login reads).
26 new tests. Backend 1792 -> 1818, 142 suites, zero regressions. Web build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- gradeSlateService writes grades:{sport} cache (closes content pipeline →
dataLevel full); fire-and-forget from oddsService.recordDownstream, gated
by shouldGradeSlate (off in test, GRADE_SLATE_ON_FETCH override)
- NFL/NHL wired: oddsService SPORT_KEYS/SPORT_MARKETS (correct the-odds-api
keys americanfootball_nfl/icehockey_nhl), proplineAdapter MARKETS, NHL
MARKET_MAP keys to avoid silent-zero
- rate limiting mounted on 8 public cached routers (odds/parlay 30/min,
rest 60/min)
- jsonlLogger writes to temp under test (no more dirtied tracked artifact);
5MB pipeline test given 20s timeout
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add NFL keys to oddsNormalizer.MARKET_MAP (defensive; same silent-zero
class as the Session 30 MLB bug) + NFL surface test
- npm audit fix: ws/qs + Supabase transitives, 7 vulns -> 0 (semver-safe)
- Audit findings documented in BUILD-STATE: grades cache has no writer,
NFL/NHL not wired end-to-end, rate limiting only on /analyze, tests
mutate a tracked jsonl, leaked GitHub PAT in origin remote (rotate)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Subscription billing:
- POST /api/stripe/checkout — creates Stripe Checkout session
- POST /api/stripe/webhook — handles lifecycle events (raw body)
- POST /api/stripe/portal — customer self-service management
- GET /api/stripe/status — current subscription info
Founder code system:
- Validates codes against env var list with expiry date
- Routes to founder Stripe Price IDs (locked rate for life)
- 4 price objects: analyst, analyst-founder, desk, desk-founder
Webhook events handled:
- checkout.session.completed → tier update + founder status
- customer.subscription.deleted → revert to free tier
- invoice.payment_failed → logged
Lazy Stripe init (no API key required at import time).
Raw body middleware for webhook signature verification.
16 new tests, 237 total (210 Node.js + 27 Python), all passing.
Phase 3 Web MVP COMPLETE. All roadmap features shipped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Next.js 14+ web app in web/ directory:
- Landing page: Hero, How It Works, Features, 3-tier Pricing with
founder badges, Footer with email capture
- Blog system: MDX-powered, /blog index + /blog/[slug] pages,
reading time, Open Graph tags, JSON-LD structured data
- Auth pages: /login + /signup (Supabase Auth ready)
- Design system: dark theme, grade colors (A/B/C/D), BetonBLK voice
- 1 seed blog post: "How to Read Line Movement Like a Sharp"
- Specs for 3.2 (Scan UI), 3.3 (Bet Tracker), 3.4 (Stripe)
Build passes clean: 7 static pages generated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three submission methods:
- POST /api/bets/quickslip — structured bet entry
- POST /api/bets/screenshot — stub OCR with confirm flow
- POST /api/bets/sync — coming soon stub
Full bet lifecycle:
- PATCH /api/bets/:id/settle — settle with outcome, recalculates performance
- GET /api/bets — list with status/book/pagination filters
- GET /api/bets/performance — ROI, win rate, profit (weekly/monthly/all_time)
Payout calculator handles straight bets (American odds) and parlays
(multiplied leg payouts). Performance service recalculates on each
settlement and upserts into performance table.
33 new tests, 221 total (194 Node.js + 27 Python), all passing.
All backend features for Phase 1 + Phase 2 now complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Line movement system:
- Baseline capture on first odds fetch of the day
- Movement detection >= 0.5 points with direction (up/down)
- Sharp money heuristic (sharp_action/public_action/unknown)
- GET /api/movements with player, stat_type, min_movement filters
- Movements included in GET /api/odds/nba live responses
Cascade detection system:
- Scratch detection: player props disappear from 2+ books
- Affected user lookup via scan_sessions + picks
- Parlay re-grade without scratched legs
- cascade_alerts created for affected users
- GET /api/alerts (Analyst/Desk only), PATCH /api/alerts/:id/read
Zero extra Odds API credits — all detection piggybacks on existing fetches.
Migration 002: line_baselines, line_movements, cascade_alerts tables.
30 new tests, 188 total (161 Node.js + 27 Python), all passing.
Phase 2 Core Product COMPLETE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core intelligence for BetonBLK prop analysis:
- POST /api/analyze/prop — single prop analysis
- POST /api/analyze/batch — multi-prop analysis for parlay scanner
- 6-step pipeline: season avg → recent form → situational splits →
cross-book lines → kill conditions → grade (A/B/C/D)
- 6 kill conditions: low_minutes, small_sample, b2b_high_usage,
blowout_risk, split_conflict, no_opponent_data
- Composite scoring with confidence (30-95), bonuses, penalties
- Added spreads market to Odds API fetch (zero extra credits)
- Full reasoning output with step-by-step breakdown
36 new tests (unit + integration), 128 total across all features
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>