Skip to content

Load-then-render SPA navigation with subtle top progress bar - #608

Merged
kentcdodds merged 20 commits into
mainfrom
cursor/load-then-render-navigation-6834
Jul 5, 2026
Merged

kentcdodds merged 20 commits into
mainfrom
cursor/load-then-render-navigation-6834

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 4, 2026 •

Copy link
Copy Markdown
Member

What

Changes client-side navigation from "swap UI, then fetch, then update again" to React Router-style preload-then-commit: on navigation the router first runs a client loader that fetches the destination route's data, and only then commits the URL change and UI swap — so the UI updates exactly once, already populated. Adds a subtle 3px progress bar along the very top of the page (spin-delay semantics: appears only after 150ms pending, shown at least 200ms — no spinner). Also adds intent prefetch (like React Router's prefetch="intent"): hovering, focusing, or touching an internal link speculatively starts its route loader, and the click adopts the in-flight or settled result instead of refetching.

navigation_single_update_demo.mp4

Nav across account/secrets/integrations/tokens/connectors/admin/community renders each page fully populated in a single update — no "Loading …" flashes.

progress_bar_demo.mp4

Progress bar demo (with a temporary 1.5s artificial loader delay, since local loads complete under the 150ms threshold): the previous page stays visible while the bar trickles, then the new page swaps in complete.

hover_intent_prefetch_single_request_demo.mp4

Intent prefetch demo: hovering the "Secrets" nav link fires secrets.json before any click (visible in the DevTools network log); the click then navigates instantly and the log still shows exactly one request — the prefetch was reused.

