Session 49: Complete onboarding flow + name micro-fixes (2185 tests)

Name micro-fixes (close the normalization arc):
- collapseInitials merges "J C Escarra" -> "JC Escarra" (display + key); both
  playerName.js copies. Added mickey:michael nickname.

Onboarding flow (end-to-end, complete):
- Storage: Supabase user_metadata.preferences (no migration).
- API: src/routes/preferences.js GET/POST (requireAuth, admin getUserById/
  updateUserById, partial merge + sanitize) + Next /api/preferences proxy.
- Page: web onboarding/page.tsx — 3 steps (sports >=1 / books skip / bankroll
  presets+custom+skip) -> SIGNAL ACTIVE -> POST onboarding_complete:true -> 2s
  -> /dashboard. Redirects to login when unauthenticated.
- Redirect: dashboard fetches /api/preferences fresh; new+incomplete users
  (created_at >= cutoff) -> /onboarding; never while auth loading; existing
  users exempt.
- Personalization: Slate default tab = prefs.sports[0]; preferred books glow in
  the card lines grid (lib/books isPreferredBook, threaded dash->Slate->GameCard).
- Settings: PREFERENCES section loads + edits + saves sports/books/limit.

Backend 2156 -> 2185 tests (+29), 184 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-19 10:20:55 -04:00
parent 91b03c4044
commit 3b47b783dc
17 changed files with 687 additions and 17 deletions
+21
View File
@@ -512,6 +512,27 @@ snapshot, locked to the line, and read from cache.
merge carries ab_per_game/rest_days; `FEATURE_NAMES` is meta bookkeeping only,
NOT a whitelist that filters the vector).
## Onboarding Flow (Session 49 — non-obvious)
- **Preferences live in Supabase `user_metadata.preferences`** (NO table/migration).
`src/routes/preferences.js` GET/POST behind `requireAuth`, read/written via the
service client's `auth.admin.getUserById`/`updateUserById`. POST is a PARTIAL
merge (only body keys change) + sanitized (valid sports only, ≤12 books, clamped
limit). Shape: `{ sports[], books[], weekly_limit, onboarding_complete }`.
- **Redirect reads FRESH from the API, not session metadata.** The dashboard
fetches `/api/preferences` on mount and redirects new+incomplete users to
`/onboarding`. Don't rely on `session.user.user_metadata` for this — it's stale
after a backend write (admin API doesn't refresh the client session).
- **Existing users are exempt** via a `created_at >= ONBOARDING_CUTOFF`
(2026-06-19) check. The redirect effect also early-returns while `authLoading`
(else it bounces unauthenticated users to onboarding instead of login).
- **Dashboard personalization:** Slate `initialTab = prefs.sports[0]`; preferred
books highlight in the card lines grid via `lib/books.js isPreferredBook`
(matches DK/draftkings/DraftKings), threaded dashboard → Slate → vyndr/GameCard.
- **Settings** has a PREFERENCES section (GET to load, POST to save) — the same
sports/books/limit the onboarding collects.
- **Name micro-fix:** `playerName.js` `collapseInitials` merges "J C" → "JC"
(display + key) so space-separated initials dedupe.
## Active Skills
- vyndr-voice (all user-facing output)
- prop-analysis (grading methodology)