Wave 2B: Offseason never-dark hub — NewsWire + FuturesBoard on /explore

Extend /explore (ExploreHub) into the 365-day never-dark hub. Two new
self-hiding sections feed REAL always-available data into the offseason:

- NewsWire (components/vyndr/NewsWire.tsx): real ESPN headlines from
  /api/news/:sport (newest-first, mono timestamps, type chips, player/team
  links) + real injuries from /api/schedule/:sport/injuries (OUT/GTD chips,
  token colors). Reuses the retired TerminalTemplates INJURY_WIRE layout but
  never routes its sample constants. Self-hides when both feeds are empty.
- FuturesBoard (components/vyndr/FuturesBoard.tsx): real futures from
  /api/futures/:sport — championship/win-total/award markets, mono tabular
  prices + movement colored by the contract (shortening=green / drifting=amber
  / flat=dim, NEVER red; move shown ONLY when the backend supplies one).
  Carries the honest "TRACKED · NOT GRADED" label — no fabricated grades on
  futures. Self-hides when markets:[].
- ExploreHub is offseason-aware (via emptyState OFF_SEASON month check): the
  hub LEADS with futures + wire when the board is dark, COMPLEMENTS the live
  board in-season. Sport selector kept; each section self-hides independently.
- Testable pure helpers: lib/futuresMove.js (move→color, never red) +
  lib/newsFormat.js (timeAgo mono-stamp, ESPN type labels).

Contracts consumed (Wave 2A owns the proxy/service files); code self-hides on
fetch failure if a proxy isn't present yet.

Tests: tests/unit/newsWire.test.js + futuresBoard.test.js (26 new). Full suite
259 suites / 3144 green; web build exit 0. vyndrParityQA stays green
(mono data, no glitch on data surfaces).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-13 23:27:19 -04:00
parent a4b6255bed
commit 1664e1b3d5
8 changed files with 558 additions and 0 deletions
+24
View File
@@ -13,8 +13,11 @@ import { playerHref } from '@/lib/playerHref';
*/
import StreaksPanel from '@/components/StreaksPanel';
import HotListPanel from '@/components/HotListPanel';
import NewsWire from '@/components/vyndr/NewsWire';
import FuturesBoard from '@/components/vyndr/FuturesBoard';
import { useAuth } from '@/contexts/AuthContext';
import { nextRunLabelET } from '@/lib/pipelineSchedule';
import { OFF_SEASON } from '@/lib/emptyState';
interface Leader {
player: string;
@@ -54,6 +57,21 @@ export default function ExploreHub() {
return q ? leaders.filter((l) => (l.player || '').toLowerCase().includes(q)) : leaders;
}, [leaders, query]);
// Wave 2B — never-dark hub. When the selected sport is in its OFF-SEASON, the
// hub LEADS with futures + the wire (real, always-available data) instead of
// a dark leaderboard; in-season those sections COMPLEMENT the live board
// below. Each self-hides independently on an empty feed — no empty boxes.
const off = OFF_SEASON[sport as keyof typeof OFF_SEASON];
const isOffseason = !!(off && off.months.includes(new Date().getMonth()));
// Both sections self-hide (return null) when their feeds are empty.
const hubSections = (
<>
<FuturesBoard sport={sport} />
<NewsWire sport={sport} />
</>
);
return (
<section style={{ maxWidth: 920, margin: '0 auto', padding: '24px 16px 120px' }}>
<div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 14, marginBottom: 22, flexWrap: 'wrap' }}>
@@ -78,6 +96,9 @@ export default function ExploreHub() {
</div>
</div>
{/* OFF-SEASON LEAD — futures + wire come FIRST when the board is dark. */}
{isOffseason && hubSections}
{/* FILTER BAR */}
<div style={{ display: 'flex', alignItems: 'center', gap: 11, flexWrap: 'wrap', marginBottom: 14, background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 11, padding: '11px 14px' }}>
<span className="mono" style={{ fontSize: 14, color: 'var(--text-2)' }}></span>
@@ -135,6 +156,9 @@ export default function ExploreHub() {
<StreaksPanel sport={sport} tier={tier} stat="all" />
<HotListPanel sport={sport} tier={tier} stat="all" />
</div>
{/* IN-SEASON — futures + wire COMPLEMENT the live board below it. */}
{!isOffseason && hubSections}
</section>
);
}