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>
4a VOICE v1.1 committed (board start); lint is EXECUTABLE — banned list +
no-exclamation law enforced in the engine (throws in test, drops in
prod) and locked by tests. Curly-apostrophe variants covered.
4b mediaEngine: deterministic templates (MORNING WIRE, SIGNAL, STREAK
WATCH, THE SETTLE, RECEIPTS, ARCHETYPE WATCH, LINE DISPATCH) filled
ONLY from snapshot/ledger/streaks JSON. Record percentages never render
under n>=20 (counts + 'Record building' below). Stark layer = curated
committed library (content/stark-lines.json), day-rotated selection —
selected, never generated.
4c /desk (founder-only: requireAuth + DESK_OWNERS email allowlist,
deny-by-default): all formats as text + <=280-char pre-segmented tweets
with per-tweet copy buttons + char counts, wire/numbers-only variants,
DATA BRIEF block (structured day numbers) with copy-for-claude.ai.
ntfy ping after the day's first snapshot: 'Desk pack ready'.
4d ghostPublisher: DRAFTS ONLY (status:'draft' test-locked), env-gated
no-op, HS256 JWT via node crypto (zero new deps). POST
/api/internal/ghost/drafts saves slate preview + settle drafts.
Nothing anywhere auto-posts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>