Session 57: Phase 0 — Kill the Lies (2309 tests)

Work-order Phase 0 (Jul 10 live audit): every fabricated UI element deleted
or rewired to real data. Deletion sprint — no new product features.

0.1 Fake NBA game: root cause was scheduleService fetching the ESPN
    scoreboard with no ?dates= param or date filter — off-season ESPN
    returns the NEAREST slate (Jun 13 NYK@SA Finals rendered as tonight).
    Now pinned to the requested ET date + defensive filter; undated events
    dropped. Honest month-aware per-sport empty states (lib/emptyState.js).
0.2 Fake header counters: liveTick stripped to a bare 1s pulse (the
    auto-incrementing "247 graded", sin-driven brain-%, aPlus/cascades are
    dead). New GET /api/snapshot/summary (cache-only, before /:sport) +
    Next proxy; HeartbeatBar shows the real graded count and SYNC =
    elapsed since the last pipeline run (amber past 5 min).
0.3 Ticker: hardcoded fallback items deleted (real snapshot exhaust only);
    MOVE kept — computeLineDeltas is real movement. <4 real items → no
    ticker; bar publishes --ticker-h so the fixed header collapses cleanly.
0.4 /terminal retired: route redirects to /dashboard; VVI/injury-wire/
    leaders layouts preserved unrouted as §12 content-engine templates.
    Nav PRIMARY = Slate/Scan/Ledger; BottomTabBar Terminal→Explore; PWA
    shortcut Terminal→Ledger; #terminal alias → /dashboard.
0.5 ›Query nav pill deleted (duplicate /scan link).

