Skip to content

improvement(settings): make workspace settings navigation feel instant - #6964

Merged
waleedlatif1 merged 6 commits into
stagingfrom
perf/settings-load
Aug 22, 2026
Merged

improvement(settings): make workspace settings navigation feel instant#6964
waleedlatif1 merged 6 commits into
stagingfrom
perf/settings-load

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Switching workspace settings tabs ran four sequential round-trips with no visual feedback — a cold RSC request, the panel render, the section's lazy chunk, then its queries. The heading was pushed up from the section body via a layout effect, so the most static thing on the page arrived last and visibly blanked between sections.

  • Resolve the heading in the route layout from the section's navigation entry, so it renders with the shell instead of after the body's chunk. One shared resolver normalizes a [section] segment for the layout, the access gate and generateMetadata.
  • Decide segment validity in the layout too, above the loading boundary. Inside a Suspense boundary notFound()/redirect() can no longer set the response status, so a legacy or unknown settings URL loaded directly would answer 200 and redirect in a second round trip. Deciding it above the boundary keeps the 404 and the 307 — and it is where segment-level routing belonged anyway.
  • Add loading.tsx. Without it the App Router holds the outgoing section on screen until the gate resolves, so a click reads as a dead click. It is also what makes the prefetch worth anything: with no loading boundary in the subtree the scheduler skips the segment request entirely.
  • Prefetch the route payload on sidebar hover/focus. The rows are buttons so leaving a dirty section runs the unsaved-changes guard, which costs them Next's automatic <Link> prefetch — the slowest hop was never warmed. Mirrors the existing credits-chip pattern.
  • Scope the general-settings server prefetch to the three sections that read it (general, billing, admin) instead of blocking all 28.
  • Overlap two pairs of independent awaits in the access gate. Every await there sits in front of the section body.
  • Warm workspace credentials under the type the secrets panel queries. The existing warm wrote the unfiltered key, so the panel still fetched cold.

Scope

Workspace plane only. Earlier revisions extended this to the account, organization and self-host planes; review found the loading boundary softened 404s there, and the shell's meta painted a denied section's heading during SSR because the opt-out runs in a layout effect. Feeding the header server-side on those planes needs per-section access at layout level — a routing change beyond this PR — so they are left untouched rather than half-migrated.

