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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user