Skip to content

refactor(desktop): loop-proof route redirects and contain content crashes - #6354

Merged
Kitenite merged 3 commits into
mainfrom
harden-route-redirects
Aug 11, 2026
Merged

Kitenite merged 3 commits into
mainfrom
harden-route-redirects

Conversation

@Kitenite

@Kitenite Kitenite commented Aug 11, 2026 •

Copy link
Copy Markdown
Member

Stacked on #6353. That PR fixes the specific SUPER-1814 crash; this one makes the whole crash class unreachable so it can't come back at a different site.

Background

TanStack's <Navigate> re-navigates whenever its props object identity changes (confirmed in the installed source and upstream main: if (previousPropsRef.current !== props)). Rendered inline, it re-navigates on every parent re-render and loops into React error #185 — which unmounts the tree. The codebase had been defending this by hoisting each <Navigate> element by hand (#5729), which is whack-a-mole: the six un-hoisted sites I found were all latent instances of the same bug.

What this does

1. A loop-proof Redirect component (renderer/components/Redirect) — re-navigates only when the resolved target href changes, not on props identity. Safe to render inline with dynamic params/search.

2. Bans <Navigate> in the renderer via Biome noRestrictedImports (importNames: ["Navigate"]), pointing offenders at Redirect or beforeLoad. CI already fails on Biome warnings, so this is binding — mutation-tested with a canary file that the rule correctly rejected.

3. Migrates all sites:

  • Reactive redirects (root index, sign-in, create-org, onboarding, authenticated layout ×3, dashboard versionMismatch, cloud gates ×2) → Redirect.
  • Static/legacy redirects (settings index, presets→terminal, tasks ?type=prs, tasks PR detail) → beforeLoad: () => { throw redirect(...) }, so no navigation happens at render time at all.

4. Fixes a latent cloud-settings bug — the CLOUD_ACCESS flag reads undefined while loading, which the old gates treated as "no access" and bounced entitled users out. Now renders nothing until the flag resolves.

5. Adds a CatchBoundary around the dashboard Outlet — a crashing content route now shows a contained error pane with the sidebar and terminals still alive, instead of bubbling to the root boundary and unmounting the whole app (which is what made SUPER-1814 present as "Superset restarted itself").

Verification (CDP, dev desktop from this worktree)

  • All four migrated redirect targets resolve correctly (/workspace/$id→/new-workspace, /tasks?type=prs→/pull-requests, /settings→/settings/account, /settings/presets→/settings/terminal) with zero fix(desktop): use full PATH for open in app in production builds #185 signals and the layout surviving every transition.
  • Forced a render throw in a dashboard route (/automations): the content-error pane rendered with the sidebar fully intact and the root "Something went wrong" page not shown — screenshot confirms containment. Reverted the probe after.
  • Redirect vs an equivalent direct navigate({search}) produce identical router state — no behavioral change beyond loop-proofing.
  • typecheck clean, lint clean, 1454 renderer tests pass.

Follow-ups (not in this PR)

  • Renderer Sentry didn't capture Jose's crash despite ErrorPage calling captureException; render-process-gone in main only logs locally. Telemetry PR worth doing so this class is visible in monitoring.
  • A Redirect unit test would need a router test harness the repo doesn't have yet.

Summary by cubic

Hardened renderer navigation to stop React #185 redirect loops and contain dashboard content crashes (SUPER-1814). Adds a loop‑proof Redirect for @tanstack/react-router, enforces it repo‑wide, moves static redirects to beforeLoad, and proves it with a renderer test.

  • Bug Fixes

    • Stop infinite re-navigation: replace inline <Navigate> with loop‑proof Redirect; move static redirects to beforeLoad so nothing navigates during render.
    • Contain dashboard content-route crashes with a catch boundary that resets on full location href; keep sidebar/terminals mounted, report to Sentry, log reporting failures, and wrap long messages.
    • Fix cloud settings gate: wait for CLOUD_ACCESS to resolve instead of treating undefined as no access.
    • Handle failed navigations: Redirect catches errors, logs them, and clears its marker so later renders can retry.
  • Refactors

    • Add renderer/components/Redirect that navigates only when the resolved href changes.
    • Ban Navigate in the renderer via Biome noRestrictedImports; use Redirect or beforeLoad + redirect(...).
    • Migrate routes (root, sign-in, create-org, onboarding, authenticated layout, dashboard version mismatch, tasks legacy PR routes, settings index, presets→terminal) to the new patterns.
    • Add a renderer unit test using @testing-library/react and @happy-dom/global-registrator to verify single navigation across re-renders.

