Session 19: Sports design overhaul — player cards with headshots, game card redesign, scan page tonight's players, odds diagnostic logging, tier gate utility (1444 tests)

This commit is contained in:
Kev
2026-06-12 00:30:13 -04:00
parent 0e3839a90a
commit 56392ec8f4
12 changed files with 825 additions and 41 deletions
+142 -2
View File
@@ -1,10 +1,150 @@
# VYNDR — Build State
## Last Updated
2026-06-11
2026-06-12
## Current Phase
SHIP BUILD v18.0 — Internal admin dashboard + Tank01 prefetch endpoint (Session 18)
SHIP BUILD v19.0 — Sports experience overhaul: player cards, game-card redesign, scan page revamp (Session 19)
## Session 19 (2026-06-12) — SHIPPED
The platform had every backend piece in place but read like a
spreadsheet. Same player name listed four times in a row, blank
scan page, generic game headers. This session restructured the
visual hierarchy so the player is the hero of every card.
### PHASE 1 — NBA proxy diagnosis
User reported "/api/odds/nba returns 503 while Express has live
data." Trace: NBA and WNBA proxies are byte-identical in shape.
Probe of production confirmed **all three** sports (NBA, WNBA,
MLB) return 503 with the same payload — root cause is upstream
of the proxy. The Express `oddsService.getOdds` 503 path fires
when odds-api fails AND no Redis cache exists.
Likely production cause: ODDS_API_KEY rotation, quota exhaustion,
or Redis disconnect (cache always empty so every request goes
live, then fails). Not fixable from code without env access.
Code change: added a `console.error` line at the 503 fallthrough
that surfaces upstream status + axios error code + truncated
upstream body. Next time someone gets paged with a 503, the log
gives them the answer instead of "Odds service unavailable."
Test: pinned the log shape (`upstream_status=`, sport name, body
substring) so a future log-cleanup PR can't silently delete it.
### PHASE 2 — PlayerCard + headshot utility
`web/src/lib/playerHeadshot.ts` exposes `getHeadshotUrl({sport,
playerId, espnId, cachedPhotoUrl})` with fallback chain:
- cached photo URL → league CDN → ESPN CDN → silhouette
- League CDNs: `cdn.nba.com/headshots/nba/latest/260x190/{id}.png`,
`cdn.wnba.com/headshots/wnba/...`,
`img.mlbstatic.com/mlb-photos/...`
- ESPN CDN used ONLY when no league ID and `espnId` present
- Soccer doesn't get a synthetic URL — API-Football's `photo`
field is cached separately and passed as `cachedPhotoUrl`
`web/public/images/player-silhouette.svg` — 64x64 generic
silhouette, dark-theme colors.
`web/src/components/PlayerCard.tsx` — new component. Header
(headshot + name + team) over N PropRow children. `<img onError>`
falls back to the silhouette so a CDN 404 doesn't leave a broken
image. Exports `groupPropsByPlayer(props)` helper.
`web/src/components/GameCard.tsx` updated:
- Imports PlayerCard + groupPropsByPlayer
- Visibility budget (`defaultVisible=4`) now applies to PLAYERS,
not raw props — previously a single player with 4+ props
consumed the whole budget and other players were hidden
- "+ N more prop(s)" → "+ N more player(s)"
### PHASE 3 — Game card header redesign
`teamAbbr(fullName, sport)` exported from GameCard:
- Override table for 30+ well-known multi-word names (Los
Angeles Lakers → LAL, St. Louis Cardinals → STL, etc.)
- Two-word names fall back to the first word's 3 letters
- Soccer composes initials when 2+ words, else truncates
Header now shows: `🏀 BOS vs DEN [NBA]` in bold mono, with the
sport label on a colored badge to the right. Below: full names
in muted text + time/venue meta line. Sport colors:
- NBA #E94B3C · WNBA #FFB347 · MLB #1E90FF · Soccer #00D4A0
### PHASE 4 — Scan page tonight's players
New "TONIGHT'S PLAYERS" chip grid above the search input, pulled
from `/api/odds/{sport}` (the canonical list of players who have
props posted today — same source The Slate uses). Each chip:
24×24 headshot + name. Click prefills the player and, when only
ONE stat type has props for that player, prefills the stat too.
Section auto-hides when the array is empty (off-season, odds-api
down, etc.) — no sad "couldn't load tonight's players" stripe.
Search dropdown enhanced: every suggestion now has a 28×28
headshot. Falls back to silhouette via onError for players the
CDN doesn't have yet.
### PHASE 5 — CSP img-src expanded
`web/next.config.ts` — img-src now includes `cdn.wnba.com` and
`img.mlbstatic.com`. Was `cdn.nba.com` + `a.espncdn.com`.
### PHASE 6 — Tier-gate utility (wired in Session 20)
`web/src/lib/tierGate.ts` — exports `canSeeFullLists(tier)`,
`canSeeGradeDetails(tier)`, `getVisibleCount(tier, totalCount)`,
`getHiddenCount(tier, totalCount)`. Free users see top 3; africa,
analyst, desk see everything. Free + africa see grade letters but
NOT detailed grade breakdowns (analyst+ only).
Not consumed yet — exported for the streaks/hot-lists work
planned in Session 20.
### Honest scope flags
- I did not run the actual UI in a browser. The web build is
clean, types resolve, and the Slate's data flow is intact, but
I can't verify the visual end state without a live render.
- Headshot CDNs will 404 for some players (rookies the league
hasn't shot yet, traded players whose league ID we haven't
re-mapped). The onError fallback prevents broken images, but
expect ~515% silhouette rate on coverage.
- The NBA proxy 503 is NOT fixed in code. The diagnostic log
helps the next operator pinpoint the root cause; the fix
itself needs env config access.
### Battery
- Express suite: **112 passed / 1444 tests** (+1 — odds service
diagnostic log test; baseline 1443)
- Web build: **clean** — all new routes register, no TS errors,
no ESLint failures
- All new TypeScript modules tree-shake into existing pages
### Files changed (Session 19)
**Created:**
- `web/src/lib/playerHeadshot.ts`
- `web/src/lib/tierGate.ts`
- `web/src/components/PlayerCard.tsx`
- `web/public/images/player-silhouette.svg`
**Modified:**
- `src/services/oddsService.js` — diagnostic log at 503 path
- `tests/unit/oddsService.test.js` — pinned log shape
- `web/src/components/GameCard.tsx` — PlayerCard integration +
teamAbbr + sport-colored header
- `web/src/app/scan/page.tsx` — tonight's players chip grid +
headshot-enriched search suggestions
- `web/next.config.ts` — CSP img-src for cdn.wnba.com +
img.mlbstatic.com
---
## Session 18 (2026-06-11) — SHIPPED