From ff0d3b199eaf99853c04accf90aa2afcfde44b14 Mon Sep 17 00:00:00 2001 From: Kev Date: Fri, 17 Jul 2026 01:24:48 -0400 Subject: [PATCH] =?UTF-8?q?P1-6=20fix:=20one=20freshness=20source=20?= =?UTF-8?q?=E2=80=94=20Slate=20'UPDATED'=20reads=20pipeline=20refreshed=5F?= =?UTF-8?q?at?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phone audit: the same screen showed green 'SIGNAL LIVE · UPDATED 0s ago' AND amber 'SYNC 46:03' — two components reading different fields. The Slate's 'UPDATED Xs ago' measured the CLIENT poll time (always ~0s, since it refetches every 60s), while the app-bar clock honestly measured the pipeline's data age (refreshed_at). '0s ago' claimed a freshness the data didn't have. Now the Slate captures snap.refreshed_at from the snapshot it already fetches (newest across sports) and 'UPDATED' shows THAT — the same source of truth as the clock. The clock still owns the amber/STALE reaction; the strip just states the honest data age. No more contradiction. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/components/Slate.tsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/web/src/components/Slate.tsx b/web/src/components/Slate.tsx index 2123475..4930107 100644 --- a/web/src/components/Slate.tsx +++ b/web/src/components/Slate.tsx @@ -175,7 +175,7 @@ interface StreaksResponse { streaks?: StreakApiRow[] } // Session 45 — pre-graded snapshot response (snapshot:{sport}:latest). interface SnapshotGrade { player?: string; player_name?: string; stat_type?: string; line?: number; direction?: string; grade?: string; archetype?: string | null; projection?: number | null; model_value?: number | null; gradedAt?: { line: number; odds?: number | null; timestamp?: string } | null } interface SnapshotDelta { player?: string; stat?: string; side?: string; delta?: number; direction?: string; currentLine?: number } -interface SnapshotResponse { grades?: SnapshotGrade[]; deltas?: SnapshotDelta[] } +interface SnapshotResponse { grades?: SnapshotGrade[]; deltas?: SnapshotDelta[]; updated_at?: string | null; refreshed_at?: string | null } // Session 46 — MLB probable pitchers response. interface PitcherSide { team?: string | null; pitcher?: string | null; era?: number | null } @@ -583,6 +583,10 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook const [games, setGames] = useState([]); // Session 45 — merged pre-graded snapshot across the loaded sports. const [snapGrades, setSnapGrades] = useState([]); + // P1-6 — the PIPELINE's freshness (refreshed_at, the same field the app-bar + // clock reads). "UPDATED Xs ago" must show the DATA age, not the client's poll + // time — else the strip claims "0s ago" while the clock (honestly) says 46m. + const [snapRefreshedAt, setSnapRefreshedAt] = useState(null); const [snapDeltas, setSnapDeltas] = useState([]); const [pitcherGames, setPitcherGames] = useState([]); // Session 64 (A1-S5) — prop viability (lineup confirmation + injury wire). @@ -685,7 +689,7 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook const oddsGames = groupByGame(oddsProps, sport); const scheduleGames = schedule?.games || []; const merged = mergeSlate(sport, scheduleGames, oddsGames, lines?.games, streaksRes?.streaks); - return { sport, merged, oddsOk, hadSchedule: scheduleGames.length > 0, snapGrades: snap?.grades || [], snapDeltas: snap?.deltas || [], pitcherGames: pitchersRes?.games || [], lineups: lineupsRes || null, injuries: injuriesRes?.byPlayer || null }; + return { sport, merged, oddsOk, hadSchedule: scheduleGames.length > 0, snapGrades: snap?.grades || [], snapDeltas: snap?.deltas || [], snapRefreshed: snap?.refreshed_at || snap?.updated_at || null, pitcherGames: pitchersRes?.games || [], lineups: lineupsRes || null, injuries: injuriesRes?.byPlayer || null }; }), ); @@ -717,6 +721,12 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook setGames(allGames); setSnapGrades(allSnapGrades); + // P1-6 — newest pipeline refreshed_at across the loaded sports (the real + // data age, shared with the app-bar clock — one source of truth). + { + const stamps = perSport.map((s) => (s.snapRefreshed ? Date.parse(s.snapRefreshed) : NaN)).filter((t) => Number.isFinite(t)); + setSnapRefreshedAt(stamps.length ? Math.max(...stamps) : null); + } setSnapDeltas(allSnapDeltas); setPitcherGames(allPitcherGames); setViability({ lineups: mergedLineups || undefined, injuries: Object.keys(mergedInjuries).length ? mergedInjuries : undefined }); @@ -952,8 +962,12 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook {games.some((g) => g.status === 'in') && ( <>·{games.filter((g) => g.status === 'in').length} LIVE )} - {lastRefreshed && ( - <>·UPDATED {freshLabel(lastRefreshed, nowTick)} + {/* P1-6 — DATA age from the pipeline's refreshed_at (the same field the + app-bar clock reads), NOT the client poll time. "UPDATED 0s ago" + while the clock said "SYNC 46:03" was the contradiction; both now + read refreshed_at. The clock owns the amber/STALE reaction. */} + {snapRefreshedAt && ( + <>·UPDATED {freshLabel(snapRefreshedAt, nowTick)} )}