Skip to content

fix(console-ui): repair provider dashboard tab navigation + gate post-install tabs#462

Merged
anupsv merged 2 commits into
masterfrom
fix/provider-dashboard-nav
Jun 24, 2026
Merged

fix(console-ui): repair provider dashboard tab navigation + gate post-install tabs#462
anupsv merged 2 commits into
masterfrom
fix/provider-dashboard-nav

Conversation

@anupsv

@anupsv anupsv commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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 but router.push silently 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:

  1. Navigation must work — fixed.
  2. Setup/Earnings should only appear after installation — the tabs previously always rendered (not a recent change); now gated on having ≥1 linked machine.

Changes

  • Switch to native <a> navigation (mirroring fix(console-ui): make sidebar navigation native #458) in:
    • providers/layout.tsx — the Dashboard/Setup/Earnings tabs
    • dashboard/OnboardingState.tsx — "Set up a provider" + "Open calculator"
    • dashboard/FixAffordance.tsx — internal "fix" CTA links (external ones already used <a>)
  • Add useHasLinkedProviders (one-shot, non-polling /api/me/providers check) 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 starts false and 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).
  • Dropped the eslint-disable @next/next/no-html-link-for-pages lines 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)

  • Deeper root cause: Next's <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.tsx and app/earn/SetupProviderCTA.tsx.

Test plan

  • npx eslint src/ — 0 errors (pre-existing warnings only).
  • npm run build — succeeds.
  • Manual: on the Provider Dashboard with no machines, only the Dashboard tab shows; Set up a provider / Open calculator navigate. With ≥1 linked machine, Setup/Earnings tabs appear and the tabs switch pages.

Made with Cursor


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

…-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>
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
d-inference Ready Ready Preview Jun 24, 2026 7:49pm
d-inference-console-ui-dev Ready Ready Preview Jun 24, 2026 7:49pm
d-inference-landing Ready Ready Preview Jun 24, 2026 7:49pm

Request Review

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

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 affected_files list in the threat model.


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:

  • FixAffordance.tsx — UI affordance component (buttons/prompts to fix provider config issues); renders state, no fetch calls apparent.
  • OnboardingState.tsx — Onboarding step tracker; local UI state only.
  • layout.tsx — Route layout wrapper for /providers; no auth logic, no coordinator fetch.
  • useHasLinkedProviders.ts — A React hook; if it calls an authenticated API route to check linked-provider status, the route it calls (not the hook itself) is the relevant surface. Reviewers should confirm this hook calls only a hardcoded NEXT_PUBLIC_COORDINATOR_URL endpoint (covered by TB-001/TB-004) and does not accept a client-supplied URL — if it does, it falls under the existing T-017 / SEC-001 pattern and the affected route should be added to that finding.

None of these files introduce a new trust boundary that requires a new threat model entry. If useHasLinkedProviders.ts was found to call a BFF route that accepts x-coordinator-url, add that route to T-017's affected_files list.


🔐 Threat model: docs/threat-model.yaml · Updates on each push to this PR

@anupsv anupsv merged commit defaa4e into master Jun 24, 2026
16 of 17 checks passed
anupsv added a commit that referenced this pull request Jun 24, 2026
…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 ethenotethan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Array.isArray(data.providers) to fail unexpectedly.

Additive_complexity — ✅ No issues found

1 finding(s) total, 1 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment on lines +33 to +34
if (!res.ok || cancelled) return;
const data = (await res.json()) as { providers?: unknown[] };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [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 ethenotethan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 accessing data.providers.

Additive_complexity — ✅ No issues found

1 finding(s) total, 1 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment on lines +33 to +34
if (!res.ok || cancelled) return;
const data = (await res.json()) as { providers?: unknown[] };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants