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:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { Skeleton, SkeletonList } from '@/components/vyndr';
|
||||
|
||||
/**
|
||||
* /desk (Session 63 / A1-S4c) — the founder's daily copy-paste arsenal.
|
||||
@@ -103,10 +104,21 @@ export default function DeskPage() {
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (status === 'loading') {
|
||||
// DS1 (§4) — the pack is assembling; layout-matched skeleton, not a text
|
||||
// wall. Mirrors the header + stack of FormatCards below.
|
||||
return (
|
||||
<section style={{ maxWidth: 860, margin: '0 auto', padding: '28px 16px 120px' }} aria-busy="true">
|
||||
<Skeleton height={14} width={90} radius={4} style={{ marginBottom: 12 }} />
|
||||
<Skeleton height={30} width={260} radius={8} style={{ marginBottom: 24 }} />
|
||||
<SkeletonList count={4} height={120} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (status !== 'ready' || !pack) {
|
||||
return (
|
||||
<section style={{ maxWidth: 640, margin: '0 auto', padding: '48px 16px' }}>
|
||||
<p className="mono" style={{ color: 'var(--text-2)', fontSize: 13 }}>{status === 'loading' ? 'Assembling the pack…' : 'Pack unavailable. Check back after the first pipeline run.'}</p>
|
||||
<p className="mono" style={{ color: 'var(--text-2)', fontSize: 13 }}>Pack unavailable. Check back after the first pipeline run.</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user