Also considered and dropped: experimental.staleTimes (its static value silently downgraded Next 16's own default, and dynamic is an app-wide router-cache change this PR does not need — the segment cache already floors prefetch entries at 30s); a body skeleton (no precedent at this layer — ResourceChromeFallback renders real chrome over rows={[]}, credit-usage renders its title over an empty body); and warming all 28 section chunks from the sidebar (measured ~113–163 extra modules across six of the hottest routes on the boundary audit — the six that predate this PR are kept).

Type of Change

  • Improvement (performance)

Testing

bun run type-check, bun run lint, bun run check:audits (incl. the module-graph boundary check, which caught the chunk-warming regression) and the block-registry check pass. 778 tests pass. New coverage for resolveSettingsSection's alias table — untested on either side of the move — and for the general-settings prefetch gate; all new assertions verified against deliberate mutants, including one earlier test that passed against a broken field-merge implementation.

Not verified in a running browser — the frame sequence is reasoned from the Next 16.3.1 scheduler and router sources, not observed.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 22, 2026 3:59am

Request Review

@cursor

cursor Bot commented Aug 22, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches settings routing (404/redirect placement) and the workspace access gate’s entitlement lookups. Viewer permission checks stay on the page; inbox/sandboxes are treated as membership-only for that gate.

Overview
Makes workspace settings tab switches feel instant: the heading paints with the shell, navigation commits immediately, and hover warms the slow server gate.

The [section] layout now resolves catalog meta (and unknown/legacy URLs) above a new loading.tsx, so the title is not waiting on a lazy body chunk and notFound/redirect still set 404/307. resolveSettingsSection is the shared alias+catalog lookup for layout, access gate, and metadata.

The sidebar prefetches every section’s route payload on hover (buttons cannot use <Link> auto-prefetch). Server prefetch of general settings is limited to general/billing/admin. Independent access-gate awaits overlap, and unused billing lookups are skipped. Secrets hover now warms the env_workspace credential list the panel actually reads.

Reviewed by Cursor Bugbot for commit 2067689. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR improves workspace-settings navigation by resolving route metadata and segment validity in the layout, adding an immediate loading boundary, prefetching selected route payloads and resources, and reducing unnecessary access-gate queries.

  • Moves static section headings and legacy-route handling into the route layout.
  • Adds route, chunk, and credentials prefetching for settings navigation.
  • Narrows general-settings hydration and overlaps independent access checks.
  • Centralizes section alias resolution and header metadata.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/settings/[section]/layout.tsx Moves legacy redirects, segment validation, and static section metadata above the loading boundary.
apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx Retains viewer access checks while overlapping independent lookups and narrowing server-side query prefetching.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx Adds route-payload prefetching plus targeted chunk and credentials warming on sidebar interaction.
apps/sim/components/settings/settings-header.tsx Lets route metadata provide the header until the mounted section body registers its live configuration.
apps/sim/hooks/queries/credentials.ts Extends credentials prefetching to use the same optional type-specific query key and request as consumers.

Sequence Diagram

sequenceDiagram
  participant U as User
  participant S as Settings sidebar
  participant R as Next.js router
  participant L as Section layout
  participant P as Section page
  participant Q as Query cache
  U->>S: Hover or focus section
  S->>R: Prefetch route payload
  S->>Q: Warm selected section query
  U->>S: Select section
  S->>R: Navigate after dirty-state guard
  R->>L: Resolve segment and static heading
  L->>P: Render section below loading boundary
  P->>P: Run viewer access checks
  P->>Q: Prefetch general settings when required
  P-->>U: Render authorized section body
Loading

Reviews (7): Last reviewed commit: "improvement(settings): resolve only the ..." | Re-trigger Greptile

Comment thread apps/sim/next.config.ts Outdated
Comment thread apps/sim/components/settings/lazy-section.tsx Outdated
Comment thread apps/sim/components/settings/organization-settings-renderer.tsx Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/components/settings/standalone-settings-shell.tsx Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/account/settings/[section]/loading.tsx Outdated
Comment thread apps/sim/components/settings/settings-unavailable.tsx Outdated
Every settings tab switch ran four sequential round-trips with no visual
feedback: a cold RSC request, then the panel render, then the section's
lazy chunk, then its queries. The heading was pushed up from the section
body, so the most static thing on the page arrived last and visibly
blanked between sections.

- resolve the section heading in the route layout from its navigation
  entry, so it paints with the shell instead of after the body's chunk
- add loading.tsx to all four settings planes, so a click commits the
  navigation immediately instead of holding the outgoing section
- give every code-split section a shared skeleton fallback, reused by the
  route boundary and the in-page Suspense boundary
- prefetch the route payload on sidebar hover/focus; the rows are buttons
  for the unsaved-changes guard, so they never got Next's <Link> prefetch
- scope the general-settings server prefetch to the three sections that
  read it, instead of blocking all 28 on it
- set staleTimes so returning to a tab reuses the client router cache
  rather than re-running the access gate
- warm workspace credentials under the type the secrets panel queries;
  the previous warm wrote a different cache entry and never landed
…e change

Follow-up review of the diff against the rest of the platform.

- The 4-row body skeleton had no precedent at this layer. Every route-level
  fallback in the app renders real chrome over empty content instead:
  ResourceChromeFallback renders its header and column headers with rows={[]},
  and the credit-usage fallback renders its real title and description over
  nothing. Skeleton is only ever used for in-component sub-regions. The
  loading boundaries now render an empty body, so the heading is what signals
  arrival and nothing shifts when the real body lands. This also removes the
  shared dynamic() options module, leaving all four section renderers
  untouched by this PR.
- Remove the experimental.staleTimes block. static: 180 silently downgraded
  Next 16's own default of 300, and dynamic: 30 is an app-wide change to
  client router cache reuse that this PR does not need: the segment cache
  already floors prefetch entries at 30s, so hover prefetch pays off without
  it. It deserves its own PR and its own measurement.
- Restore the six section chunk warms that existed before this PR. Dropping
  them alongside the 28-section map was an unintended regression; they were
  already in the module graph, so warming them costs nothing.
Six independent review passes over the diff. Three real defects, all
introduced by the header-meta fallback or the loading boundary.

- A denied organization section rendered the section's catalog heading,
  description and Docs link above a "you do not have access" body, because
  SettingsUnavailable renders its own centred heading and registers nothing.
  It now claims an empty header, which is how a body opts out of the meta
  fallback. Releasing the header is an explicit null rather than an
  EMPTY_CONFIG sentinel, so "no body owns this" is stated instead of implied.
- The account credit-usage route resolves to its parent billing section, so
  the shell painted "Billing" in the server frame before hydration swapped in
  "Credit usage". The shell now only supplies meta for a section's own route,
  not for detail routes beneath it.
- Adding loading.tsx put the page inside a Suspense boundary, where
  notFound() and redirect() can no longer set the response status: a legacy
  or unknown settings URL loaded directly answered 200 and redirected in a
  second round trip instead of 307/404. Segment-level routing moved into the
  layout, above the boundary, which is also where it belonged.

Also from review:

- Parallelize two pairs of independent awaits in the access gate. Every await
  there sits in front of the section body, so this shortens the exact wait the
  PR is about.
- Note in settings-header that the layout effect is load-bearing: a passive
  effect would let the previous section's title show for a frame.
- Correct the loading.tsx docs, which claimed the empty body matched every
  other route-level fallback. It is the only null one; the accurate statement
  is that the shell above it already renders the chrome.
- Tests: cover resolveSettingsSection's alias table (previously untested on
  either side of the move) and the general-settings prefetch gate. Strengthen
  the wholesale-substitution test, which passed against a field-merge
  implementation because SettingsPanel always emits a description key. All
  four verified against mutants.
