Files
vyndr/tests/unit/ds1SpeedTrust.test.js
builtbykev 1c681df5d3 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>
2026-07-12 19:25:32 -04:00

97 lines
3.9 KiB
JavaScript

/**
* DS1 (DESIGN-SPEC §4 + §17) — source invariants for the Speed + Trust bugs.
* - React #418 hydration: no client-only value rendered in the SSR path
* without a mounted guard.
* - Loading walls: every named loader is a skeleton, not a text wall.
* - Persistence: the scan write uses the authoritative session token.
*/
const fs = require('fs');
const path = require('path');
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
describe('DS1 — React #418 hydration safety (§4, audit #5)', () => {
const landing = read('app/page.tsx');
test('landing no longer reads localStorage inside a useState initializer', () => {
// The exact antipattern that caused #418: the client-only stored-session
// check ran during render, so server (false) and client (true) diverged.
expect(landing).not.toMatch(/useState<boolean>\(\s*\(\)\s*=>\s*hasStoredSession\(\)\s*\)/);
expect(landing).not.toMatch(/useState\(\s*\(\)\s*=>\s*hasStoredSession/);
});
test('the client-only value is deferred behind a mounted flag', () => {
// Server + first client render both compute maybeSignedIn from mounted=false.
expect(landing).toMatch(/const\s+\[mounted,\s*setMounted\]\s*=\s*useState\(false\)/);
expect(landing).toMatch(/setMounted\(true\)/);
expect(landing).toMatch(/maybeSignedIn\s*=\s*mounted\s*&&\s*hasStoredSession\(\)/);
});
test('hasStoredSession still guards against server-side window access', () => {
expect(landing).toMatch(/typeof window === 'undefined'/);
});
});
describe('DS1 — loading walls are skeletons, never text (§4, audit #5)', () => {
const files = {
'dashboard slate load': 'app/dashboard/page.tsx',
'/desk assembling': 'app/desk/page.tsx',
'/ledger loading': 'app/ledger/page.tsx',
'scan loading': 'app/scan/page.tsx',
'landing redirect': 'app/page.tsx',
};
const BANNED = [
'Loading the slate',
'Assembling the pack',
'Loading the model',
/<p[^>]*>\s*Loading…\s*<\/p>/, // the bare ledger text loader
];
for (const [name, rel] of Object.entries(files)) {
test(`${name} contains no text-wall loader`, () => {
const src = read(rel);
for (const b of BANNED) {
if (typeof b === 'string') expect(src).not.toContain(b);
else expect(src).not.toMatch(b);
}
});
}
test('each loader path renders the shared Skeleton', () => {
for (const rel of ['app/dashboard/page.tsx', 'app/desk/page.tsx', 'app/ledger/page.tsx', 'app/scan/page.tsx', 'app/page.tsx']) {
const src = read(rel);
expect(src).toMatch(/Skeleton/);
expect(src).toMatch(/from '@\/components\/vyndr'/);
}
});
test('the Skeleton primitive is tokenized (no raw hex) and reduced-motion-safe', () => {
const skel = read('components/vyndr/Skeleton.tsx');
// Uses the CSS class, no inline colors.
expect(skel).toMatch(/vyndr-skeleton/);
expect(skel).not.toMatch(/#[0-9a-fA-F]{3,8}/);
const css = fs.readFileSync(path.join(WEB, 'app', 'globals.css'), 'utf8');
expect(css).toMatch(/\.vyndr-skeleton\s*\{/);
expect(css).toMatch(/var\(--bg-surface\)/);
// The global prefers-reduced-motion rule freezes every animation.
expect(css).toMatch(/@media \(prefers-reduced-motion: reduce\)/);
});
});
describe('DS1 — scan→ledger persistence uses the authoritative token (§17)', () => {
const scan = read('app/scan/page.tsx');
test('scan pulls the bearer token from the live Supabase session', () => {
expect(scan).toMatch(/session,/); // destructured from useAuth
expect(scan).toMatch(/session\?\.access_token/);
});
test('scan no longer relies solely on the OAuth-only legacy localStorage key', () => {
// The stale key may remain only as a FALLBACK after the session token.
const m = scan.match(/session\?\.access_token\s*\|\|[\s\S]{0,120}sb-token/);
expect(m).toBeTruthy();
});
});