Skip to content

feat(platform): add loading states for every tenant route + fix the window filter - #155

Merged
clickmatos merged 1 commit into
mainfrom
feat/route-loading-states
Aug 13, 2026
Merged

feat(platform): add loading states for every tenant route + fix the window filter#155
clickmatos merged 1 commit into
mainfrom
feat/route-loading-states

Conversation

@clickmatos

Copy link
Copy Markdown
Contributor

Problem

Every page under [tenant]/ is an async Server Component doing direct Supabase/session fetches, with zero Suspense boundary and zero loading.tsx anywhere in the app. Clicking a nav link, a repo row, or the window/period filter just sat there frozen until the new RSC payload streamed back — no visual feedback, and nothing stopping a second click before the first one resolves.

What

  • loading.tsx on every data-fetching route segment: dashboard, repos, repos/[repoName], compare, ai-exposure, audit-log, team, settings, settings/integrations, settings/integrations/[provider], connect. This is Next's built-in mechanism — the nearest loading.tsx renders automatically as a Suspense fallback while a route segment's data is resolving, for both <Link> navigation and router.push/replace.
  • components/ui/skeleton.tsx: a shared shadcn-style primitive each loading.tsx composes into a rough match of its real layout (header + card grid / table rows), not a generic spinner.
  • WindowSelector fix: this is the actual "filtro" in the bug report — it calls router.replace on change (reruns the whole server page for every dashboard/compare/repo-detail view that reads ?window=), and had zero pending feedback. Wrapped the navigation in useTransition: the <Select> disables and shows a spinner immediately, which also blocks a second click from firing another replace() before the first resolves.

What I deliberately left alone

  • WindowSelector uses useTransition instead of relying on loading.tsx — wrapping a navigation in a transition keeps the current page visible with isPending=true rather than flashing to the route's skeleton. Right feel for a filter tweak; a full skeleton flash would feel worse than nothing for a same-page param change.
  • compare-view's search input and column-sort are pure client-side useState/useMemo over already-fetched data — no network round-trip, so a loading indicator there would be pure noise.

Verification

  • npx tsc --noEmit: clean.
  • npm run test: 253/253 passing.
  • npm run lint: 0 errors, same 72 pre-existing warnings.
  • Started the dev server with placeholder env vars — boots without error, public routes serve, protected routes still correctly redirect to /auth/signin (no regression).

Not verified: the actual skeleton appearance and the WindowSelector spinner in a live authenticated session — this sandbox has no real Supabase project or user session configured, so every [tenant]/* page redirects before reaching the code these loading.tsx files guard. Worth a manual pass in a real environment before/after merge.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

…indow filter

Every page under [tenant]/ is an async Server Component doing direct
Supabase/session fetches with zero Suspense boundary and zero
loading.tsx — clicking a nav link, a repo row, or a settings tab just
sat there frozen until the new RSC payload streamed back, with nothing
to signal the click registered (and nothing stopping a second click).

- Added loading.tsx to every route segment that fetches data on the
  server: dashboard, repos, repos/[repoName], compare, ai-exposure,
  audit-log, team, settings, settings/integrations,
  settings/integrations/[provider], connect. Next's built-in mechanism
  — the nearest loading.tsx renders automatically as a Suspense
  fallback while a route segment's data is still resolving, on both
  <Link> navigation and router.push/replace.
- Added a shared shadcn-style Skeleton primitive
  (components/ui/skeleton.tsx) each loading.tsx composes into a rough
  match of its page's real layout (header + card grid / table rows),
  not just a generic spinner.
- WindowSelector (the org/window filter used on dashboard, compare, and
  repo-detail) had literally no pending feedback: it calls
  router.replace on change, which reruns the whole server page, and
  the <Select> just sat there unchanged until the round-trip finished.
  Wrapped the navigation in useTransition — the select disables and
  shows a spinner immediately, which also blocks a second click from
  firing another replace() before the first resolves. Deliberately
  NOT loading.tsx-driven here: wrapping in a transition keeps the
  current page visible with isPending=true instead of flashing to the
  route's skeleton, which is the right feel for a filter tweak vs. a
  full navigation.
- Left compare-view's search/sort alone — both are pure client-side
  useState/useMemo over already-fetched data, no network round-trip,
  so a loading indicator there would be pure noise.

Verification: npx tsc --noEmit clean, npm run test 253/253, npm run
lint 0 errors (same 72 pre-existing warnings). Started the dev server
with placeholder env vars and confirmed it boots without error and
protected routes still redirect correctly (no auth/build regression).
Could not visually verify the skeletons rendering in an authenticated
browser session — this sandbox has no real Supabase project or user
session configured, so every [tenant]/* page redirects to /auth/signin
before reaching the data-fetching code the loading.tsx files guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clickbus-iris Ready Ready Preview Aug 13, 2026 7:20pm

Request Review

@clickmatos
clickmatos merged commit 91e0244 into main Aug 13, 2026
5 checks passed
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