Two review findings, both from extending the mechanism to the account,
organization and self-host planes without extending the fixes with it.

- The loading boundary softened 404s on those three planes. Segment
  validation was moved above the boundary for the workspace plane only, so a
  direct load of an unknown or legacy segment on the others answered 200 and
  soft-404ed after hydration.
- SettingsUnavailable's opt-out registers in a layout effect, which does not
  run during SSR. A direct load of a denied organization section still painted
  the denied section's catalog title, description and Docs link until
  hydration — the exact caption the opt-out was added to prevent.

Feeding the header server-side on those planes needs per-section access at
layout level, which is a routing change well beyond this PR. So the three
standalone loading boundaries, the standalone shell's meta, the
SettingsUnavailable opt-out and the standalone sidebar's route prefetch are
all reverted: without a loading boundary in the subtree the scheduler skips
the segment request, so that prefetch bought nothing on its own.

What ships is the workspace settings plane, where the same mechanism is
correct end to end: segment validation and the heading both resolve in the
layout, above the boundary, and a denied section redirects rather than
rendering an unavailable body under a header.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1 waleedlatif1 changed the title improvement(settings): make settings section navigation feel instant improvement(settings): make workspace settings navigation feel instant Aug 22, 2026

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d87db95. Configure here.

The access gate asked `isCredentialGroupsAvailable` for an answer the host
context had already derived from the same owner billing one await earlier, so
every workspace-section navigation paid a second feature-flag lookup to learn
what was already on `hostContext.features`.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fe1252a. Configure here.

The access gate fanned out four entitlement lookups for every workspace
section and then built the whole navigation list to ask whether one section
was in it.

- `inbox` and `sandboxes` feed only `locked`, which marks a section as needing
  an upgrade rather than hiding it. The gate reads membership alone, so those
  two billing round-trips could never change the outcome for any section.
  Removing them is behaviour-identical, not a narrowing.
- `forks` is read only by the `forks` entry, so every other section was
  resolving a lineage check it could not act on.

`permissionConfig` is deliberately left alone: its keys hide sections, so
skipping the lookup for a section that turns out to be config-gated would
reveal it. That fails open, where the other two fail closed.

Opening a section such as secrets or byok now awaits nothing beyond the
already-conditional permission-group read.

Also corrects the chunk-warmer rationale. Measurement showed the cost is the
boundary audit counting `import()` as a graph edge, not parsed JS — each
section is already `dynamic()`-imported by the panel — and that code-splitting
the sidebar moves exactly one module, so it cannot unlock warming the rest.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2067689. Configure here.

@waleedlatif1
waleedlatif1 merged commit 3a04426 into staging Aug 22, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the perf/settings-load branch August 22, 2026 04:12
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2067689. Configure here.

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