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>
This commit is contained in:
@@ -22,6 +22,8 @@ interface SnapGrade {
|
||||
grade?: string;
|
||||
confidence?: number;
|
||||
archetype?: string | null;
|
||||
// Session 60 (6.1) — settled outcome (yesterday's-proof fallback rows).
|
||||
outcome?: { result: string; actual?: number | null } | null;
|
||||
}
|
||||
|
||||
const SPORTS = ['mlb', 'nba', 'wnba'] as const;
|
||||
@@ -38,6 +40,7 @@ const isTop = (g?: string) => g === 'A+' || g === 'A';
|
||||
|
||||
export default function TopSignals() {
|
||||
const [signals, setSignals] = useState<SnapGrade[] | null>(null);
|
||||
const [header, setHeader] = useState<'signals' | 'board' | 'proof'>('board');
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
@@ -52,12 +55,28 @@ export default function TopSignals() {
|
||||
);
|
||||
if (!active) return;
|
||||
const all: SnapGrade[] = [];
|
||||
const settled: SnapGrade[] = [];
|
||||
for (const res of results) {
|
||||
const grades = res && Array.isArray(res.grades) ? res.grades : [];
|
||||
for (const g of grades) if (isTop(g.grade)) all.push(g);
|
||||
for (const g of grades) {
|
||||
if (g.outcome) settled.push(g);
|
||||
else if (g.grade) all.push(g);
|
||||
}
|
||||
}
|
||||
all.sort((a, b) => (Number(b.confidence) || 0) - (Number(a.confidence) || 0));
|
||||
setSignals(all.slice(0, 3));
|
||||
// Session 60 (6.1) — never oversell: top 3 by grade whatever they
|
||||
// are, but the TOP SIGNALS header only with ≥1 A-tier; otherwise
|
||||
// "TONIGHT'S BOARD". Nothing graded yet → yesterday's settled reads
|
||||
// WITH outcome chips (the proof strip carries the slate).
|
||||
if (all.length > 0) {
|
||||
setSignals(all.slice(0, 3));
|
||||
setHeader(all.some((g) => isTop(g.grade)) ? 'signals' : 'board');
|
||||
} else if (settled.length > 0) {
|
||||
setSignals(settled.slice(0, 3));
|
||||
setHeader('proof');
|
||||
} else {
|
||||
setSignals([]);
|
||||
}
|
||||
} catch {
|
||||
if (active) setSignals([]);
|
||||
}
|
||||
@@ -67,16 +86,19 @@ export default function TopSignals() {
|
||||
return () => { active = false; clearInterval(id); };
|
||||
}, []);
|
||||
|
||||
// Self-hide off-hours (nothing graded A yet) — never an empty shell.
|
||||
// Self-hide only when there's NOTHING real (no grades and no settles).
|
||||
if (!signals || signals.length === 0) return null;
|
||||
const headerText = header === 'signals' ? "TONIGHT'S TOP SIGNALS"
|
||||
: header === 'proof' ? "YESTERDAY'S SETTLED READS" : "TONIGHT'S BOARD";
|
||||
const headerSub = header === 'proof' ? '· MISSES INCLUDED' : '· LIVE FROM THE SLATE';
|
||||
|
||||
return (
|
||||
<section style={{ maxWidth: 960, margin: '0 auto', padding: '8px 16px 24px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', marginBottom: 12 }}>
|
||||
<div className="mono" style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, letterSpacing: '0.1em', color: 'var(--text-secondary, #8A8A9A)' }}>
|
||||
<span className="live-dot" aria-hidden style={{ width: 7, height: 7, borderRadius: '50%', background: 'var(--grade-a, #00D4A0)', display: 'inline-block' }} />
|
||||
<span style={{ color: 'var(--grade-a, #00D4A0)', fontWeight: 700 }}>TONIGHT'S TOP SIGNALS</span>
|
||||
<span style={{ color: 'var(--text-tertiary, #707080)' }}>· LIVE FROM THE SLATE</span>
|
||||
<span style={{ color: 'var(--grade-a, #00D4A0)', fontWeight: 700 }}>{headerText}</span>
|
||||
<span style={{ color: 'var(--text-tertiary, #707080)' }}>{headerSub}</span>
|
||||
</div>
|
||||
<AccuracyBadge variant="inline" />
|
||||
</div>
|
||||
@@ -104,6 +126,13 @@ export default function TopSignals() {
|
||||
<div style={{ fontWeight: 700, fontSize: 14, color: '#fff', fontFamily: 'var(--sans, sans-serif)', marginBottom: 4 }}>{player}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary, #B8BCC8)' }}>
|
||||
{shortStat(g.stat_type || g.stat)} {side}{g.line}
|
||||
{/* Outcome chip on the proof-strip fallback — misses included. */}
|
||||
{g.outcome && (
|
||||
<span style={{ marginLeft: 8, fontWeight: 700, color: g.outcome.result === 'hit' ? 'var(--grade-a, #00D4A0)' : g.outcome.result === 'miss' ? 'var(--miss, #FF5252)' : 'var(--text-tertiary)' }}>
|
||||
{g.outcome.result === 'hit' ? '✓ HIT' : g.outcome.result === 'miss' ? '✕ MISS' : '– PUSH'}
|
||||
{g.outcome.actual != null ? ` (${g.outcome.actual})` : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user