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:
+147
-1
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user