Commit Graph

13 Commits

Author SHA1 Message Date
builtbykev 76c289d4c1 Wave 5A: /u house-mode public profile (D2)
Reserved house handle (default 'vyndr', env HOUSE_HANDLE) resolves to the
PUBLIC model record — getModelAggregate() with no userId (user_id=NULL rows)
— WITHOUT a public_profiles row. It is the ONLY special case; every other
handle keeps the private-by-default, byte-identical-404 no-existence-leak
contract. The house profile is always public and never 404s (a fetch failure
degrades to an honest building state).

- src/routes/profiles.js: house short-circuit + sendHouseProfile (public
  aggregate + by_tier + public settled entries), reserved before the publish
  lookup so a user claim is shadowed.
- PublicProfile.tsx: house label 'VYNDR MODEL · PUBLIC RECORD' + hero/subtitle
  off data.house; keeps the CLV-VERIFIED record hero + TierRecord calibration
  + recent settled reads (misses included).
- opengraph-image.tsx (1200x630): house-branded eyebrow/heading.
- portrait/route.tsx: new 1080x1350 share crop (real aggregate or tagline
  fallback, never a fabricated number).
- Discoverability: 'VIEW AS PUBLIC PAGE ->' on the ledger MODEL header +
  'VIEW PUBLIC RECORD ->' under the landing ModelRecord, both to /u/vyndr.
- tests/unit/houseProfile.test.js: house resolves to user_id=NULL aggregate
  (no public_profiles row) + by_tier; unknown/unpublished user handles stay
  byte-identical 404; page renders house label + TierRecord + portrait crop.

3019 tests green (3012 -> 3019); next build EXIT=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:26:48 -04:00
builtbykev 1c681df5d3 DS1 (design): speed + trust bugs
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>
2026-07-12 19:25:32 -04:00
builtbykev 32d7200571 S7 (a1): newsletter — THE VYNDR REPORT
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>
2026-07-11 14:29:22 -04:00
builtbykev 1b4f2772d6 Session G (night2): Phase 6 — landing first-paint + content engine + OG
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>
2026-07-11 02:26:45 -04:00
builtbykev 80481d6f6a Session C (night2): the aggregator mounted — Explore hub, lens panels, one filter
- 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>
2026-07-11 00:47:01 -04:00
builtbykev d296e40cb6 Session 58: Phase 1 — Truth Infrastructure (2327 tests)
ledger_entries is live (migration 019 applied to prod, RLS + NULLS NOT
DISTINCT dedupe verified against the real database). Every grade now
persists, settles against the real result, and carries closing-line value.

- ledgerService: pipeline pre-grade upserts (public model record, user_id
  null, idempotent), closing capture on every snapshot (last write before
  game start = the close), settlement with SIGNED CLV (over = locked -
  closing; beat/faded/flat), 30d model aggregate with the hard n>=20 rule.
- Write paths: snapshotService -> ledger (priority path); Next /api/scan ->
  ledger for authenticated users only (anon never touches the public
  record). Refused reads write nothing and don't burn a scan.
- Honest refusal (work-order 1.5): no projection => insufficient_data,
  grade null, "INSUFFICIENT DATA - no read" UI. The web gradeAdapter no
  longer displays the line as the model projection (the audit's
  model==line / +0% edge degenerate); the card renders absent states.
  projectionFor is sport-aware (l5 -> l20 -> {stat}_per_90 -> xG).
- /ledger: MY READS | MODEL tabs; model header shows hit% + beat-close%
  only at n>=20, else RECORD BUILDING + live pending count. ModelRecord
  deferred-render strip on landing + player hero. CLV + outcome chips,
  revised_from_grade strikethrough (Phase 2.5 ready).
- SYNC (Task 5): thresholds vs SNAPSHOT_EXPECTED_INTERVAL (normal <1.5x,
  amber >=1.5x, STALE red >=3x) via /api/snapshot/summary.
- Phase 2.5 logged in specs/vyndr-roadmap.md (build after Phase 3).
- Data-semantics hardening: strict null-safe numeric parsing everywhere a
  market value is handled (Number(null)===0 would have fabricated lines).

Backend 2309 -> 2327 tests (201 suites), web build exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 21:34:26 -04:00
builtbykev d09a06c054 Session 55: Self-learning loop + real-time layer (2274 tests)
Product overhaul core — the two transformative, differentiated systems:

Self-learning loop (Phase 2): outcomeService settles locked snapshot grades
against real MLB Stats API results → hit/miss/push, rolling accuracy by grade
tier (30d window). Idempotent, injectable, unit-tested. New GET /api/accuracy +
/api/ledger/accuracy + internal settle triggers + cron hook. AccuracyBadge
(dashboard/scan/landing) is honest — "LEARNING" below MIN_SAMPLE, never a fake
number. Settled HIT/MISS chips overlay the live slate.

Real-time layer (Phase 1): Slate silent 60s auto-refresh (no flash, no wipe on
transient blips) + "SIGNAL LIVE · UPDATED Xs ago" freshness strip; Ticker LIVE
badge that flashes on fresh events.

Landing (Phase 3): TopSignals shows tonight's real top-3 A-rated grades + live
accuracy — the product shown, not described.

Founder pricing: FOUNDER_CODE_EXPIRY default 2026-06-30 → 2026-12-31 (had
lapsed, disabling every founder code + the ClaimMeter pitch). That expiry — not
a tier change — was the real cause of the 4 stripe test failures.

Backend 2255 (4 failing) → 2274 (all green; +19 new, +4 fixed). Web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 15:39:13 -04:00
builtbykev 1d83682cdb Session 35: Design system Phase D — core screens: Grade Result, Slate card, Scan, Terminal, Landing (1839 tests)
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>
2026-06-16 00:20:45 -04:00
builtbykev 907c7b17c1 Session 34: Design system Phase C — app shell, nav, routing, auth gate, footer, 404 (1818 tests)
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>
2026-06-15 23:27:58 -04:00
builtbykev 0538205fab Session 23: All-day intelligence layer — schedule, game lines, streaks, hot lists, stat filtering, ParlayAPI dead (1567 tests) 2026-06-12 11:16:58 -04:00
builtbykev beaf8b2a61 Session 17: Audit response — checkout 401 fix, hero prop 404 fix, Slate parsing fix, ALL tab cascade isolation, cookie/nav/footer/autocomplete polish (1438 tests) 2026-06-11 21:22:59 -04:00
builtbykev 1fa04dc776 Sessions 5-7a: 955 tests, deployment ready 2026-06-08 18:35:13 -04:00
builtbykev bfa8345ebf feat: Feature 3.1 — Landing page + blog + Phase 3 specs
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>
2026-03-22 09:43:38 -04:00