DS1 (design): speed + trust bugs

Fixes the three DESIGN-SPEC Part 4 + #17 audit findings.

1. React #418 hydration mismatch (landing → dashboard entry). The
   `maybeSignedIn` value was computed in a useState INITIALIZER that reads
   localStorage during render: server (no window) → false → emits the
   marketing tree; a signed-in visitor's first CLIENT render → true → emits
   the loading placeholder. Whole-subtree server/client mismatch → React
   discarded and re-rendered the page. Deferred behind a mounted flag so the
   first client render matches the server; the stored-session check flips
   post-mount. SSR HTML is no longer discarded.

2. Loading walls → skeletons. New tokenized Skeleton primitive
   (.vyndr-skeleton, reduced-motion-safe via the global rule). Swapped into
   every text-wall loader: dashboard slate load ("Loading the slate…"), /desk
   ("Assembling the pack…"), /ledger ("Loading…"), scan ("Loading the model…"),
   and the landing redirect placeholder. No bare text loader remains.

3. scan→ledger persistence. Root cause: the scan page read its bearer token
   from localStorage['sb-token'] — a key written ONLY by the OAuth callback —
   so email/password users posted /api/scan anonymously and the ledger write
   (gated on an authed user) was silently skipped. Now uses the authoritative
   session.access_token (matching the ledger read path). Extracted the row
   builder to web/src/lib/ledgerRow.js (shared, testable).

Tests: +17 (scanLedgerPersistence write→mine round-trip + scope + idempotency;
ds1SpeedTrust hydration/skeleton/persistence source invariants). Full suite
233 suites / 2793 green; web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-12 19:25:32 -04:00
parent 24af247b29
commit 1c681df5d3
12 changed files with 498 additions and 45 deletions
+24 -29
View File
@@ -193,12 +193,10 @@ async function writeLedgerEntry(
data: { grade?: string; projection?: number; confidence?: number; edge_pct?: number },
) {
try {
const { nameKey, normalizeName } = await import('@/lib/playerName');
const { nameKey } = await import('@/lib/playerName');
const { buildManualLedgerRow, LEDGER_CONFLICT_COLS } = await import('@/lib/ledgerRow');
const sport = body.sport.toLowerCase();
const playerKey = nameKey(body.player);
const gameDate = new Intl.DateTimeFormat('en-CA', {
timeZone: 'America/New_York', year: 'numeric', month: '2-digit', day: '2-digit',
}).format(new Date());
// Cache-only snapshot read (never triggers an odds fetch → no quota).
let lockedOdds: string | null = null;
@@ -220,31 +218,28 @@ async function writeLedgerEntry(
if (match?.gradedAt?.odds != null) lockedOdds = String(match.gradedAt.odds);
} catch { /* absent beats wrong */ }
await sb.from('ledger_entries').upsert(
{
user_id: userId,
player_key: playerKey,
player_name: normalizeName(body.player).display || body.player,
sport,
stat: body.stat.toLowerCase(),
line: body.line,
side: body.direction,
locked_odds: lockedOdds,
book: body.book ?? 'draftkings',
// Session 59 — team from the snapshot's stats resolve (real feed);
// opponent stays null on manual scans (no game context — never guessed).
team,
opponent: null,
grade: data.grade,
edge: typeof data.edge_pct === 'number' ? data.edge_pct : null,
confidence: typeof data.confidence === 'number' ? data.confidence : null,
model_value: typeof data.projection === 'number' ? data.projection : null,
graded_at: new Date().toISOString(),
game_id: `manual:${sport}:${gameDate}:${playerKey}`,
game_date: gameDate,
},
{ onConflict: 'user_id,player_key,stat,line,side,game_id', ignoreDuplicates: true },
);
// Session 59 — team from the snapshot's stats resolve (real feed); opponent
// stays null on manual scans (no game context — never guessed).
const row = buildManualLedgerRow({
userId,
sport: body.sport,
player: body.player,
stat: body.stat,
line: body.line,
side: body.direction,
book: body.book ?? 'draftkings',
team,
lockedOdds,
grade: data.grade,
edge: data.edge_pct,
confidence: data.confidence,
projection: data.projection,
});
await sb.from('ledger_entries').upsert(row, {
onConflict: LEDGER_CONFLICT_COLS,
ignoreDuplicates: true,
});
} catch (err) {
console.warn('[scan] ledger write failed', err);
}