DS2: Dashboard Slate Rebuild — one hero, pending collapse, never-empty hero

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>
This commit is contained in:
Kev
2026-07-13 00:07:06 -04:00
parent cf91c04e90
commit fe294a5de3
4 changed files with 531 additions and 48 deletions
+159 -47
View File
@@ -15,6 +15,11 @@ import { AccuracyBadge, Skeleton, SkeletonList } from '@/components/vyndr';
import { emptyStateCopy } from '@/lib/emptyState';
// Session 59 (work-order 2.3) — the real pipeline schedule for waiting states.
import { nextRunLabelET } from '@/lib/pipelineSchedule';
// DS2 (Design v2) — the never-empty hero engine: rank tonight's grades on a
// varying signal, and fall back to yesterday's PROVEN A-tier receipts so first
// paint ALWAYS proves the model (#1, #13, Part 6).
import { selectTopGrades, buildHeroReceipts } from '@/lib/slateAdapter';
import GradeBadge from '@/components/vyndr/GradeBadge';
import { currentAccessToken } from '@/lib/authToken';
type Sport = 'NBA' | 'MLB' | 'WNBA';
@@ -61,6 +66,32 @@ interface ParlayLegStat {
parlay_count: number;
}
// DS2 — a PROVEN receipt (yesterday's settled A-tier hit) for the never-empty hero.
interface HeroReceipt {
player: string;
stat: string;
line: number;
side: string;
grade: string;
sport: string;
outcome: string;
actual: number | null;
clvResult: string | null;
}
// The /api/ledger/model settled-row shape (subset we read for receipts).
interface ModelEntry {
player_name?: string;
sport?: string;
stat?: string;
line?: number;
side?: string;
grade?: string;
outcome?: string | null;
actual_value?: number | null;
clv_result?: string | null;
}
interface RecentScan {
id: string;
player_name: string;
@@ -86,6 +117,9 @@ export default function DashboardPage() {
const [sport, setSport] = useState<Sport>('NBA');
const [games, setGames] = useState<Game[] | null>(null);
const [topGrades, setTopGrades] = useState<TopGrade[] | null>(null);
// DS2 — yesterday's PROVEN receipts, fetched only when tonight has no grades
// yet (the never-empty hero fallback). null = not yet loaded.
const [heroReceipts, setHeroReceipts] = useState<HeroReceipt[] | null>(null);
const [mostParlayed, setMostParlayed] = useState<ParlayLegStat[] | null>(null);
const [recentScans, setRecentScans] = useState<RecentScan[] | null>(null);
// Session 49 — the user's primary sport tab + preferred books from prefs.
@@ -127,6 +161,7 @@ export default function DashboardPage() {
let cancelled = false;
setGames(null);
setTopGrades(null);
setHeroReceipts(null);
Promise.all([
fetch(`/api/games/tonight?sport=${sport}`).then((r) => r.json()).catch(() => ({ games: [] })),
@@ -163,6 +198,24 @@ export default function DashboardPage() {
};
}, [sport]);
// DS2 (#1, Part 6) — the NEVER-EMPTY hero fallback. When tonight's slate has
// no graded props yet, pull the model's most-recent settled reads and keep
// only the PROVEN A-tier hits (buildHeroReceipts). First paint always proves
// the model works (the real 55-19 / 74% record). Best-effort, self-hiding.
useEffect(() => {
if (topGrades === null || topGrades.length > 0) return; // tonight has grades → no fallback needed
let cancelled = false;
fetch(`/api/ledger/model?sport=${sport.toLowerCase()}&limit=80`)
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (cancelled) return;
const entries: ModelEntry[] = Array.isArray(data?.entries) ? data.entries : [];
setHeroReceipts(buildHeroReceipts(entries, 8) as HeroReceipt[]);
})
.catch(() => { if (!cancelled) setHeroReceipts([]); });
return () => { cancelled = true; };
}, [topGrades, sport]);
// Most parlayed + recent scans don't depend on sport
useEffect(() => {
fetch('/api/props/most-parlayed')
@@ -253,57 +306,116 @@ export default function DashboardPage() {
}}
/>
{/* Top grades horizontal scroll */}
<Section
title="Top grades tonight"
subtitle="The A-tier picks. Tap to read or add to parlay."
>
{topGrades === null ? (
<SkeletonRow />
) : topGrades.length === 0 ? (
// Session 59 (work-order 2.3) — the real schedule, not passive waiting.
<p style={emptyCopy}>No grades yet. Grades post {nextRunLabelET() || 'on the next pipeline run'}.</p>
) : (
<div
style={{
display: 'flex',
gap: 12,
overflowX: 'auto',
padding: '4px 4px 12px',
scrollSnapType: 'x mandatory',
}}
{/* Top grades tonight — DS2 (#1, #13, Part 6). Tonight's grades RANKED on
the varying signal (selectTopGrades: tier → confidence → edge) so the
row isn't identical-weight noise; when tonight is empty we fall back to
yesterday's PROVEN A-tier receipts so first paint ALWAYS proves the
model. Only the truly-empty case shows the honest schedule copy. */}
{(() => {
const ranked = topGrades ? selectTopGrades(topGrades, 10) as TopGrade[] : [];
const proofMode = topGrades !== null && ranked.length === 0 && (heroReceipts?.length ?? 0) > 0;
return (
<Section
title="Top grades tonight"
subtitle={proofMode ? 'No slate graded yet — the models most recent proven A-tier reads.' : 'The A-tier picks, ranked by confidence. Tap to read or add to parlay.'}
>
{topGrades.map((g, i) => (
<button
key={`${g.player}-${g.stat}-${i}`}
onClick={() => router.push(`/scan?sport=${g.sport}&player=${encodeURIComponent(g.player)}&stat=${g.stat}&line=${g.line}`)}
className="surface diagonal-cut surface-hover"
{topGrades === null ? (
<SkeletonRow />
) : ranked.length > 0 ? (
<div
style={{
minWidth: 200,
padding: 16,
textAlign: 'left',
scrollSnapAlign: 'start',
cursor: 'pointer',
border: '1px solid var(--border)',
borderRadius: 16,
background: 'var(--bg-surface)',
color: 'inherit',
fontFamily: 'inherit',
display: 'flex',
gap: 12,
overflowX: 'auto',
padding: '4px 4px 12px',
scrollSnapType: 'x mandatory',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
<SportPill sport={g.sport} />
<GradePill grade={g.grade} />
</div>
<h3 style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>{g.player}</h3>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
{g.direction} {g.line} {g.stat.replace(/_/g, ' ')}
</p>
</button>
))}
</div>
)}
</Section>
{ranked.map((g, i) => (
<button
key={`${g.player}-${g.stat}-${i}`}
onClick={() => router.push(`/scan?sport=${g.sport}&player=${encodeURIComponent(g.player)}&stat=${g.stat}&line=${g.line}`)}
className="surface diagonal-cut surface-hover"
style={{
minWidth: 200,
padding: 16,
textAlign: 'left',
scrollSnapAlign: 'start',
cursor: 'pointer',
border: '1px solid var(--border)',
borderRadius: 16,
background: 'var(--bg-surface)',
color: 'inherit',
fontFamily: 'inherit',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
<SportPill sport={g.sport} />
<GradePill grade={g.grade} />
</div>
<h3 style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>{g.player}</h3>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
{g.direction} {g.line} {g.stat.replace(/_/g, ' ')}
</p>
{/* #13 — surface the VARYING signal so ranked rows read as a
real ranking, not identical noise. */}
{g.confidence != null && (
<p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.04em', marginTop: 6, fontVariantNumeric: 'tabular-nums' }}>
{g.confidence}% CONF
</p>
)}
</button>
))}
</div>
) : proofMode ? (
// The never-empty proof: yesterday's PROVEN A-tier receipts.
<div
style={{
display: 'flex',
gap: 12,
overflowX: 'auto',
padding: '4px 4px 12px',
scrollSnapType: 'x mandatory',
}}
>
{(heroReceipts || []).map((r, i) => (
<div
key={`${r.player}-${r.stat}-${i}`}
className="surface diagonal-cut"
style={{
minWidth: 210,
padding: 16,
scrollSnapAlign: 'start',
border: '1px solid var(--grade-a)',
borderRadius: 16,
background: 'var(--bg-surface)',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<span className="mono" style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--grade-a)' }}>YESTERDAY · PROVEN</span>
<GradeBadge grade={r.grade} size={30} glow />
</div>
<h3 style={{ fontSize: 14, fontWeight: 700, marginBottom: 4 }}>{r.player}</h3>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize', fontVariantNumeric: 'tabular-nums' }}>
{String(r.side).toUpperCase().startsWith('U') ? 'under' : 'over'} {r.line} {String(r.stat).replace(/_/g, ' ')}
</p>
<p className="mono" style={{ fontSize: 12, fontWeight: 800, color: 'var(--grade-a)', marginTop: 8, fontVariantNumeric: 'tabular-nums' }}>
&#10003; HIT{r.actual != null ? ` (${r.actual})` : ''}
{r.clvResult === 'beat' ? <span style={{ color: 'var(--text-tertiary)', fontWeight: 700 }}> · CLV BEAT</span> : null}
</p>
</div>
))}
</div>
) : heroReceipts === null ? (
// tonight empty, fallback still loading → skeleton, never a text wall.
<SkeletonRow />
) : (
// Truly nothing yet — the honest schedule-derived waiting copy (QA.22).
<p style={emptyCopy}>No grades yet. Grades post {nextRunLabelET() || 'on the next pipeline run'}.</p>
)}
</Section>
);
})()}
{/* Tonight's games */}
<Section title={`Today's ${sport} games`} subtitle={slateEmpty ? null : `${games?.length ?? 0} game${games?.length === 1 ? '' : 's'} today`}>
+38 -1
View File
@@ -10,6 +10,8 @@ import TeamLogo from '@/components/vyndr/TeamLogo';
import { accentColor } from '@/lib/teamMeta';
import { playerHref } from '@/lib/playerHref';
import { isPreferredBook } from '@/lib/books';
import { pendingSummary, topReadForCard } from '@/lib/slateAdapter';
import { nextRunLabelET } from '@/lib/pipelineSchedule';
import { useParlay, legKey } from '@/contexts/ParlayContext';
export interface GameLine {
@@ -182,6 +184,12 @@ export default function GameCard({ game: g, onAddParlay, onOpen, preferredBooks
const [linesExpanded, setLinesExpanded] = useState(false);
const collapsed = useMemo(() => collapseStrips(g.playerStrips || []), [g.playerStrips]);
const stripsToRender = showAllReads ? collapsed.sorted : collapsed.visible;
// DS2 (#2) — the ONE bold hero per card: the single best live graded read,
// promoted large + mono + tabular. Everything else in the strips below stays
// demoted context. (#14) — when the card has NO graded reads yet, the awaiting
// props collapse to ONE pending line instead of six identical rows.
const topRead = useMemo(() => topReadForCard(g.playerStrips || []), [g.playerStrips]);
const pending = useMemo(() => pendingSummary(g.playerStrips || []), [g.playerStrips]);
// A1 S11 — LIVE SLATE MODE: any strip prop carrying live tracking shows the
// once-per-card label. Grades locked pre-game NEVER change in-game.
const isTracking = useMemo(
@@ -321,7 +329,36 @@ export default function GameCard({ game: g, onAddParlay, onOpen, preferredBooks
</span>
)}
</SectionHead>
{g.playerStrips && g.playerStrips.length > 0 ? (
{/* DS2 (#2) — ONE bold hero: the card's best read, large + mono +
tabular. The failure mode was every value at identical weight; this
promotes the single number that matters and demotes the rest. */}
{topRead && !pending && (
<div
style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 11, padding: '10px 12px', borderRadius: 9, background: 'var(--bg-2)', border: '1px solid var(--border-hi)' }}
>
<GradeBadge grade={topRead.grade} size={48} glow />
<div style={{ minWidth: 0, flex: 1 }}>
<div style={{ fontSize: 15, fontWeight: 800, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{topRead.player}</div>
<div className="mono" style={{ fontSize: 12, color: 'var(--text-1)', marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>
{topRead.stat} {String(topRead.side).toUpperCase().startsWith('U') ? 'u' : 'o'}{topRead.line}
{topRead.team ? <span style={{ color: 'var(--text-2)' }}> · {topRead.team}</span> : null}
</div>
</div>
<span className="mono" style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--text-2)', flexShrink: 0 }}>TOP READ</span>
</div>
)}
{/* DS2 (#14) — collapse the dead "Grades post …" repetition. An
all-awaiting card renders ONE compact line, not six identical rows. */}
{pending ? (
<div className="mono" style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--text-1)', flexWrap: 'wrap' }}>
<span style={{ fontWeight: 800, color: 'var(--text-0)', fontVariantNumeric: 'tabular-nums' }}>{pending.count}</span>
<span>props pending</span>
<span style={{ color: 'var(--text-2)' }}>·</span>
<span style={{ color: 'var(--text-2)' }}>{pending.players} player{pending.players === 1 ? '' : 's'}</span>
<span style={{ color: 'var(--text-2)' }}>·</span>
<span style={{ color: 'var(--text-2)', fontStyle: 'italic' }}>{nextRunLabelET() ? `grade ${nextRunLabelET()}` : 'grade on the next pipeline run'}</span>
</div>
) : g.playerStrips && g.playerStrips.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{stripsToRender.map((ps, i) => (
<StatStrip
+140
View File
@@ -421,6 +421,139 @@ function buildPlayerStripsFromProps(gameProps, gradeIndex, deltaIndex, now = Dat
});
}
// ── DS2 (Design v2) — Dashboard Slate Rebuild engine ────────────────
// Pure, testable functions behind the founder's #1 rebuild: rank the top
// grades on a VARYING signal (#13), promote ONE bold hero per card (#2),
// collapse the dead "Grades post …" repetition (#14), and the NEVER-EMPTY
// hero (#1, Part 6) — first paint ALWAYS proves the model.
const DS2_GRADE_RANK = {
'A+': 0, A: 1, 'A-': 2, 'B+': 3, B: 4, 'B-': 5, 'C+': 6, C: 7, 'C-': 8, D: 9, F: 10,
};
/** Grade → sortable tier rank (lower = better). Unknown → 99. */
function gradeRankOf(g) {
const k = String(g == null ? '' : g).trim().toUpperCase();
return DS2_GRADE_RANK[k] !== undefined ? DS2_GRADE_RANK[k] : 99;
}
const numOr = (v, fallback) => {
const n = typeof v === 'number' ? v : parseFloat(v);
return Number.isFinite(n) ? n : fallback;
};
/**
* #13 — rank tonight's grades by TIER, then the VARYING signal so a leaderboard
* of near-identical rows stops being noise: confidence desc, then |edge| desc,
* stable by input order. Drops gradeless rows. Returns at most `limit`.
*/
function selectTopGrades(grades, limit = 10) {
const arr = (Array.isArray(grades) ? grades : []).filter((g) => g && g.grade);
const scored = arr.map((g, idx) => ({
g,
idx,
rank: gradeRankOf(g.grade),
conf: numOr(g.confidence, -1),
edge: Math.abs(numOr(g.edge, -Infinity)),
}));
scored.sort((a, b) => a.rank - b.rank || b.conf - a.conf || b.edge - a.edge || a.idx - b.idx);
return scored.slice(0, Math.max(0, limit)).map((s) => s.g);
}
/**
* #1 / Part 6 — build PROVEN receipts from settled ledger rows: only A-tier
* grades that HIT (a miss never becomes proof). Carries the real result so the
* receipt is verifiable. Best-grade first. `settledRows` = /api/ledger/model
* entries (player_name, stat, line, side, grade, outcome, actual_value, …).
*/
function buildHeroReceipts(settledRows, limit = 8) {
const rows = (Array.isArray(settledRows) ? settledRows : []).filter(
(r) => r && String(r.outcome || '').toLowerCase() === 'hit' && gradeRankOf(r.grade) <= 2,
);
const mapped = rows.map((r) => ({
player: displayName(r.player_name || r.player || ''),
stat: r.stat,
line: r.line,
side: String(r.side || 'over'),
grade: r.grade,
sport: String(r.sport || '').toUpperCase(),
outcome: 'hit',
actual: r.actual_value != null ? r.actual_value : null,
clvResult: r.clv_result || null,
}));
mapped.sort((a, b) => gradeRankOf(a.grade) - gradeRankOf(b.grade));
return mapped.slice(0, Math.max(0, limit));
}
/**
* #1 / Part 6 — the never-empty hero engine. Tonight's ranked top grades win;
* when tonight is empty, fall back to yesterday's PROVEN A-tier receipts; only
* `empty` when both are absent. First paint ALWAYS proves the model when any
* settled proof exists.
*/
function heroFallbackState(tonightGrades, settledRows, limit = 10) {
const tonight = selectTopGrades(tonightGrades, limit);
if (tonight.length > 0) return { mode: 'tonight', items: tonight };
const receipts = buildHeroReceipts(settledRows, Math.min(limit, 8));
if (receipts.length > 0) return { mode: 'receipts', items: receipts };
return { mode: 'empty', items: [] };
}
/**
* #14 — collapse the dead per-prop "Grades post …" repetition. When a card has
* NO graded reads yet (every prop awaiting), returns a single summary
* ({ count, players, statLabels }) so the UI renders ONE compact line instead
* of six identical rows. Any graded prop present → null (there are real reads
* to show). No awaiting props → null.
*/
function pendingSummary(strips) {
const list = Array.isArray(strips) ? strips : [];
let awaiting = 0;
let graded = 0;
const players = new Set();
const statLabels = [];
for (const s of list) {
for (const p of s.props || []) {
if (p.grade) graded += 1;
else if (p.awaiting) {
awaiting += 1;
players.add(s.player);
if (statLabels.length < 6) statLabels.push(`${p.stat} ${p.line}`);
}
}
}
if (graded > 0 || awaiting === 0) return null;
return { count: awaiting, players: players.size, statLabels };
}
/**
* #2 — the ONE bold hero per card: the single highest-tier LIVE graded prop
* (dead/not-in reads never lead). Returns { player, team, archetype, stat,
* line, side, grade } or null when the card has no graded reads.
*/
function topReadForCard(strips) {
let best = null;
for (const s of Array.isArray(strips) ? strips : []) {
for (const p of s.props || []) {
if (!p.grade || p.dead) continue;
const rank = gradeRankOf(p.grade);
if (!best || rank < best.rank) {
best = {
rank,
player: s.player,
team: s.team || '',
archetype: s.archetype || null,
stat: p.stat,
line: p.line,
side: p.side || 'O',
grade: p.grade,
};
}
}
}
if (!best) return null;
const { rank, ...rest } = best; // eslint-disable-line no-unused-vars
return rest;
}
// ── MLB probable pitchers (Session 46) ──────────────────────────────
const teamToken = (name) => String(name == null ? '' : name).toLowerCase().replace(/[^a-z0-9 ]/g, '').trim();
const teamMascot = (name) => { const t = teamToken(name).split(' ').filter(Boolean); return t.length ? t[t.length - 1] : ''; };
@@ -468,4 +601,11 @@ module.exports = {
buildPlayerStripsFromProps,
buildPitcherMap,
pitchersForGameTeams,
// DS2 — dashboard rebuild engine.
gradeRankOf,
selectTopGrades,
buildHeroReceipts,
heroFallbackState,
pendingSummary,
topReadForCard,
};