How

  • client-router.tsx: route-loader registry (registerRouteLoaders, matched with remix/route-pattern), async navigate() that aborts superseded navigations (latest wins), runs the matched loader, then commits (pushState + notify). Applies to link clicks, popstate (old UI stays until data is ready), GET/POST form submissions, and same-path refreshes after POST redirects. Hash-only moves — forward clicks and popstate — commit immediately without running loaders. New navigationstart/navigationend events drive the progress bar. Loader errors commit anyway and let the route's fallback fetch surface the error; the router marks the destination stale so same-path refreshes (no href change) still trigger that fallback refetch.
  • route-loader.ts: loaders now return RouteLoaderRedirect (via routeLoaderRedirect) instead of calling window.location.assign themselves; the router performs the full-document redirect at commit time. This keeps speculative prefetch runs side-effect free — hovering a link whose loader hits a 401 can never redirect the page the user is still on.
  • intent-prefetch.ts: single latest-wins prefetch slot. Hover intent is debounced (100ms dwell) so sweeping the cursor across a nav list fires nothing; focus and touch prefetch immediately (and cancel any pending hover timer so it can't fire later and clobber the deliberate prefetch). Navigations adopt the pending or freshly settled promise (30s TTL, monotonic clock); failed prefetches fall back to a normal loader run; hovering a different link aborts the previous prefetch; form POSTs abort pending prefetches so pre-mutation data is never adopted. Navigating anywhere other than the prefetched link aborts and clears the slot, so a speculative result for a link the user never followed can't be adopted later. Prefetch is skipped for the current page and for the path an active navigation is already loading. Opt out per link with data-prefetch="none".
  • navigation-data.ts: single-slot, consume-once preloaded data store; tryConsumeRouteLoaderData in loader-data-context.tsx unifies SSR-embedded and navigation-preloaded consumption. Also holds the one-shot stale marker (markNavigationDataStale / consumeStaleNavigationData) routes consume to force a refetch after a failed same-path refresh.
  • navigation-progress.tsx: fixed top bar, colors.primary, trickles to 90%, completes and fades on end; spin-delay-style 150ms show delay + 200ms minimum visibility.
  • All data routes converted to export loaders (account, integrations, package tokens, connectors, secrets, admin users/roles/reports, oauth-authorize, community index/detail). Each keeps its fallback fetch path for loader-error and edge cases. Admin routes reset their load-failure latch when the route href changes. Routes no longer pre-set their "loaded href" to a destination before navigate() commits — the async navigation made that optimistic set race interim renders and fire spurious fallback fetches for the old URL.
  • frame-prefetch.ts: community pages prefetch their <Frame> HTML in the loader; resolveFrame in entry.tsx consumes the cache (consume-once, cleared per prefetch) so frame content is instant after commit.
  • Docs: request-lifecycle client navigation section updated (preload-then-commit + intent prefetch).

Testing

  • npm run validate — all six checks green (format, lint, typecheck, unit tests, Playwright E2E, MCP E2E).
  • Manual browser testing (logged in as seeded admin): all nav links, community search, back/forward, logout/login form POSTs, DevTools console clean; verified no loading-text flashes and single-update renders (videos above).
  • Intent prefetch verified end-to-end in headless Chromium and desktop Chrome: hover fires exactly one request which the click reuses (1 total request per navigation); quick sweeps across links fire nothing; keyboard focus + Enter reuses the prefetch; hovering an auth-only link while logged out does not redirect, but clicking it does; hovering link B supersedes and aborts link A's prefetch; hover-A-then-focus-B prefetches only B.
  • Bug-repro scripts run against both the pre-fix and post-fix builds: same-path refresh with a failing loader now refetches (was: silent stale UI); token-page URL updates no longer double-fetch from an optimistic href race (2 GETs → 1); hash-only back/forward no longer runs route loaders while cross-path back still does.
  • Progress bar verified with an artificial loader delay (reverted before commit).
  • All Bugbot and CodeRabbit findings addressed across five review rounds; the final Bugbot run passes with no findings. One earlier suggestion ("aborted navigations skip navigationend") was assessed as a false positive: every abort comes from a superseding navigation that owns the matching navigationend, and the progress bar's pending flag is a boolean, so the winning navigation's end always clears it.
System recap — extends app-ui (medium risk)

Mode: recap · Base: main @ e51fa91 · Head: f5cef55

Classification: extends — changes the browser app's client navigation contract (preload-then-commit + intent prefetch) and adds a navigation progress indicator; no new primitives, no server route changes.

Primitives touched

Primitive Group Impact
app-ui surfaces extends — client router runs route loaders before committing navigations; hover/focus intent prefetch; new progress bar

System map

flowchart LR
	appUi["app-ui"]:::extended
	appSessions["app-sessions"]:::untouched
	d1AppDb["d1-app-db"]:::untouched
	appUi --> appSessions --> d1AppDb
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Change flow

sequenceDiagram
	participant User
	participant Router as client-router
	participant Prefetch as intent-prefetch
	participant Loader as route loader
	participant UI as Router component

	User->>Router: hover / focus / touch internal link
	Router->>Prefetch: debounce 100ms, then run loader speculatively
	Prefetch->>Loader: fetch destination JSON (+ frame HTML prefetch)
	User->>Router: click link / popstate / form redirect
	Router->>Router: navigationstart (progress bar arms, 150ms delay)
	Router->>Prefetch: adopt pending/settled result (or run loader now)
	Loader-->>Router: Partial<AppLoaderData> or RouteLoaderRedirect
	Router->>Router: store preloaded slot, pushState
	Router->>UI: navigate event → single render with data
	Router->>Router: navigationend (bar completes, fades)
Loading

Before / after

Aspect Before After
SPA navigation swap UI → "Loading …" → fetch → second update run loader → commit once, fully populated
Link hover nothing loader starts speculatively; click adopts it
Back/forward instant swap + refetch old UI stays until data ready, then one swap
Pending indication per-route "Loading …" text top progress bar (>150ms only, min 200ms shown)
Loader auth redirects loader called window.location.assign directly loader returns redirect; router performs it at commit
Community frames frame fetched after route swap frame HTML prefetched in loader, instant swap

Invariants

Per-user isolation unaffected: loaders call the same authenticated JSON APIs the routes already used; 401s still redirect to login at commit time — and never from a speculative prefetch.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added route-loader-driven data loading across account, admin, community, and OAuth pages.
    • Implemented preload-then-commit SPA navigation with one-slot preloaded loader data for faster first render.
    • Added link intent prefetching for route loaders, plus a cached frame prefetch system.
    • Introduced a top-of-viewport navigation progress indicator.
  • Bug Fixes
    • Improved navigation consistency for redirects, back/forward, same-path refresh, and hash-only updates.
    • Prevented displaying stale, pre-mutation data by aborting pending prefetches on form POST.
  • Tests
    • Added coverage for route-loader matching, prefetch adoption, and preloaded navigation data consumption.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds route-loader-driven client navigation, a single-slot preloaded navigation store, intent prefetch, a navigation progress bar, frame prefetch caching, and route-specific loader migrations across account, admin, community, and OAuth client routes.

Changes

Preload-then-commit navigation

Layer / File(s) Summary
Loader contracts and matching
packages/worker/client/route-loader.ts, packages/worker/client/client-router.tsx, packages/worker/client/client-router.node.test.ts, packages/worker/client/routes/index.tsx, packages/worker/client/app.tsx
Adds route-loader contracts, registration, matching, and loader map wiring with specificity tests.
Preloaded navigation data
packages/worker/client/navigation-data.ts, packages/worker/client/navigation-data.node.test.ts, packages/worker/client/loader-data-context.tsx
Adds normalized href storage, consume-once helpers, route-loader fallback consumption, and coverage tests.
Loader-driven navigation flow
packages/worker/client/client-router.tsx, packages/worker/client/routes/index.tsx
Reworks navigation, form submission, popstate handling, and same-path refresh around loader execution and navigation lifecycle events.
Intent prefetch, progress bar, and frame cache
packages/worker/client/intent-prefetch.ts, packages/worker/client/intent-prefetch.node.test.ts, packages/worker/client/navigation-progress.tsx, packages/worker/client/frame-prefetch.ts, packages/worker/client/entry.tsx, packages/worker/client/app.tsx
Adds intent prefetch, navigation progress UI, frame prefetch caching, and cached frame consumption plus app wiring.
Route loader migrations
packages/worker/client/routes/account*.tsx, packages/worker/client/routes/admin-*.tsx, packages/worker/client/routes/community*.tsx, packages/worker/client/routes/oauth-authorize.tsx
Adds route loaders and switches route components to consume route-loader data instead of embedded-loader hydration.
Architecture documentation
docs/contributing/architecture/request-lifecycle.md
Documents the preload-then-commit navigation model, intent prefetch, and progress bar behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant ClientRouter
  participant RouteLoader
  participant NavStore
  participant RouteComponent

  App->>ClientRouter: registerRouteLoaders(clientRouteLoaders)
  ClientRouter->>ClientRouter: navigate(to)
  ClientRouter->>RouteLoader: matched loader(url, signal)
  RouteLoader-->>ClientRouter: RouteLoaderResult
  ClientRouter->>NavStore: setPreloadedNavigationData(href, data)
  ClientRouter->>ClientRouter: commit history + notify
  RouteComponent->>NavStore: tryConsumeRouteLoaderData(key, href)
  NavStore-->>RouteComponent: route-loader data or fallback
  ClientRouter->>ClientRouter: dispatch navigationend
Loading

Possibly related PRs

  • kentcdodds/kody#75: Both PRs modify packages/worker/client/routes/account-secrets.tsx and its refresh/hydration flow.
  • kentcdodds/kody#451: Both PRs intersect on packages/worker/client/routes/account.tsx and related account profile data flow.
  • kentcdodds/kody#550: Both PRs touch packages/worker/client/routes/account-integrations.tsx and its client-side data loading path.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main navigation flow change and the added top progress bar.
✨ 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 cursor/load-then-render-navigation-6834

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.

@kentcdodds
kentcdodds marked this pull request as ready for review July 4, 2026 21:20
Comment thread packages/worker/client/routes/account.tsx Outdated
Comment thread packages/worker/client/client-router.tsx
@github-actions

github-actions Bot commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-608.kentcdodds.workers.dev

Worker: kody-pr-608
D1: kody-pr-608-db
KV: kody-pr-608-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/worker/client/routes/admin-community-reports.tsx (1)

227-240: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate stale fallback loads when route-loader data wins.

If an older loadReports() fallback is still in flight, applyRouteLoaderData() can render the preloaded destination data, then the old fallback can pass the unchanged loadRequestId check and overwrite the UI with stale reports.

Proposed fix
 function applyRouteLoaderData(href: string) {
 	if (!isAdminCommunityReportsPath(href)) return false
 	const routeData = tryConsumeRouteLoaderData(
 		handle,
 		'adminCommunityReports',
 		href,
 	)
 	if (!routeData) return false
+	loadRequestId += 1
+	loadingForHref = null
+	lastFailedHref = null
 	reports = routeData.reports
 	statusFilter = routeData.statusFilter
 	status = 'ready'
 	message = null
 	lastLoadedHref = href
 	return true
 }

Also applies to: 250-254

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/admin-community-reports.tsx` around lines 227 -
240, The applyRouteLoaderData/loadReports flow in adminCommunityReports is
allowing an older fallback request to overwrite newer route-loader data. Update
applyRouteLoaderData and the loadReports path so that when
tryConsumeRouteLoaderData wins, any in-flight fallback request is invalidated
(for example by bumping the request token or marking the current load stale)
before setting reports/status/message, and ensure the loadRequestId check in
loadReports cannot accept the obsolete response afterward.
packages/worker/client/routes/admin-roles.tsx (1)

101-109: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate stale fallback loads when applying route-loader data.

An in-flight loadAdminRoles() started before navigation can still pass the unchanged loadRequestId check after preloaded role data is applied, then overwrite the committed route-loader result.

Proposed fix
 function applyRouteLoaderData(href: string) {
 	if (!isAdminRolesPath(href)) return false
 	const routeData = tryConsumeRouteLoaderData(handle, 'adminRoles', href)
 	if (!routeData) return false
+	loadRequestId += 1
+	loadingForHref = null
+	lastFailedHref = null
 	roles = routeData.roles
 	status = 'ready'
 	message = null
 	lastLoadedHref = href
 	return true
 }

Also applies to: 117-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/admin-roles.tsx` around lines 101 - 109, The
route-loader commit path in applyRouteLoaderData should also invalidate any
in-flight loadAdminRoles() fallback request so it cannot overwrite the newly
applied data. Update the state handling around applyRouteLoaderData and
loadAdminRoles to bump or reset the shared loadRequestId (or equivalent guard)
whenever preloaded adminRoles data is consumed, ensuring any older request
started before navigation fails its commit check and cannot replace the
committed route-loader result.
packages/worker/client/routes/admin-users.tsx (1)

212-233: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate stale fallback loads before showing preloaded admin users.

If a previous loadAdminUsers() fallback finishes after route-loader data is applied, it can still pass the unchanged loadRequestId guard and overwrite the committed users/page state.

Proposed fix
 function applyRouteLoaderData(href: string) {
 	if (!isAdminUsersPath(href)) return false
 	const routeData = tryConsumeRouteLoaderData(handle, 'adminUsers', href)
 	if (!routeData) return false
+	loadRequestId += 1
+	loadingForHref = null
+	lastFailedHref = null
 	users = routeData.users
 	availableRoles = routeData.availableRoles
 	page = routeData.page
 	pageSize = routeData.pageSize
 	total = routeData.total

Also applies to: 242-246

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/admin-users.tsx` around lines 212 - 233, The
admin users route can still be overwritten by a late fallback load because
applyRouteLoaderData commits preloaded data without invalidating any in-flight
loadAdminUsers() request that shares the same loadRequestId guard. Update
applyRouteLoaderData in admin-users.tsx to bump or reset the request token/state
used by loadAdminUsers() so any older fallback response is treated as stale, and
make sure the same invalidation is applied wherever the committed route-loader
data is handled to prevent it from reapplying users/page after preloaded data is
shown.
🧹 Nitpick comments (4)
packages/worker/client/frame-prefetch.ts (1)

21-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated header-building logic with entry.tsx.

The Accept: text/html + optional REMIX_FRAME_TARGET_HEADER construction here duplicates the equivalent block in entry.tsx's resolveFrame fallback. Extracting a shared helper keeps both call sites in sync if the header contract changes.

♻️ Proposed extraction
+export function buildFrameHeaders(target: string | undefined): Headers {
+	const headers = new Headers({ Accept: 'text/html' })
+	if (target) {
+		headers.set(REMIX_FRAME_TARGET_HEADER, target)
+	}
+	return headers
+}
+
 export async function prefetchFrame(
 	src: string,
 	target: string | undefined,
 	signal: AbortSignal,
 ): Promise<void> {
 	clearPrefetchedFrames()
 	try {
-		const headers = new Headers({ Accept: 'text/html' })
-		if (target) {
-			headers.set(REMIX_FRAME_TARGET_HEADER, target)
-		}
+		const headers = buildFrameHeaders(target)
 		const response = await fetch(src, { headers, signal })

Then in entry.tsx:

-		const headers = new Headers({ Accept: 'text/html' })
-		if (target) {
-			headers.set(REMIX_FRAME_TARGET_HEADER, target)
-		}
+		const headers = buildFrameHeaders(target)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/frame-prefetch.ts` around lines 21 - 41, The header
construction in prefetchFrame duplicates the same
Accept/REMIX_FRAME_TARGET_HEADER logic used in entry.tsx’s resolveFrame
fallback, so extract that shared logic into a reusable helper and have both call
sites use it. Update prefetchFrame to delegate header creation to the helper,
and adjust the resolveFrame fallback in entry.tsx to use the same helper so any
future header contract changes stay consistent in one place.
packages/worker/client/routes/oauth-authorize.tsx (1)

51-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate fetch/validate logic with loadInfo.

This loader re-implements the same fetch → .json().catch(() => null) → ok/payload.ok validation performed in loadInfo (Lines 108-148). Extracting a shared fetchAuthorizeInfo(query, signal?) helper used by both the route loader and the fallback path would reduce the risk of the two implementations drifting when the /oauth/authorize-info contract changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/oauth-authorize.tsx` around lines 51 - 82, The
oauthAuthorizeRouteLoader function duplicates the same `/oauth/authorize-info`
fetch and response validation logic already implemented in loadInfo. Extract a
shared helper such as fetchAuthorizeInfo(query, signal?) that performs the
fetch, JSON parsing, and ok/payload.ok handling, then have both
oauthAuthorizeRouteLoader and loadInfo call it so the contract stays consistent
if the endpoint changes.
packages/worker/client/routes/community-detail.tsx (2)

51-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for the new route loader.

communityDetailRouteLoader has several distinct branches (missing listingId, 404, non-ok response/payload, success) but no accompanying unit test in this cohort, unlike the router/navigation-data layers earlier in the stack. Given this sits on the critical navigation path, tests would help lock in the fallback-to-fetch contract.
Want me to draft unit tests for the branches (missing id, 404, non-ok payload, success)?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/community-detail.tsx` around lines 51 - 91, Add
unit test coverage for communityDetailRouteLoader to lock in its
fallback-to-fetch behavior across all branches. Use the
communityDetailRouteLoader symbol and cover missing listingId, 404 from the API,
non-ok response or payload, and the successful return shape with frame prefetch.
Mock getListingIdFromPathname, fetch, readJson, and prefetchFrame so the tests
assert the correct errors and returned communityDetailShell data without relying
on real network behavior.

51-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared shell-fetch logic.

communityDetailRouteLoader re-implements the fetch → 404-check → readJson → ok-check sequence that loadDetailShell (below) already contains. Factoring this into a small shared helper (e.g. fetchCommunityDetailShell(listingId, signal?)) would keep the API contract in one place and prevent the two paths from drifting apart as the endpoint evolves.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/client/routes/community-detail.tsx` around lines 51 - 91,
Extract the shared community detail shell loading sequence so it is implemented
in one place instead of duplicated between communityDetailRouteLoader and
loadDetailShell. Create or reuse a small helper that performs the fetch, 404
handling, readJson parsing, and ok validation, then have
communityDetailRouteLoader call that helper before building
communityDetailShell. Keep the helper aligned with the existing loadDetailShell
behavior so both paths use the same API contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/client/client-router.tsx`:
- Around line 255-259: commitImmediateNavigation currently always emits
navigationstart/navigationend and ignores the suppressStart option, which can
create an extra unpaired navigation event in the hash-only fast path. Update
commitImmediateNavigation in client-router.tsx to accept and honor the same
options used by runNavigationWithLoader, and make navigateInternal pass through
options so redirects from navigateWithRefreshForSamePath and
submitFormThroughRouter with suppressStart true skip dispatchNavigationStart
consistently. Keep the start/end pairing behavior aligned with the existing
navigation contract.
- Around line 330-379: The form submit flow in submitFormThroughRouter is
missing the shared navigation cancellation behavior used by
runNavigationWithLoader, so a stale POST can still redirect after a newer
navigation. Update the POST fetch in submitFormThroughRouter to use the shared
navigation abort controller signal, and before calling
navigateWithRefreshForSamePath on response.redirected or Location-based
redirects, verify the current navigation is still active and bail out if it was
superseded. Keep the abort and stale-navigation checks aligned with the existing
navigation helpers (navigationAbortController, navigateWithRefreshForSamePath,
navigateInternal).

---

Outside diff comments:
In `@packages/worker/client/routes/admin-community-reports.tsx`:
- Around line 227-240: The applyRouteLoaderData/loadReports flow in
adminCommunityReports is allowing an older fallback request to overwrite newer
route-loader data. Update applyRouteLoaderData and the loadReports path so that
when tryConsumeRouteLoaderData wins, any in-flight fallback request is
invalidated (for example by bumping the request token or marking the current
load stale) before setting reports/status/message, and ensure the loadRequestId
check in loadReports cannot accept the obsolete response afterward.

In `@packages/worker/client/routes/admin-roles.tsx`:
- Around line 101-109: The route-loader commit path in applyRouteLoaderData
should also invalidate any in-flight loadAdminRoles() fallback request so it
cannot overwrite the newly applied data. Update the state handling around
applyRouteLoaderData and loadAdminRoles to bump or reset the shared
loadRequestId (or equivalent guard) whenever preloaded adminRoles data is
consumed, ensuring any older request started before navigation fails its commit
check and cannot replace the committed route-loader result.

In `@packages/worker/client/routes/admin-users.tsx`:
- Around line 212-233: The admin users route can still be overwritten by a late
fallback load because applyRouteLoaderData commits preloaded data without
invalidating any in-flight loadAdminUsers() request that shares the same
loadRequestId guard. Update applyRouteLoaderData in admin-users.tsx to bump or
reset the request token/state used by loadAdminUsers() so any older fallback
response is treated as stale, and make sure the same invalidation is applied
wherever the committed route-loader data is handled to prevent it from
reapplying users/page after preloaded data is shown.

---

Nitpick comments:
In `@packages/worker/client/frame-prefetch.ts`:
- Around line 21-41: The header construction in prefetchFrame duplicates the
same Accept/REMIX_FRAME_TARGET_HEADER logic used in entry.tsx’s resolveFrame
fallback, so extract that shared logic into a reusable helper and have both call
sites use it. Update prefetchFrame to delegate header creation to the helper,
and adjust the resolveFrame fallback in entry.tsx to use the same helper so any
future header contract changes stay consistent in one place.

In `@packages/worker/client/routes/community-detail.tsx`:
- Around line 51-91: Add unit test coverage for communityDetailRouteLoader to
lock in its fallback-to-fetch behavior across all branches. Use the
communityDetailRouteLoader symbol and cover missing listingId, 404 from the API,
non-ok response or payload, and the successful return shape with frame prefetch.
Mock getListingIdFromPathname, fetch, readJson, and prefetchFrame so the tests
assert the correct errors and returned communityDetailShell data without relying
on real network behavior.
- Around line 51-91: Extract the shared community detail shell loading sequence
so it is implemented in one place instead of duplicated between
communityDetailRouteLoader and loadDetailShell. Create or reuse a small helper
that performs the fetch, 404 handling, readJson parsing, and ok validation, then
have communityDetailRouteLoader call that helper before building
communityDetailShell. Keep the helper aligned with the existing loadDetailShell
behavior so both paths use the same API contract.

In `@packages/worker/client/routes/oauth-authorize.tsx`:
- Around line 51-82: The oauthAuthorizeRouteLoader function duplicates the same
`/oauth/authorize-info` fetch and response validation logic already implemented
in loadInfo. Extract a shared helper such as fetchAuthorizeInfo(query, signal?)
that performs the fetch, JSON parsing, and ok/payload.ok handling, then have
both oauthAuthorizeRouteLoader and loadInfo call it so the contract stays
consistent if the endpoint changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d9292e3-266c-4e2f-bec6-8117b9b60861

📥 Commits

Reviewing files that changed from the base of the PR and between e51fa91 and d3dee69.

📒 Files selected for processing (22)
  • docs/contributing/architecture/request-lifecycle.md
  • packages/worker/client/app.tsx
  • packages/worker/client/client-router.node.test.ts
  • packages/worker/client/client-router.tsx
  • packages/worker/client/entry.tsx
  • packages/worker/client/frame-prefetch.ts
  • packages/worker/client/loader-data-context.tsx
  • packages/worker/client/navigation-data.node.test.ts
  • packages/worker/client/navigation-data.ts
  • packages/worker/client/navigation-progress.tsx
  • packages/worker/client/routes/account-integrations.tsx
  • packages/worker/client/routes/account-package-invocation-tokens.tsx
  • packages/worker/client/routes/account-remote-connectors.tsx
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/client/routes/account.tsx
  • packages/worker/client/routes/admin-community-reports.tsx
  • packages/worker/client/routes/admin-roles.tsx
  • packages/worker/client/routes/admin-users.tsx
  • packages/worker/client/routes/community-detail.tsx
  • packages/worker/client/routes/community.tsx
  • packages/worker/client/routes/index.tsx
  • packages/worker/client/routes/oauth-authorize.tsx

Comment thread packages/worker/client/client-router.tsx Outdated
Comment thread packages/worker/client/client-router.tsx
Comment thread packages/worker/client/client-router.tsx
… hash commits, store preloads only at commit
Comment thread packages/worker/client/client-router.tsx
Comment thread packages/worker/client/routes/oauth-authorize.tsx Outdated
Comment thread packages/worker/client/routes/account-secrets.tsx
Comment thread packages/worker/client/intent-prefetch.ts Outdated
Comment thread packages/worker/client/routes/admin-users.tsx
Comment thread packages/worker/client/client-router.tsx
Comment thread packages/worker/client/routes/account-package-invocation-tokens.tsx
Comment thread packages/worker/client/routes/community-detail.tsx Outdated
Comment thread packages/worker/client/client-router.tsx
Comment thread packages/worker/client/client-router.tsx

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d44e488. Configure here.

Comment thread packages/worker/client/client-router.tsx
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.

2 participants