fix(console-ui): repair provider dashboard tab navigation + gate post-install tabs#462
Conversation
…-install tabs The provider dashboard tabs (Dashboard/Setup/Earnings) and the onboarding "Set up a provider" / "Open calculator" buttons used next/link, whose client router is currently broken app-wide (the #457/#458 regression): the links render but router.push silently no-ops, so clicking them never switched pages. #458 converted only the sidebar to native <a> and explicitly left the broken Link path in place; these dashboard surfaces were never converted. - Switch the providers tabs, onboarding CTAs, and the dashboard fix-affordance internal links to native <a> navigation (browser-native route loads), the same workaround #458 used for the sidebar. - Gate the Setup/Earnings tabs behind a one-shot "has linked providers" check so they appear only after a machine is linked; pre-install shows just the Dashboard, whose onboarding state owns the setup flow. The check starts false and reads state only inside an effect, so the first render is deterministic (no hydration mismatch). Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No security-relevant files changed — this PR touches only dashboard UI components with no impact on any modelled trust boundary. Trust boundaries touched: None (all four files are pure React/Next.js UI components in the providers dashboard; they do not touch auth, API routes, localStorage secrets, coordinator communication, or any BFF route). Threat coverage: No T-xxx threats are affected. None of the changed files appear in any New surface assessment — should any of these be added to the threat model? A quick read of the changed files suggests they are all rendering/state components:
None of these files introduce a new trust boundary that requires a new threat model entry. If 🔐 Threat model: |
…tion (root cause) (#463) 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.
ethenotethan
left a comment
There was a problem hiding this comment.
Automated Code Review — Layr-Labs/d-inference#
Verdict: REQUEST_CHANGES
Security — ✅ No issues found
Performance — ✅ No issues found
Type_diligence — 1 finding(s) (1 blocking)
- 🟡 [MEDIUM]
console-ui/src/app/providers/useHasLinkedProviders.ts:33-34— JSON response cast to typed interface without validation- Suggestion: Add type guard or runtime validation before casting
await res.json()to{ providers?: unknown[] }. The API could return malformed data causingArray.isArray(data.providers)to fail unexpectedly.
- Suggestion: Add type guard or runtime validation before casting
Additive_complexity — ✅ No issues found
1 finding(s) total, 1 blocking. Verdict: REQUEST_CHANGES.
🤖 Automated review by Centaur · DAR-186
| if (!res.ok || cancelled) return; | ||
| const data = (await res.json()) as { providers?: unknown[] }; |
There was a problem hiding this comment.
🟡 [MEDIUM] 🏷️ JSON response cast to typed interface without validation
💡 Suggestion: Add type guard or runtime validation before casting await res.json() to { providers?: unknown[] }. The API could return malformed data causing Array.isArray(data.providers) to fail unexpectedly.
📊 Score: 3×3 = 9 · Category: missing_type_assertion
ethenotethan
left a comment
There was a problem hiding this comment.
Automated Code Review — Layr-Labs/d-inference#
Verdict: REQUEST_CHANGES
Security — ✅ No issues found
Performance — ✅ No issues found
Type_diligence — 1 finding(s) (1 blocking)
- 🟡 [MEDIUM]
console-ui/src/app/providers/useHasLinkedProviders.ts:33-34— JSON response cast to typed interface without validation- Suggestion: Add type guard or runtime validation before casting
await res.json()to{ providers?: unknown[] }. The API could return malformed data causing type confusion when accessingdata.providers.
- Suggestion: Add type guard or runtime validation before casting
Additive_complexity — ✅ No issues found
1 finding(s) total, 1 blocking. Verdict: REQUEST_CHANGES.
🤖 Automated review by Centaur · DAR-186
| if (!res.ok || cancelled) return; | ||
| const data = (await res.json()) as { providers?: unknown[] }; |
There was a problem hiding this comment.
🟡 [MEDIUM] 🏷️ JSON response cast to typed interface without validation
💡 Suggestion: Add type guard or runtime validation before casting await res.json() to { providers?: unknown[] }. The API could return malformed data causing type confusion when accessing data.providers.
📊 Score: 3×4 = 12 · Category: missing_type_assertion
Summary
On the Provider Dashboard the top tabs (Dashboard / Setup / Earnings) and the onboarding Set up a provider / Open calculator buttons rendered but did nothing — clicking them never switched pages.
Root cause: they use
next/link, whose App Router client-router path is currently broken app-wide (the #457/#458 regression) — the link renders butrouter.pushsilently no-ops, so only full-page-load<a>navigation works. #458 worked around this for the sidebar by switching to native<a>and explicitly left "the broken Next Link client-router path" in place; these dashboard surfaces were never converted, which is the regression.Two asks were folded in:
Changes
<a>navigation (mirroring fix(console-ui): make sidebar navigation native #458) in:providers/layout.tsx— the Dashboard/Setup/Earnings tabsdashboard/OnboardingState.tsx— "Set up a provider" + "Open calculator"dashboard/FixAffordance.tsx— internal "fix" CTA links (external ones already used<a>)useHasLinkedProviders(one-shot, non-polling/api/me/providerscheck) and gate the Setup/Earnings tabs behind it. Pre-install shows only Dashboard (whose onboarding state owns the setup flow); the tabs appear once a machine is linked. The hook startsfalseand reads state only inside an effect, so the first render is deterministic — no hydration mismatch (the same discipline as the fix(console-ui): sidebar nav unclickable — fix React #418 hydration mismatch #457 fix).eslint-disable @next/next/no-html-link-for-pageslines fix(console-ui): make sidebar navigation native #458 introduced; the rule doesn't fire in this App Router config (it was flagged as an unused directive).Not in this PR (follow-ups)
<Link>client router is broken app-wide. The durable fix is to find/repair that (then revert all the native-<a>band-aids). Two<Link>usages remain on the broken path outside this scope:app/stats/page.tsxandapp/earn/SetupProviderCTA.tsx.Test plan
npx eslint src/— 0 errors (pre-existing warnings only).npm run build— succeeds.Made with Cursor
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.