fix(console-ui): fix hydration mismatch that broke client-side navigation (root cause)#463
Conversation
…tion (root cause) VerificationModeProvider — added in #450 and wrapping the entire app — read localStorage in its useState initializer, so the first client render diverged from the server HTML whenever the user had toggled "technical" mode. On a React hydration mismatch the server DOM is discarded and the tree is regenerated on the client, which breaks App Router client navigation app-wide: next/link renders but router.push silently no-ops (links don't switch pages). This is the regression #457 chased — it made the store and InviteCodeBanner hydration-deterministic but missed this provider — and #458 then band-aided by switching the sidebar to native <a>. Fix: start "normal" on the server and the first client render, then load the persisted preference in an effect (the same hydration-determinism pattern as #457). Adds a regression test pinning the deterministic first render. This is the root-cause fix for the broken Link client router; the native-<a> workarounds (#458 sidebar, #462 provider tabs) can be reverted in a follow-up once verified on a preview deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No threat-model-covered files were modified; this PR adds a UI verification-mode component and its test, which falls entirely outside the current threat model's Trust boundaries touchedNone of the existing TB-xxx boundaries are directly exercised by these two files. New attack surface not covered by an existing threatThe new
Recommendation for threat model maintainersIf No SEC-* findings are resolved by this PR. 🔐 Threat model: |
|
Found 1 test failure on Blacksmith runners: Failure
|
ethenotethan
left a comment
There was a problem hiding this comment.
Automated Code Review — Layr-Labs/d-inference#
Verdict: COMMENT
Security — ✅ No issues found
Performance — ✅ No issues found
Type_diligence — ✅ No issues found
Additive_complexity — ✅ No issues found
✅ All four passes clean. No issues found.
🤖 Automated review by Centaur · DAR-186
…guardrails) Adds a real-browser E2E layer (Playwright + Chromium) for the console UI. Unlike the jsdom Vitest unit tests, these boot the app and drive an actual browser, so they catch SSR-hydration and client-navigation regressions jsdom cannot. - playwright.config.ts: hermetic mock-auth dev server (Privy unconfigured) — no coordinator or secrets needed. - e2e/navigation.spec.ts: every shell route loads with no React hydration error; a persisted verification-mode preference hydrates cleanly; sidebar links and the provider-dashboard tabs switch routes. - vitest.config.ts: exclude e2e/ so Vitest doesn't pick up the Playwright specs. - npm scripts: test:e2e / test:e2e:ui. - CI: new "Console UI E2E (Playwright)" job (installs chromium, runs the suite). Honest scope note: this hermetic mock-auth harness does NOT reproduce the production #463 hydration break (the verification-mode consumers need real authenticated trust data to render divergent DOM), so the suite is a broad hydration + navigation guardrail rather than proof of that specific fix. Verified locally: 11/11 pass. Co-authored-by: Cursor <cursoragent@cursor.com>
ethenotethan
left a comment
There was a problem hiding this comment.
Automated Code Review — Layr-Labs/d-inference#
Verdict: COMMENT
Security — ✅ No issues found
Performance — ✅ No issues found
Type_diligence — ✅ No issues found
Additive_complexity — ✅ No issues found
✅ All four passes clean. No issues found.
🤖 Automated review by Centaur · DAR-186
ethenotethan
left a comment
There was a problem hiding this comment.
Automated Code Review — Layr-Labs/d-inference#
Verdict: COMMENT
Security — ✅ No issues found
Performance — ✅ No issues found
Type_diligence — ✅ No issues found
Additive_complexity — ✅ No issues found
✅ All four passes clean. No issues found.
🤖 Automated review by Centaur · DAR-186

Summary
This is the root cause behind the broken navigation that #457/#458/#462 have been chasing/band-aiding: client-side
next/linknavigation silently fails app-wide (links render butrouter.pushno-ops), so only full-page-load<a>works.VerificationModeProvider— added in #450 and wrapping the entire app inlayout.tsx— readlocalStorageinside itsuseStateinitializer:So when a user had toggled technical mode, the first client render (
technical) diverged from the server HTML (normal). On a React hydration mismatch the server DOM is discarded and the tree is regenerated on the client, which breaks App Router client navigation everywhere.This is the exact bug class #457 fixed for the store and
InviteCodeBanner— it just missed this provider (also from #450). #458 then worked around the symptom by switching the sidebar to native<a>.Fix
Make the first render deterministic (matches the server), then load the persisted preference in an effect — the same pattern as #457:
Adds
verification-mode.test.tsxpinning the contract: first render isnormaleven when localStorage saystechnical, the persisted value applies after mount, and toggle persists.Scope / verification note
localStorage/window/Date/randomread in the global render path (layout→AppShelland the providers it mounts). After this change,VerificationModeProviderwas the only remaining render-timelocalStorageread;ThemeProvider, the store (fix(console-ui): sidebar nav unclickable — fix React #418 hydration mismatch #457), andInviteCodeBanner(fix(console-ui): sidebar nav unclickable — fix React #418 hydration mismatch #457) are already effect-based.ProviderSlackPopupalso reads localStorage in its initializers but is masked-safe (its!isProvidergate makes the first rendernullon both server and client) — a latent instance of the same pattern worth hardening separately.<a>band-aids in place. Please verify on the preview deploy withlocalStorage['darkbloom-verification-mode'] = 'technical'thatnext/linknavigation works (no hydration error in console).Follow-up (separate PR)
Once verified, the native-
<a>workarounds can be reverted to restore SPA navigation: the sidebar (#458) and the provider tabs (#462 — keep its "hide tabs until installed" change, revert only the<a>swap), plus the 2 remaining<Link>s instats/earnstay correct either way.Test plan
npx vitest run— 3/3 in the new suite.npx eslint— clean.npm run build— succeeds.next/linkelements navigates.Made with Cursor
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.