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
@@ -0,0 +1,68 @@
// Session 60 (night2/G) — the aggregator's content formats (spec §12).
// streak-watch is the ZERO-GRADE daily format; daily-report flags itself
// DO-NOT-POST until the record is real (n>=20).
const express = require('express');
const request = require('supertest');
const mockStore = new Map();
jest.mock('../../src/utils/redis', () => ({
cacheGet: async (k) => (mockStore.has(k) ? mockStore.get(k) : null),
cacheSet: async (k, v) => { mockStore.set(k, v); return true; },
getRedisClient: () => null,
isDegraded: () => true,
}));
jest.mock('../../src/services/rosterLogs', () => ({ loadRosterLogs: jest.fn(async () => []) }));
const { loadRosterLogs } = require('../../src/services/rosterLogs');
function mountApp() {
delete require.cache[require.resolve('../../src/routes/content')];
const app = express();
app.use('/api/content', require('../../src/routes/content'));
return app;
}
beforeEach(() => { mockStore.clear(); jest.clearAllMocks(); });
describe('GET /api/content/top-signals/:sport', () => {
test('returns tonight top graded props from the snapshot cache', async () => {
mockStore.set('snapshot:mlb:latest', {
updated_at: 'x',
grades: [
{ player: 'Aaron Judge', stat_type: 'home_runs', line: 0.5, direction: 'over', grade: 'A', confidence: 80, gradedAt: { timestamp: 't1' } },
{ player: 'Settled Guy', stat_type: 'hits', line: 1.5, grade: 'A', confidence: 90, outcome: { result: 'hit' } },
],
});
const res = await request(mountApp()).get('/api/content/top-signals/mlb');
expect(res.status).toBe(200);
expect(res.body.dataLevel).toBe('full');
expect(res.body.signals).toHaveLength(1); // settled props excluded
expect(res.body.signals[0].player).toBe('Aaron Judge');
});
});
describe('GET /api/content/streak-watch/:sport', () => {
test('the zero-grade format: real streaks through the lens', async () => {
loadRosterLogs.mockResolvedValue([
{ name: 'Streaky Guy', team: 'Tampa Bay Rays', games: [
{ date: '2026-07-10', opponent: 'Boston Red Sox', hits: 2 },
{ date: '2026-07-09', opponent: 'Boston Red Sox', hits: 1 },
{ date: '2026-07-08', opponent: 'New York Yankees', hits: 1 },
] },
]);
const res = await request(mountApp()).get('/api/content/streak-watch/mlb');
expect(res.status).toBe(200);
expect(res.body.dataLevel).toBe('full');
expect(res.body.streaks[0].player).toBe('Streaky Guy');
expect(res.body.streaks[0].read).toContain('hit streak');
});
});
describe('GET /api/content/daily-report/:sport', () => {
test('flags DO-NOT-POST while the record is building', async () => {
const res = await request(mountApp()).get('/api/content/daily-report/mlb');
expect(res.status).toBe(200);
expect(res.body.do_not_post).toBe(true);
expect(res.body.reason).toContain('record building');
});
});