Backend 2289 → 2309 tests (199 suites), web build exit 0.
Spec: specs/phase-0-kill-the-lies.md. Next: work-order Phase 1 (ledger
persistence + settlement).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-10 20:17:49 -04:00
parent 2ae8a5697e
commit c8790fde55
27 changed files with 891 additions and 311 deletions
+23
View File
@@ -0,0 +1,23 @@
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3000';
/**
* Snapshot summary proxy (Session 57, Phase 0) — forwards
* GET /api/snapshot/summary (real graded count + last pipeline run) for the
* HeartbeatBar. Static segment wins over the [sport] dynamic route.
*/
export async function GET() {
try {
const upstream = await fetch(`${BACKEND_URL}/api/snapshot/summary`, {
method: 'GET',
headers: { Accept: 'application/json' },
});
const data = await upstream.json().catch(() => ({ graded: 0, updated_at: null, sports: {} }));
return NextResponse.json(data, { status: upstream.ok ? 200 : upstream.status });
} catch {
return NextResponse.json({ graded: 0, updated_at: null, sports: {} }, { status: 200 });
}
}
+6 -2
View File
@@ -11,6 +11,8 @@ import { GradePill } from '@/components/GradeCard';
import Slate from '@/components/Slate';
// Session 55 — the self-learning loop's track record, live in the header.
import { AccuracyBadge } from '@/components/vyndr';
// Session 57 (Phase 0) — honest per-sport empty-slate copy.
import { emptyStateCopy } from '@/lib/emptyState';
type Sport = 'NBA' | 'MLB' | 'WNBA';
@@ -325,14 +327,16 @@ export default function DashboardPage() {
{games === null ? (
<SkeletonRow stacked />
) : games.length === 0 ? (
// Session 57 (Phase 0) — honest per-sport empty state (spec §6):
// an off-season sport says when it returns; in-season = off-day.
<div
className="surface diagonal-cut tex-scan"
style={{ padding: 32, textAlign: 'center', display: 'grid', gap: 8, justifyItems: 'center' }}
>
<p className="lbl" style={{ color: 'var(--grade-c)' }}>NO SLATE</p>
<h3 style={{ fontSize: 18, fontWeight: 700 }}>No games on the slate tonight.</h3>
<h3 style={{ fontSize: 18, fontWeight: 700 }}>{emptyStateCopy(sport.toLowerCase()).title}</h3>
<p style={{ color: 'var(--text-1)', fontSize: 14, maxWidth: 420 }}>
Check the Ledger to review past grades, or explore the Leaderboard.
{emptyStateCopy(sport.toLowerCase()).body}
</p>
<div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
<a href="/ledger" className="btn-primary" style={{ padding: '10px 18px' }}>View the Ledger </a>
+5 -2
View File
@@ -138,9 +138,12 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<ParlayProvider>
<HashRedirect />
<Nav />
{/* Header = 60px nav + 32px ticker + 30px heartbeat (§8); offset main. */}
{/* Header = 60px nav + 30px heartbeat (+2 slack) + a DYNAMIC
ticker: the Ticker publishes --ticker-h (32px with ≥4 real
items, else 0px — spec §3) so main's offset collapses with
it. Keep the 92px base in sync with Nav/HeartbeatBar. */}
<AuthGate>
<main style={{ paddingTop: 124, minHeight: '100vh', paddingBottom: 80 }}>{children}</main>
<main style={{ paddingTop: 'calc(92px + var(--ticker-h, 0px))', minHeight: '100vh', paddingBottom: 80 }}>{children}</main>
</AuthGate>
<Footer />
<ParlayPanel />
+9 -203
View File
@@ -1,206 +1,12 @@
import SectionHead from '@/components/vyndr/SectionHead';
import GradeBadge from '@/components/vyndr/GradeBadge';
import SportBadge from '@/components/vyndr/SportBadge';
export const metadata = { title: 'The Terminal' };
// Sample league-intelligence dataset (§7 shapes). Real wiring to
// scheduleService.getGameSummary (injury cascades) + schedule/odds (leaders)
// lands in a later session; the screen + contracts are real now.
const INJURY_WIRE = [
{
player: 'Jamal Murray', team: 'DEN', sport: 'nba', status: 'OUT', injury: 'Hamstring', posted: '2h ago',
cascade: [
{ player: 'Nikola Jokić', stat: 'Assists', delta: '+3.2% usage', grade: 'A+' },
{ player: 'Russell Westbrook', stat: 'Points', delta: '+4.1% usage', grade: 'B' },
],
},
{
player: 'Jeremy Sochan', team: 'SA', sport: 'nba', status: 'OUT', injury: 'Ankle sprain', posted: '3h ago',
cascade: [
{ player: 'Victor Wembanyama', stat: 'Points', delta: '+3.2% usage', grade: 'A' },
{ player: 'Devin Vassell', stat: '3PT Made', delta: '+2.0% usage', grade: 'B' },
],
},
{
player: 'Mookie Betts', team: 'LAD', sport: 'mlb', status: 'GTD', injury: 'Wrist', posted: '40m ago',
cascade: [{ player: 'Shohei Ohtani', stat: 'RBIs', delta: '+1.9% lineup', grade: 'C' }],
},
];
const IMPACTED_GAMES = [
{ sport: 'nba', match: 'LAL @ SA', time: '10:30 PM ET', vvi: 92, graded: 7, note: 'Sochan OUT spikes Wembanyama usage; LAL pace + 26th-vs-C matchup compound it.', drivers: ['INJURY CASCADE', 'PACE', 'MATCHUP', 'BLOWOUT RISK'] },
{ sport: 'mlb', match: 'LAD @ ATL', time: '7:20 PM ET', vvi: 78, graded: 5, note: 'Truist Park boosts LH power; Ohtani platoon edge vs RHP, ATL bullpen on fumes.', drivers: ['PARK FACTOR', 'PLATOON', 'BULLPEN FATIGUE'] },
{ sport: 'wnba', match: 'NY @ LV', time: '9:00 PM ET', vvi: 71, graded: 4, note: "Wilson 31% usage vs NY's soft interior; Liberty on a back-to-back.", drivers: ['USAGE', 'MATCHUP', 'REST EDGE'] },
];
const FACTOR_PULSE = [
{ count: 11, label: 'Injury cascades', hint: 'props recalibrated', color: 'var(--g-b)' },
{ count: 8, label: 'Pace mismatches', hint: 'tempo edges', color: 'var(--g-a)' },
{ count: 6, label: 'Park / platoon', hint: 'MLB power spots', color: 'var(--g-a)' },
{ count: 5, label: 'Blowout risk', hint: 'minutes-capped', color: 'var(--miss)' },
];
const LEADERS: { key: string; rows: { player: string; team: string; val: string; grade: string; gradeable: boolean }[] }[] = [
{
key: 'NBA · Points',
rows: [
{ player: 'Nikola Jokić', team: 'DEN', val: '31.2', grade: 'A+', gradeable: true },
{ player: 'Victor Wembanyama', team: 'SA', val: '29.4', grade: 'A', gradeable: true },
{ player: 'Anthony Edwards', team: 'MIN', val: '28.4', grade: 'A', gradeable: true },
],
},
{
key: 'MLB · Total Bases',
rows: [
{ player: 'Shohei Ohtani', team: 'LAD', val: '2.25', grade: 'A', gradeable: true },
{ player: 'Aaron Judge', team: 'NYY', val: '2.10', grade: 'B', gradeable: false },
],
},
];
const MATCHUP_EXPLOITS = [
{ sport: 'nba', team: 'LAL', rank: '26th', stat: 'Points allowed to C', exploit: 'Wembanyama Points O26.5 · A' },
{ sport: 'wnba', team: 'NY', rank: '8th', stat: 'Opp paint FG% allowed', exploit: "A'ja Wilson Points O23.5 · A" },
{ sport: 'mlb', team: 'ATL', rank: 'T-4th', stat: 'HR/9 to lefties', exploit: 'Ohtani Total Bases O1.5 · A' },
];
const statusColor = (s: string) =>
s === 'OUT' ? 'var(--miss)' : s === 'GTD' || s === 'QUESTIONABLE' ? 'var(--amber)' : 'var(--text-1)';
function vviColor(v: number) {
return v >= 85 ? 'var(--g-ap)' : v >= 75 ? 'var(--g-a)' : v >= 65 ? 'var(--g-b)' : 'var(--g-c)';
}
const card: React.CSSProperties = { background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, padding: 16 };
import { redirect } from 'next/navigation';
/**
* Session 57 (Phase 0) — /terminal is retired. The surface was fabricated
* sample data (Finals-era NBA content rendered as live intelligence); audit
* verdict: REBUILD. Its layouts are preserved as §12 content-engine templates
* in components/intel/TerminalTemplates.tsx. "The Terminal" survives as brand
* language only (pricing copy). Old links land on the real slate.
*/
export default function TerminalPage() {
return (
<section style={{ maxWidth: 1100, margin: '0 auto', padding: '28px 16px 96px' }}>
{/* HEADER */}
<header style={{ marginBottom: 22 }}>
<SectionHead accent="var(--g-a)"> LEAGUE INTELLIGENCE</SectionHead>
<h1 className="mono" style={{ fontSize: 30, fontWeight: 800, letterSpacing: '-0.02em', margin: '8px 0 6px' }}>THE TERMINAL</h1>
<p className="mono" style={{ fontSize: 13, color: 'var(--text-1)' }}>
The context the books hide injury cascades, volatility, and the spots tonight's slate underprices.
</p>
</header>
{/* VVI — IMPACTED GAMES (intel surface) */}
<SectionHead style={{ marginBottom: 12 }}>VYNDR VOLATILITY INDEX · MOST-IMPACTED GAMES</SectionHead>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 12, marginBottom: 28 }}>
{IMPACTED_GAMES.map((g) => (
<div key={g.match} className="intel-surface scanlines" style={{ borderRadius: 10, padding: 16, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'relative', zIndex: 2 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<SportBadge sport={g.sport} size="sm" />
<span className="mono" style={{ fontSize: 15, fontWeight: 800, color: '#e8fff4' }}>{g.match}</span>
</div>
<div style={{ textAlign: 'right' }}>
<div className="mono" style={{ fontSize: 26, fontWeight: 800, lineHeight: 1, color: vviColor(g.vvi), textShadow: '0 0 14px currentColor' }}>{g.vvi}</div>
<div className="label" style={{ fontSize: 8.5, color: 'rgba(232,255,244,.5)' }}>VVI</div>
</div>
</div>
<div className="mono" style={{ fontSize: 11.5, color: 'rgba(232,255,244,.75)', margin: '10px 0', lineHeight: 1.55 }}>{g.note}</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
{g.drivers.map((d) => (
<span key={d} className="mono" style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', padding: '2px 6px', borderRadius: 3, color: '#bdf5e2', border: '1px solid rgba(0,255,184,.3)', background: 'rgba(0,0,0,.25)' }}>{d}</span>
))}
</div>
<div className="mono" style={{ marginTop: 10, fontSize: 11, color: 'var(--g-a)' }}>{g.graded} graded props · {g.time}</div>
</div>
</div>
))}
</div>
<div className="terminal-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 20 }}>
{/* INJURY CASCADES */}
<div>
<SectionHead style={{ marginBottom: 12 }}>INJURY WIRE · CASCADE ANALYSIS</SectionHead>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{INJURY_WIRE.map((w) => (
<div key={w.player} style={card}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<SportBadge sport={w.sport} size="sm" />
<span style={{ fontSize: 14, fontWeight: 700 }}>{w.player}</span>
<span className="mono" style={{ fontSize: 11, color: 'var(--text-2)' }}>{w.team}</span>
</div>
<span className="mono" style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', color: statusColor(w.status) }}>{w.status}</span>
</div>
<div className="mono" style={{ fontSize: 11, color: 'var(--text-2)', marginBottom: 10 }}>{w.injury} · {w.posted}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
{w.cascade.map((c, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
<span className="mono" style={{ fontSize: 12, color: 'var(--g-a)' }}>↳</span>
<span style={{ flex: 1, minWidth: 0 }}>
<span style={{ fontSize: 13, fontWeight: 600 }}>{c.player}</span>
<span className="mono" style={{ fontSize: 11.5, color: 'var(--text-1)' }}> · {c.stat} <span style={{ color: 'var(--g-a)', fontWeight: 700 }}>▲ {c.delta}</span></span>
</span>
<GradeBadge grade={c.grade} size="sm" glow />
</div>
))}
</div>
</div>
))}
</div>
</div>
{/* RIGHT COLUMN */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
{/* FACTOR PULSE */}
<div>
<SectionHead style={{ marginBottom: 12 }}>FACTOR PULSE · FIRING NOW</SectionHead>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
{FACTOR_PULSE.map((f) => (
<div key={f.label} style={card}>
<div className="mono" style={{ fontSize: 28, fontWeight: 800, color: f.color, lineHeight: 1 }}>{f.count}</div>
<div style={{ fontSize: 12.5, fontWeight: 700, marginTop: 6 }}>{f.label}</div>
<div className="mono" style={{ fontSize: 10.5, color: 'var(--text-2)', marginTop: 2 }}>{f.hint}</div>
</div>
))}
</div>
</div>
{/* GRADEABLE LEADERS */}
<div>
<SectionHead style={{ marginBottom: 12 }}>GRADEABLE LEADERS</SectionHead>
{LEADERS.map((grp) => (
<div key={grp.key} style={{ ...card, marginBottom: 10 }}>
<div className="label" style={{ marginBottom: 9 }}>{grp.key}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
{grp.rows.map((r) => (
<div key={r.player} style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
<span style={{ flex: 1, minWidth: 0, fontSize: 13, fontWeight: 600 }}>{r.player} <span className="mono" style={{ fontSize: 11, color: 'var(--text-2)' }}>{r.team}</span></span>
<span className="mono" style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-0)' }}>{r.val}</span>
<GradeBadge grade={r.grade} size="sm" glow={r.gradeable} />
</div>
))}
</div>
</div>
))}
</div>
</div>
</div>
{/* MATCHUP EXPLOITS */}
<div style={{ marginTop: 28 }}>
<SectionHead style={{ marginBottom: 12 }}>MATCHUP EXPLOITS · UNDERPRICED SPOTS</SectionHead>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 }}>
{MATCHUP_EXPLOITS.map((m, i) => (
<div key={i} style={card}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
<SportBadge sport={m.sport} size="sm" />
<span className="mono" style={{ fontSize: 13, fontWeight: 700 }}>{m.team}</span>
<span className="mono" style={{ fontSize: 11, color: 'var(--miss)', fontWeight: 700 }}>{m.rank}</span>
</div>
<div className="mono" style={{ fontSize: 11.5, color: 'var(--text-1)', marginBottom: 8 }}>{m.stat}</div>
<div className="mono" style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--g-a)' }}> {m.exploit}</div>
</div>
))}
</div>
</div>
</section>
);
redirect('/dashboard');
}