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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user