Written for commit bce1863. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added reliable redirect handling across authentication, onboarding, settings, dashboard, and legacy routes.
    • Preserved relevant URL parameters during legacy route redirects.
    • Added dashboard error recovery with error details, Go Home, and Reload App actions.
  • Bug Fixes

    • Prevented repeated navigation and improved retry behavior when redirects fail.
    • Prevented dashboard content errors from escaping the dashboard layout.
  • Tests

    • Added comprehensive coverage for redirect behavior, including navigation updates and loop prevention.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: afe4cafe-2cb0-4661-b902-f105d1f9ea03

📥 Commits

Reviewing files that changed from the base of the PR and between c239e80 and bce1863.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • apps/desktop/package.json
  • apps/desktop/src/renderer/components/Redirect/Redirect.test.tsx
  • apps/desktop/src/renderer/components/Redirect/Redirect.tsx
  • apps/desktop/src/renderer/components/Redirect/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardContentError/DashboardContentError.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardContentError/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/pr/$prNumber/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/onboarding/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/presets/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/cloud/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/cloud/secrets/page.tsx
  • apps/desktop/src/renderer/routes/create-organization/page.tsx
  • apps/desktop/src/renderer/routes/page.tsx
  • apps/desktop/src/renderer/routes/sign-in/page.tsx
  • biome.jsonc

📝 Walkthrough

Walkthrough

The desktop renderer adds a loop-safe Redirect component, migrates redirects to route loading or the shared component, adds dashboard content error handling, and prevents future Navigate imports.

Changes

Desktop routing

