Session 17: Audit response — checkout 401 fix, hero prop 404 fix, Slate parsing fix, ALL tab cascade isolation, cookie/nav/footer/autocomplete polish (1438 tests)

This commit is contained in:
Kev
2026-06-11 21:22:59 -04:00
parent 73b65a0248
commit beaf8b2a61
14 changed files with 681 additions and 25 deletions
+147 -1
View File
@@ -4,7 +4,153 @@
2026-06-11
## Current Phase
SHIP BUILD v16.0 — Live hero prop + sport-scoped markets + launch polish (Session 16)
SHIP BUILD v17.0 — Audit response: checkout 401, hero 404, Slate parsing, polish (Session 17)
## Session 17 (2026-06-12) — SHIPPED
A platform audit from a signed-in / signed-out walkthrough flagged
12 issues. This session traced each to root cause and shipped fixes.
Stripe is live with real products + webhooks; the symptoms audited
were code-side, not Stripe-side.
### FIX 1 — Checkout 401 "User profile not found" [CRITICAL]
`src/middleware/auth.js` 401'd authenticated users whose `auth.users`
row had no matching `public.users` profile. Signup writes to
`auth.users` automatically; the application-side row never landed
for SSO callbacks and legacy accounts that pre-dated the trigger.
Fix: when `.single()` returns PostgREST's `PGRST116` ("no rows"), the
middleware now upserts a default `{id, email, tier:'free'}` row and
re-reads. Idempotent under concurrent requests. Distinct 401 message
(`User profile creation failed`) when the upsert itself fails — lets
the operator separate missing-row recovery from real DB outages in
logs. 9 tests cover happy path, missing row → upsert, message-only
PGRST116 detection, upsert error, post-upsert empty re-read, and
non-PGRST116 errors NOT triggering an upsert.
### FIX 2 — Hero prop 404 [CRITICAL]
`web/src/app/api/hero-prop/route.ts` shipped Session 16 with BOTH
`dynamic = 'force-dynamic'` AND `revalidate = 900`. Next.js App
Router silently 404s on this conflict. Removed `revalidate`. The
15-minute cache still works via the existing `Cache-Control:
s-maxage=900` response header.
### FIX 3 — WNBA games not surfacing [HIGH]
`Slate.tsx`'s `groupByGame` skipped every prop because
`Number.isFinite(r.line)` failed on the actual Express response shape.
Express's `groupProps` returns props with `lines: [{ book, line,
over_odds, under_odds }]`, but the Slate expected a flat `line:
number`. Every WNBA / NBA / MLB prop was filtered out.
Fix: added a `pickLine()` unwrapper that prefers the flat `r.line`
when present (legacy callers + test fixtures) and otherwise picks the
first numeric line out of `r.lines[]`. The Slate now correctly
surfaces game cards for any sport with a populated `lines` array.
### FIX 4 — ALL tab error cascade [HIGH]
`Slate.tsx`'s cascade surfaced a top-level error whenever ANY single
sport rejected — even when the other sports succeeded with empty
data. Reworked to track per-sport failures separately and only show
the top-level banner when EVERY attempted sport rejected. Failed-
but-attempted sports get appended to the existing footer "endpoint
not configured" line.
### FIX 5 — Cookie consent visibility [HIGH — Legal]
Root cause was visual overlap, not the component's logic:
`BottomTabBar` and `CookieConsent` both `position: fixed; bottom: 0`,
and BottomTabBar's 64px height visually obscured the banner.
Resolved transitively by FIX 7 — anonymous visitors no longer see
BottomTabBar, so the cookie banner has the bottom of the viewport to
itself on first visit.
### FIX 6 — Scan autocomplete silent failure [MEDIUM]
The dropdown logic was correct — the silent failure happened when
`/api/players/search` returned `{ players: [] }` (NBA service down,
or no spelling match). Added a visible "no players matched" state
when the search has run but returned empty, so users get feedback.
### FIX 7 — Mobile bottom nav auth gate [MEDIUM]
`BottomTabBar.tsx` rendered for all users on all eligible routes.
Anonymous visitors on `/pricing` saw Home/Read/Parlay/Ledger/Profile —
all auth-gated destinations that would 401 on click. Gated behind
`useAuth()` with a `loading || !user` early-return. Also fixed FIX 5
transitively.
### FIX 8 — Footer support email + stale copy [LOW]
Added `Support` link (mailto:support@vyndr.app) to the Legal column.
Removed `(test mode while we onboard founders)` from Pricing.tsx —
Stripe is live. Replaced with "First 100 users lock $14.99/mo
Analyst for life."
### FIX 9 — Sentry zero events [MEDIUM]
Code wiring is correct in both backend (`initSentry()` + `setupExpress
ErrorHandler` mounted) and frontend (`SentryInit` reads
`NEXT_PUBLIC_SENTRY_DSN`). Audit found zero events because the DSN
env vars aren't set in Coolify. Code-level no-op; documented as a
Coolify env action.
### FIX 10 — Read counter visibility [LOW]
Quota pill appeared in the global Nav across every page. Restricted
to `/scan` and `/dashboard` (the surfaces where it acts as quota
context next to the scan action) via a pathname check in `Nav.tsx`.
### FIX 11 — Profile page [NO-OP]
Audit reported "no profile page exists." Verified: it does, at
`web/src/app/profile/page.tsx` (196 lines, includes email, tier,
subscription_status, subscription_end, founder_pricing,
cancel_at_period_end). Audit looked at a stale build.
### FIX 12 — Tonight's slate landing preview [MEDIUM]
`web/src/components/TonightsSlate.tsx` (new) — game-count strip
mounted between `Hero` and `LivePropsStrip`. Fetches the three
sport-odds proxies in parallel, dedupes games by (away, home, time),
renders "X NBA · Y WNBA · Z MLB games being graded right now." with
a signup CTA. Hides itself when every sport returns zero.
### Tests added (Session 17)
| Suite | Tests |
|----------------------------------------|-------|
| `tests/unit/requireAuth.test.js` | 9 |
| **Session 17 total** | **9** |
### Quality gates
- `npm test`: **1438 / 1438 passing** (1429 + 9 new), 111 suites, 0 regressions
- `web/npm run build`: clean — `/api/hero-prop` now compiles to `ƒ`
(was silently 404'd in production by the conflicting directives)
- License audit: third-party deps remain permissive
### Honest verification status
Build + tests verified. I CANNOT verify the following on the live
site from here — they need a deploy + re-audit smoke test:
- Checkout 401 ↔ actual Supabase row creation under load
- Hero prop endpoint returning JSON in production
- WNBA Slate tab actually showing games
- Cookie banner visible on first incognito load
- Mobile bottom nav truly absent for signed-out visitors
### Coolify follow-ups (operator action)
1. Set `SENTRY_DSN` and `NEXT_PUBLIC_SENTRY_DSN` env vars to enable
server-side and browser-side error capture. Currently unset →
Sentry dashboard sees zero events even when 503/401 errors occur.
2. The Session 16 sport-scoped markets fix is in code; the
`NODE_OPTIONS=--require /app/data/patch.js` workaround can be
dropped from the web service env after this deploy.
---
## Session 16 (2026-06-11) — SHIPPED