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:
Kev
2026-07-11 02:26:45 -04:00
parent f110bd63f1
commit 1b4f2772d6
6 changed files with 285 additions and 9 deletions
+84
View File
@@ -35,6 +35,90 @@ function guard(req, res) {
return sport;
}
/**
* Session 60 (night2/G, spec §12) — the aggregator's content formats.
*
* GET /top-signals/:sport — tonight's top graded props (snapshot cache).
* GET /streak-watch/:sport — the DAILY ZERO-GRADE FORMAT: real streaks
* through the lens; postable even on a morning with no lines.
* GET /daily-report/:sport — the settled-record report. BUILT but flagged
* DO-NOT-POST until the record clears n≥20: the payload carries
* `do_not_post: true` until then. Flag, don't fake.
*/
router.get('/top-signals/:sport', async (req, res) => {
const sport = guard(req, res);
if (!sport) return undefined;
try {
const snap = await cacheGet(`snapshot:${sport}:latest`);
const grades = (snap && Array.isArray(snap.grades) ? snap.grades : [])
.filter((g) => g.grade && !g.outcome)
.sort((a, b) => (Number(b.confidence) || 0) - (Number(a.confidence) || 0))
.slice(0, 5)
.map((g) => ({
player: g.player || g.player_name, team: g.team || null,
stat: g.stat_type || g.stat, line: g.line,
side: String(g.direction || 'over').toLowerCase(), grade: g.grade,
archetype: g.archetype || null,
locked_at: (g.gradedAt && g.gradedAt.timestamp) || null,
}));
res.set('Cache-Control', 'public, max-age=300');
return res.set(MISSION_HEADER).json({
sport, format: 'top-signals', updated_at: snap && snap.updated_at,
dataLevel: grades.length > 0 ? 'full' : 'empty', signals: grades,
});
} catch (err) {
console.error(`[content/top-signals/${sport}]`, err.message);
return res.set(MISSION_HEADER).json({ sport, format: 'top-signals', dataLevel: 'empty', signals: [] });
}
});
router.get('/streak-watch/:sport', async (req, res) => {
const sport = guard(req, res);
if (!sport) return undefined;
try {
const { loadRosterLogs } = require('../services/rosterLogs');
const streaksService = require('../services/streaksService');
const { applyLens } = require('../services/streakLens');
const roster = await loadRosterLogs(sport);
const rows = [
...streaksService.computeStreaks(roster, sport, { limit: 8 }),
...streaksService.computeFormHeat(roster, sport, { limit: 4 }),
];
const todayET = new Intl.DateTimeFormat('en-CA', { timeZone: 'America/New_York' }).format(new Date());
const sched = await cacheGet(`schedule:${sport}:${todayET}`);
const withLens = applyLens(rows, { scheduleGames: Array.isArray(sched) ? sched : [], pitcherGames: [] });
res.set('Cache-Control', 'public, max-age=600');
return res.set(MISSION_HEADER).json({
sport, format: 'streak-watch',
dataLevel: withLens.length > 0 ? 'full' : 'empty',
streaks: withLens.map((r) => ({ player: r.player, team: r.team, description: r.description, read: r.lens && r.lens.read, streak: r.currentStreak })),
});
} catch (err) {
console.error(`[content/streak-watch/${sport}]`, err.message);
return res.set(MISSION_HEADER).json({ sport, format: 'streak-watch', dataLevel: 'empty', streaks: [] });
}
});
router.get('/daily-report/:sport', async (req, res) => {
const sport = guard(req, res);
if (!sport) return undefined;
try {
const ledgerService = require('../services/ledgerService');
const agg = await ledgerService.getModelAggregate({ sport });
const ready = agg.settled >= (agg.min_sample || 20) && agg.hit_pct != null;
res.set('Cache-Control', 'public, max-age=600');
return res.set(MISSION_HEADER).json({
sport, format: 'daily-report',
do_not_post: !ready,
reason: ready ? null : `record building — ${agg.settled}/${agg.min_sample || 20} settles`,
aggregate: agg,
});
} catch (err) {
console.error(`[content/daily-report/${sport}]`, err.message);
return res.set(MISSION_HEADER).json({ sport, format: 'daily-report', do_not_post: true, reason: 'unavailable' });
}
});
router.get('/slate/:sport', async (req, res) => {
const sport = guard(req, res);
if (!sport) return undefined;