Layer / File(s) Summary
Loop-safe Redirect component
apps/desktop/src/renderer/components/Redirect/*, apps/desktop/package.json
Adds href-based navigation deduplication, retry handling, no DOM output, and tests for navigation behavior.
Route-loading legacy redirects
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/*, apps/desktop/src/renderer/routes/_authenticated/settings/*
Moves legacy task and settings redirects into beforeLoad while preserving route and search parameters.
Shared Redirect route adoption
apps/desktop/src/renderer/routes/page.tsx, apps/desktop/src/renderer/routes/sign-in/*, apps/desktop/src/renderer/routes/create-organization/*, apps/desktop/src/renderer/routes/_authenticated/*, biome.jsonc
Replaces renderer Navigate usage with Redirect and adds a lint restriction against new Navigate imports.
Dashboard content error boundary
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardContentError/*
Adds dashboard error reporting, recovery actions, URL-based boundary resets, and shared version-mismatch redirection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Route
  participant Redirect
  participant TanStackRouter
  participant Destination
  Route->>Redirect: render redirect options
  Redirect->>TanStackRouter: navigate once per href
  TanStackRouter->>Destination: load and render target route
Loading

Possibly related PRs

Suggested reviewers: saddlepaddle

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch harden-route-redirects

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 17 files

You’re at about 90% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Architecture diagram
sequenceDiagram
    participant UI as React Tree
    participant Redirect as Redirect Component
    participant Router as TanStack Router
    participant Layout as Dashboard Layout
    participant Boundary as CatchBoundary
    participant ErrorPane as DashboardContentError
    participant Sentry as Sentry
    participant Biome as Biome Linter

    Note over UI,Router: NEW: Loop-proof redirect flow
    UI->>UI: Parent re-renders
    UI->>Redirect: Renders inline with props
    Redirect->>Router: buildLocation(props)
    Router-->>Redirect: resolved href
    Redirect->>Redirect: Compare href with lastHrefRef
    alt href changed
        Redirect->>Router: navigate(props)
        Router-->>UI: New route rendered
    else href unchanged
        Note over Redirect: Skip navigation (loop-proof)
    end

    Note over UI,Router: Static redirects via beforeLoad
    UI->>Router: Navigate to static route
    Router->>Router: Execute beforeLoad
    Router->>Router: throw redirect(...)
    Router-->>UI: Redirect to new route before render

    Note over UI,Layout: Dashboard content crash containment
    UI->>Layout: Render dashboard layout
    Layout->>Boundary: Wrap Outlet with CatchBoundary
    Boundary->>Boundary: Track location.pathname as reset key
    Boundary->>UI: Render content route
    alt Content route throws
        Boundary-->>ErrorPane: Capture error
        ErrorPane->>Sentry: captureException (lazy import)
        ErrorPane-->>UI: Render error pane (sidebar alive)
    else Content renders fine
        Boundary-->>UI: Render normal content
    end

    Note over UI,Biome: CHANGED: Enforce safe redirect patterns
    Biome->>Biome: Check import restrictions
    alt Import of Navigate detected
        Biome-->>UI: Lint error (CI fails)
    else Use Redirect or beforeLoad
        Biome-->>UI: Pass
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@github-actions

github-actions Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) ✅ View Branch
Vercel API (Vercel) ✅ Open Preview
Vercel Web (Vercel) ✅ Open Preview
Vercel Marketing (Vercel) ✅ Open Preview
Vercel Admin (Vercel) ✅ Open Preview
Vercel Docs (Vercel) ✅ Open Preview

Preview updates automatically with new commits

@Kitenite

Copy link
Copy Markdown
Member Author

Addressed all four review items:

  • Redirect (P1) — navigate() now has a .catch that logs and clears the handled marker so a failed redirect can retry instead of leaking an unhandled rejection.
  • CatchBoundary reset key (P2) — now keys on the full location href (was pathname), so a same-path search/hash change recovers a stuck error pane.
  • Sentry failure (P2) — logged instead of swallowed.
  • break-words (P3) — added so long path/URL errors wrap.

Redirect loop-proof unit test still green; typecheck/lint/full desktop suite (2669) pass. Commit 9678cd4.

@Kitenite
Kitenite changed the base branch from fix-workspace-deletion-re to main August 11, 2026 22:12
…shes

Makes the SUPER-1814 crash class unreachable rather than fixing sites
one at a time.

- add renderer Redirect component: re-navigates only when the resolved
  target href changes, not on props-identity change like TanStack's
  <Navigate> (which loops into React #185 when rendered inline)
- ban Navigate in the renderer via biome noRestrictedImports, pointing
  at Redirect or beforeLoad+redirect; CI already fails on warnings
- migrate the 5 hoisted + 1 versionMismatch <Navigate> sites to Redirect
- convert static/legacy redirects (settings index, presets, tasks PR
  tabs, tasks PR detail) to beforeLoad throw redirect() so no navigation
  happens at render time at all
- fix cloud settings gates bouncing entitled users while the CLOUD_ACCESS
  flag is still loading (undefined read as no-access)
- wrap the dashboard Outlet in a CatchBoundary so a crashing content
  route shows an error pane with the sidebar/terminals alive, instead of
  bubbling to the root boundary and unmounting the whole app

Verified over CDP: all redirect targets resolve with no #185 signals; a
forced render throw in a dashboard route is contained to the content
pane (sidebar survives, root error page not shown).
Renders Redirect through a real TanStack router (happy-dom + testing-
library, mirroring packages/chat) and asserts it navigates exactly once
even when its parent re-renders 10x handing it a fresh props object each
time — the precise condition that loops <Navigate> into React #185.
Mutation-verified: reverting Redirect to props-identity comparison makes
this test report 11 navigations instead of 1.

happy-dom registers process-wide globals and Bun runs test files in one
process, so the file unregisters in afterAll to restore the shared mock
document — without it, readonly DOM globals leak and break unrelated
suites (e.g. v1-migration's globalThis.localStorage assignment). Full
desktop suite verified green (2669 pass, exit 0).
- Redirect: catch failed navigations (log + clear the handled marker so
  a later render can retry) instead of leaking an unhandled rejection
- dashboard CatchBoundary: reset on full location href, not just
  pathname, so a same-path search/hash change (filter/tab) recovers a
  stuck error pane
- DashboardContentError: log Sentry reporting failures instead of
  swallowing them; add break-words so long path/URL errors wrap
@Kitenite
Kitenite force-pushed the harden-route-redirects branch from 9678cd4 to bce1863 Compare August 11, 2026 22:13
@Kitenite
Kitenite merged commit 2b6f51e into main Aug 11, 2026
11 of 13 checks passed

This branch was previously deployed

1 inactive deployment
preview — bce18639 Deployed Aug 11, 2026 by Kitenite via Deploy Docs #7470
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.

1 participant