Repository navigation
perf(web): halve the cold-start bundle by splitting Clerk and cold routes - #9058
UI Consistency: no issues found
All clear — no UI consistency violations found in the changed web UI code.
The previously flagged blank first paint (top-level Suspense fallback={null} tearing down the #boot-shell splash) is resolved at this head: apps/web/src/main.tsx now gates the first createRoot().render() on Promise.all([<selected managed-auth shell import>, router.load()]), so the HTML splash holds until real UI can paint, and the failure path replaces it with a readable message instead of leaving it up forever.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Files reviewed (in scope): apps/web/src/main.tsx, apps/web/src/router.ts, apps/web/src/components/AppSidebarLayout.tsx, apps/web/src/components/settings/ThemeEditorHost.tsx, apps/web/src/components/clerk/BrowserManagedAuthShell.tsx, apps/web/src/components/clerk/ElectronManagedAuthShell.tsx, apps/web/src/lib/chunkReloadGuard.ts (+ test). apps/web/vite.config.ts and apps/web/index.html were read as context only.
Checks performed:
- Boot/splash ownership:
#boot-shellmarkup lives inside#rootinindex.html; the new render gate keeps it mounted until the auth shell and initial route chunks resolve, so the previous splash → blank → app sequence no longer occurs. The catch path writes into the existing centered#boot-shellcontainer, which keeps its inherited theme background/foreground. - New
Suspense fallback={null}boundaries (SettingsSidebarNav,ThemeEditorPanel) match the existing convention inChatView.tsx(PreviewPanel,DiffPanel,FilePreviewPanel). The sidebar boundary mounts during a router navigation transition, where React holds the previous UI rather than flashing an empty sidebar; the theme editor boundary only mounts when a session opens. - No new call sites recreate a core primitive;
SettingsSidebarNavstill composesButton,Input,Kbd, and the sidebar primitives unchanged (it was moved behind a lazy import only, not edited). - No Tailwind/global CSS ownership, theme variant, scroll/virtualized fade, or environment-routing surfaces were touched by this diff.
defaultPreload: "intent"only triggers routebeforeLoadguards, which are pure redirect checks (settings.tsx,projects.$projectKey.tsx,_chat.tsx) or memoized (__root.tsx), so hover prefetching introduces no user-visible side effects.
🔍 Agent activity — 20 tool calls
💭 The prior Suspense fallback issue looks resolved now—the diff shows modules being resolved via Promise.all before render. I'm now checking main.tsx, which awaits router.load() and the auth shell before rendering, keeping the boot splash held properly, and I'm looking at how the error path updates the boot shell's text content. I need to check whether
#boot-shellactually exists in index.html. I'm also noting that ThemeEditorHost and SettingsSidebarNav both use lazy loading with Suspense fallback null, which could cause brief blank states—probably fine for ThemeEditorHost since it's user-triggered, but the settings sidebar popping in blank is worth checking against preload behavior. Let me open index.html and the related files to confirm.
1. browse_code — apps/web/index.html
2. browse_code — apps/web/src/main.tsx
💭 Now I want to check index.html to see the structure of the #boot-shell element.
3. git_tools — boot-shell
💭 I'm noting that replacing the boot-shell content with plain text is a minor styling issue but intentionally clears the logo, and confirming main.tsx falls within scope while index.html doesn't. I'm also examining whether the lazy-loaded settings sidebar nav with a null Suspense fallback could cause a brief empty sidebar flash when navigating to settings routes.
4. browse_code — apps/web/src/components/AppSidebarLayout.tsx
5. browse_code — apps/web/src/components/settings/SettingsSidebarNav.tsx
💭 I'm also weighing that lazy-loading the settings sidebar means the "/" keyboard shortcut won't work until that chunk loads, though that seems acceptable since it's settings-only. The bigger concern is the Suspense fallback rendering null, which would leave the sidebar body empty during the load — but since the settings route itself is already lazy-loaded with preloading enabled, this may not be a real regression. Actually, since navigation waits for the route chunk to resolve before committing, but the sidebar nav chunk only starts fetching after that commit happens, there'd be a brief moment where the settings page renders with an empty sidebar — the two chunks fetch serially rather than in parallel. I'm trying to figure out if this visual gap is significant enough to flag as a layout preservation issue, or if it's negligible in practice. I should also check whether SETTINGS_NAV_ITEMS is imported statically elsewhere in the startup graph, since that would defeat the whole point of lazy-splitting this module. Let me grep for usages of SETTINGS_NAV_ITEMS, SettingsSidebarNav, and ThemeEditorPanel to confirm.
6. git_tools — SETTINGS_NAV_ITEMS|SettingsSidebarNav
7. git_tools — ThemeEditorPanel|ThemeEditorHost
💭 I'm looking at ThemeEditorHost, where the panel now mounts lazily behind Suspense with a null fallback—so clicking "Edit theme" shows nothing until the chunk loads, which is fine locally but a gap on web with no loading feedback. I want to check if ThemeEditorPanel relied on that previous synchronous mount for behaviors like applying the initial theme state right away.
8. browse_code — apps/web/src/components/settings/ThemeEditorHost.tsx
💭 I'm now checking whether there are remaining UI-consistency issues since the earlier finding was resolved. One possibility is a chunk-load error in a lazy Suspense boundary for the settings sidebar or theme editor—if a stale deploy happens while the app is running, clicking into the theme editor could trigger a preload error that forces a reload and loses user state, which seems like a real product concern worth flagging. I'm weighing whether the mid-session reload behavior falls within scope for a UI-consistency check, since it's more of a UX/behavioral regression than a component-system issue, though it does touch on visual/state preservation. I also want to check whether the lazy-loaded ThemeEditorPanel gives any loading feedback when its fallback is null, and whether the repo has an established convention for lazy-loading with spinners or skeletons—so I should grep for existing lazy() usage across the web app to compare.
9. git_tools — Suspense fallback
10. git_tools — lazy(() =>
💭 I recall ChatView's lazy panels already use fallback={null}, so that's an existing convention here rather than a new inconsistency worth flagging. The more interesting case might be the sidebar, where SidebarContent/SidebarFooter would be missing during load, especially since the sidebar has resizing behavior tied to
data-app-sidebar. Checking whether autoCodeSplitting and the absence of a defaultPendingComponent could cause a blank screen — but since main.tsx awaits router.load() before rendering, and the router just stays pending until chunks resolve, this seems safe. I don't think there's a genuine regression here yet, but I want to keep looking. The main.tsx error handling just sets some fallback text with no reload button, which seems fine as-is. The Suspense fallback null in the sidebar means the footer briefly disappears during the first settings navigation, but only once per session, and I'm now wondering whether this new Suspense boundary interacts oddly with React 18 transitions during navigation, possibly hiding the sidebar temporarily on mount. Since TanStack Router likely wraps navigation in startTransition, React would actually hold the current view until the lazy sidebar chunk resolves rather than flashing a blank fallback, so this isn't really a problem. There's also a possible reload-related data loss issue from the vite:preloadError handler mid-session, but that seems out of scope for this particular UI consistency check. I'm checking the chunk reload guard logic for real bugs — it sets a flag, reloads, then clears the guard on successful boot, and there's a test covering it, so that looks fine. One subtlety worth noting is that the reload handler fires for any preload error, even ones that occur later in a session like from hover-intent prefetching, which would discard in-progress state, though this seems like an intentional tradeoff rather than a bug. I'm also weighing whether router.load() can actually reject on loader errors versus just capturing them into the route match error state — if it does reject in some edge case, the app would fall back to plain unstyled error text with no reload button, which would be a real UI regression, though this is speculative without confirming TanStack Router's actual rejection behavior. I should check whether callingrouter.load()beforeRouterProvidermounts could cause double-loading or history subscription issues, though that's not really a UI-consistency concern. I'm leaning toward concluding "all clear" unless something concrete turns up — the boot-shell error text visibility, thereloadScheduledguard, and the preload error handling all seem fine on closer look. The chunk-load-failure retry logic seems sound too:vite:preloadErroronly fires for built-app preload failures, and the reload-once flag correctly prevents infinite reload loops if the failure persists after a retry. Now I want to look more carefully at howSettingsSidebarNav's lazy loading and its null Suspense fallback interact in practice. For navigation-triggered lazy loads that happen inside a React transition, React's rule that newly-added Suspense boundaries don't show fallbacks during a transition means the old UI stays visible while the chunk loads—that behaves correctly. ButThemeEditorHostopening from a plain click handler isn't wrapped in a transition, so React will show the null fallback immediately, meaning nothing appears on screen until that chunk finishes loading. I'm satisfied the review is otherwise clean—environment routing, scroll fades, and Tailwind usage are untouched, and removing the staticSettingsSidebarNavimport shouldn't affect the dark theme variant. I'm also checking that both new shells shareclerkAppearancerather than duplicating it, and considering whether anything still eagerly imports the Clerk Electron/React packages, though that's more of a bundling concern than a UI consistency issue. Given the prior finding is resolved on the current head and no new issues have surfaced, I'm leaning toward declaring "All clear," though I need to format that within the required JSON schema rather than as a bare statement. I'm also double-checking whether an earlier unresolved thread needs to be marked resolved. I decide the fix clearly landed, so resolving the thread is the right call, and I'll fetch the review comment node id to do so. But before finalizing, I want to double check whether router.load() could hang on a slow initial route loader, which would leave the splash screen stuck indefinitely — that's a real concern worth verifying. I should check the root route and index route files for loaders that hit the server, since a cold-start query could delay the splash much longer than the old spinner-based approach did.
11. git_tools — loader:|beforeLoad:|defaultPendingComponent|pendingComponent
12. browse_code — apps/web/src/router.ts
💭 I should check the beforeLoad hooks in __root.tsx and _chat.tsx since their async work might now be blocking the splash screen.
13. browse_code — apps/web/src/routes/__root.tsx
14. browse_code — apps/web/src/routes/_chat.tsx
💭 I'm realizing the root beforeLoad now waits on a server call to resolve the initial auth gate state, whereas before, React rendered right away and the boot splash cleared while RouterProvider still had to run its own beforeLoad before showing anything meaningful. So this behavior isn't actually new — there was already a blank window during initial load before this change; keeping the splash up during that time is genuinely an improvement, not a regression. Though I should check whether a thrown error in root beforeLoad still surfaces through the styled RootRouteErrorView, or whether router.load() rejecting instead falls through to main.tsx's plain fallback text, which would lose the nicer error UI and retry/pairing guidance. I want to trace how TanStack Router's loadMatches captures route errors onto matches for error components versus when router.load() itself throws — I'm not fully certain whether errors from a route's beforeLoad get swallowed internally or propagate up, so I need to dig into the actual library version in use to confirm.
15. browse_code — In apps/web, what does resolveInitialServerAuthGateState() do — can it reject/throw (network errors), or does it catch internally and always resolve to a status? Also, what does RootRouteErrorView render? Show the relevant source.
16. git_tools — "@tanstack/react-router"
💭 I'm trying to recall whether a root beforeLoad rejection propagates as a thrown error from router.load(), or whether TanStack Router catches it internally and stores it on the match so RouterProvider can render the root error component instead. So general errors get set onto the match state rather than rethrown, meaning
router.load()resolves even on a beforeLoad failure and RouterProvider handles rendering the error UI. That confirms the.catchin main.tsx is really just guarding against the auth shell chunk import failing, which seems like a reasonable, narrow concern — so this part of the review looks solid. But there's a subtler issue worth flagging: initial render now waits forrouter.load()to finish, including the root beforeLoad's retry loop that can take up to 15 seconds, whereas previously React rendered right away and RouterProvider would show a blank pending state until matches resolved since there's no default pending component. That change in perceived load behavior seems worth calling out.
17. browse_code — apps/web/src/AppRoot.tsx
💭 Another possibility is the
defaultPreload: "intent"setting, which triggers routebeforeLoadhooks on hover — including settings and project routes. If those beforeLoad functions have side effects like navigation or RPC calls, hover preloading could unintentionally trigger them, so I want to check settings.tsx's beforeLoad for such effects. I also recall thatpair.tsxandconnect.tsxthrow redirects in their beforeLoad, and I'm trying to remember how TanStack Router'spreloadRoutehandles thrown redirects during preload — I believe it catches them and just skips preloading rather than following the redirect, but I should verify by checking settings beforeLoad since that's the likely hover target in the sidebar.
18. browse_code — apps/web/src/routes/settings.tsx
19. browse_code — apps/web/src/routes/projects.$projectKey.tsx
💭 The redirect from /settings to /settings/general during preload should be handled fine by TanStack's preload logic without causing UI issues. I'm also checking whether intent-based preloading could trigger unwanted network calls on hover, but since root beforeLoad is already cached, this seems safe. Given the top-level Suspense finding is already fixed by the new commit, I should resolve that review thread to keep the PR clean, so let me pull up the review comments to find the right node id.
20. github_api_read_only — repos/{owner}/{repo}/pulls/{pull_number}/comments
💭 The thread has already been addressed and maintainers marked it as resolving, so there's nothing further for me to do here. No new findings to report, so I don't need to post anything additional.
Agent Credits: 167 credits
Token usage: 34 uncached input · 656,009 cache-read input · 129,542 cache-write input · 18,094 output
Agent Credits may also include non-token charges from external tools such as web research.