Session 14: Africa checkout, Tank01 NBA/MLB wiring, WNBA+MLB odds proxies, OAuth icons, loading skeletons (1330 tests)

This commit is contained in:
Kev
2026-06-11 10:06:49 -04:00
parent 10159209fa
commit f5d79cf70d
22 changed files with 979 additions and 27 deletions
+126 -1
View File
@@ -4,7 +4,132 @@
2026-06-10
## Current Phase
SHIP BUILD v13.0 — The Slate (browse-first dashboard) + OAuth providers + Africa geo (Session 13)
SHIP BUILD v14.0 — Africa checkout + Tank01 wiring + WNBA/MLB odds + UX polish (Session 14)
## Session 14 (2026-06-11) — SHIPPED
### Phase 1 — Africa tier checkout
- `src/services/stripeService.js``PRICE_MAP.africa` added (reads
`STRIPE_PRICE_AFRICA`, null when unset). `getPriceId('africa')`
returns the new `PRICE_UNCONFIGURED` sentinel when the env var
isn't set. `createCheckoutSession` translates the sentinel to a
503 with `code: 'tier_unconfigured'` so the frontend can render a
helpful message instead of a generic failure.
- `src/routes/stripe.js` — validation whitelist extended:
`['africa', 'analyst', 'desk']`. The catch block recognizes
`err.code === 'tier_unconfigured'` and surfaces it cleanly.
- Tests: +6 (3 integration around `/api/stripe/checkout` for the
africa tier, 3 unit around `getPriceId('africa')` and the
exported sentinel).
- **DB CHECK constraint blocker from Session 12 still applies** —
Stripe webhook writes of `tier='africa'` to `users.tier` /
`user_profiles.tier` will 23514 until the manual SQL drops + re-
adds the constraint with 'africa' included. Validation-layer fix
is in place; the migration is the next step.
### Phase 2 + 3 — Tank01 NBA + MLB wired into computeFeatures
Architectural choice: cache-read path only on the user request
path. The Tank01 adapters (Session 9) already wrap their primitives
behind Redis with TTL'd `tank01:*` keys. The new
`src/services/intelligence/tank01Augment.js` reads those keys
directly without ever calling RapidAPI — that keeps the user
request path off the 1000/mo free-tier budget. A daily prefetch
(future session) will populate the keys; until then the augmentor
returns empty objects and the existing ESPN-derived features stand
alone.
- `augmentNbaFeatures({gameId, playerName, ymd})` reads
`tank01:nba:boxscore:{gameId}` and `tank01:nba:odds:{ymd}`,
surfaces `t01_pts/reb/ast/threes/blk/stl/tov/minutes/_final` for
the named player when present, plus a `t01_market_present`
marker when daily odds are cached.
- `augmentMlbFeatures({gameId, batterName, batterId, pitcherId,
pitcherName, ymd})` reads `tank01:mlb:bvp:{batterId}:{pitcherId}`
and surfaces BvP signals (`t01_bvp_pa/ab/h/hr/so` + derived
`t01_bvp_so_rate`). Best-effort fallbacks: name-only markers when
IDs are absent (future ID resolution), daily-scoreboard presence
marker when pitcher is unknown.
- `computeFeatures.js` calls both augmentors after `safeGetFeatures`
and merges the result with `Object.assign`. Wrapped in try/catch
so a Redis hiccup never poisons a grade.
- Tests: 13 new in `tests/unit/tank01Augment.test.js`. Existing
computeFeatures + soccerBranch suites still green (no
regressions).
### Phase 4 — WNBA + MLB odds proxies
- `oddsService.SPORT_KEYS` — added `wnba: 'basketball_wnba'` and
`mlb: 'baseball_mlb'`. Off-season odds-api responses return empty
arrays which the Slate handles cleanly.
- `src/routes/odds.js` — new `buildSportRoute()` factory drives
`/api/odds/wnba` and `/api/odds/mlb` (clones of the existing
`/api/odds/nba` handler).
- Next.js proxies: `web/src/app/api/odds/{nba,wnba,mlb}/route.ts`
(the NBA one was also missing — Slate had been pointing at a
non-existent route).
- `Slate.tsx` `FETCH_URLS` — WNBA + MLB no longer flagged as
unsupported. ALL tab fans out to all four sports via
`Promise.allSettled`.
### Phase 5 — UX polish
- `web/src/components/OAuthIcons.tsx` — inline SVGs for Google G,
Apple silhouette, X glyph. ~1 KB each, no icon library import.
- Login + signup pages wire icons into the OAuth buttons with a
shared layout helper.
- Slate loading state — bare "Loading the slate…" text replaced
with three shimmer-skeleton placeholder cards approximating
GameCard dimensions. `@keyframes vyndr-shimmer` added to
`globals.css` so other loading surfaces can reuse the animation.
- Empty state messaging — the Slate's empty-result case already
shows a "Scan it manually →" CTA from Session 13; Session 14
preserves that path.
- Mobile nav — added a subtle "Scan manually →" tertiary link in
the mobile hamburger panel. The desktop nav stays clean (the
Slate IS the scan surface there).
### Tests added (Session 14)
| Suite | Tests |
|----------------------------------------|-------|
| `tests/unit/tank01Augment.test.js` | 13 |
| `tests/integration/stripe.test.js` extended (Africa checkout) | +3 |
| `tests/unit/stripeService.test.js` extended (Africa getPriceId) | +3 |
| **Session 14 total** | **19** |
### Quality gates
- `npm test`: **1330 / 1330 passing** (1311 + 19), 103 suites, 0 regressions
- `web/npm run build`: clean — all four odds proxies prerender
- License audit: third-party deps remain permissive
### Honest gaps
- Tank01 cache keys are not yet populated by any prefetch — the
augmentor wiring is in place but reads will miss until a daily
prefetch script lands. The augmentor returns `{}` on miss, so
grades work exactly as before until the keys populate.
- Africa-tier writes to users.tier will still 23514 (CHECK
violation) post-checkout. The DB constraint migration remains a
manual SQL step from Session 12.
- `STRIPE_PRICE_AFRICA` env var is not set in Coolify yet. Until
it is, `/api/stripe/checkout` returns 503 with
`code: 'tier_unconfigured'` for `tier:'africa'`.
- WNBA odds: odds-api may not always carry props during off-season.
Slate degrades cleanly (empty `props` array + empty state UX).
- OAuth: Google works (if Supabase Site URL + Redirect URLs are
configured). Apple + X buttons render with their icons but the
redirect won't succeed until provider configuration lands in the
Supabase dashboard (Apple Developer Service ID + key; X OAuth
2.0 client).
### Coolify env (Session 14 additions)
```
# New, required to unblock Africa checkout end-to-end:
STRIPE_PRICE_AFRICA=price_... # After creating the product in Stripe dashboard
```
---
## Session 13 (2026-06-11) — SHIPPED