Skip to content

fix(sidebar): prefetch folders and workspace permissions on cold load - #5426

Merged
waleedlatif1 merged 5 commits into
stagingfrom
sidebar-prefetch-folders-permissions
Jul 5, 2026
Merged

fix(sidebar): prefetch folders and workspace permissions on cold load#5426
waleedlatif1 merged 5 commits into
stagingfrom
sidebar-prefetch-folders-permissions

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Sidebar workflow list and mothership chat list were already server-prefetched, but the folder list wasn't — so a cold sidebar load painted a flat workflow list, then flashed into folder-grouped view once useFolders resolved client-side
  • Added a folder prefetch (folderKeys.list) to prefetchWorkspaceSidebar, backed by a new listFoldersForWorkspace server-side query mirroring the existing GET /api/folders logic
  • Also prefetched workspace permissions (workspaceKeys.permissions) — WorkspaceHeader's permission-gated actions (e.g. "New workflow") were flipping shortly after mount for the same reason. Extracted the shared logic into getWorkspacePermissionsForViewer in lib/workspaces/permissions/utils.ts so the route and the prefetch use one implementation instead of two
  • Deliberately did not prefetch the full workspace list (workspaceKeys.list) — its route does invite-policy evaluation, org plan lookups, and first-run default-workspace creation side effects, not just a DB read. Duplicating that into the prefetch path would be a much larger, separate change

Type of Change

  • Bug fix

Testing

  • bunx tsc --noEmit clean
  • bun run check:client-boundary, bun run check:react-query both pass
  • bunx biome check clean on touched files
  • Existing route test suites for /api/folders and /api/workspaces/[id]/permissions (40 tests) pass unmodified against the refactored handler

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

vercelBot commented Jul 5, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
docsReadyReadyPreview, CommentJul 5, 2026 6:08pm

Request Review

@cursor

cursorBot commented Jul 5, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches workspace permission resolution on both the permissions API and server prefetch; behavior should match prior handlers but authorization regressions would be user-visible.

Overview
Fixes sidebar UI flash on cold load by server-prefetching active folders and workspace permissions alongside workflows and chats, using the same React Query keys and mappers as the client hooks.

listFoldersForWorkspace centralizes the folder list DB query (including archived scope and ISO date wire shape) for GET /api/folders and prefetchWorkspaceSidebar. getWorkspacePermissionsForViewer does the same for the permissions payload (member list + viewer role); GET /api/workspaces/[id]/permissions now delegates to it instead of inlining access checks.

Exported FOLDER_LIST_STALE_TIME and WORKSPACE_PERMISSIONS_STALE_TIME so prefetch and hooks share stale-time constants.

Reviewed by Cursor Bugbot for commit 2a10b88. Configure here.

@greptile-apps

greptile-appsBot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a cold-load flash in the sidebar where workflows appeared in a flat list briefly before jumping into the folder-grouped view, and where workspace-gated actions (like "New workflow") momentarily toggled after mount. It adds server-side prefetches for both the active folder list and workspace permissions, placing them alongside the already-prefetched workflow and chat lists.

  • lib/folders/queries.ts (new): Extracts the listFoldersForWorkspace query — including timestamp normalization to FolderApi wire format — so GET /api/folders and the prefetch share one implementation.
  • lib/workspaces/permissions/utils.ts: Adds getWorkspacePermissionsForViewer consolidating the previously scattered hasWorkspaceAdminAccess + checkWorkspaceAccess + getUserEntityPermissions + getUsersWithPermissions calls into a single function; the permissions route and sidebar prefetch both use it.
  • app/workspace/[workspaceId]/prefetch.ts: Adds two new prefetchQuery calls (folders + workspace permissions) inside the existing Promise.all; FOLDER_LIST_STALE_TIME and mapFolder are exported from their respective hook files to keep key/mapper co-location intact.

Confidence Score: 5/5

Safe to merge — the changes are additive prefetch calls and straightforward extractions of existing inline logic into shared helpers.

The folder and permissions prefetch functions are backed by the same Drizzle queries the existing API routes use; the refactored GET permissions handler preserves the exact same access-control conditions and response shape as the original; and the exported stale-time constants keep the prefetch and client hooks in lock-step. No behavioral regressions were found.

No files require special attention.

Important Files Changed

FilenameOverview
apps/sim/lib/folders/queries.tsNew file extracting listFoldersForWorkspace with proper FolderApi timestamp normalization; cleanly replaces the inline Drizzle query in GET /api/folders.
apps/sim/lib/workspaces/permissions/utils.tsAdds getWorkspacePermissionsForViewer which consolidates 4 separate DB-touching calls into one; logic is equivalent to the old route handler and now shared with the prefetch path.
apps/sim/app/workspace/[workspaceId]/prefetch.tsAdds two new prefetchQuery calls (folderKeys.list + workspaceKeys.permissions) inside the existing Promise.all; query keys and stale times match the client hooks exactly.
apps/sim/app/api/workspaces/[id]/permissions/route.tsGET handler simplified from 4 separate DB calls to a single getWorkspacePermissionsForViewer call; behavior is semantically identical (same 404 conditions, same response shape).
apps/sim/app/api/folders/route.tsInline Drizzle query replaced with listFoldersForWorkspace; filtering and ordering logic is preserved exactly.
apps/sim/hooks/queries/folders.tsExtracts FOLDER_LIST_STALE_TIME as a named export and exports mapFolder; staleTime is now consistently sourced from this constant across useFolders and useFolderMap.
apps/sim/hooks/queries/workspace.tsExtracts WORKSPACE_PERMISSIONS_STALE_TIME (30 s) as a named export so the prefetch and the hook use the same constant.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant RSC as RSC Page (server)
participant PF as prefetchWorkspaceSidebar
participant DB as Database
RSC->>PF: prefetchWorkspaceSidebar(queryClient, workspaceId, userId)
PF->>DB: checkWorkspaceAccess (getWorkspaceWithOwner + getEffectiveWorkspacePermission)
DB-->>PF: access result
alt user cannot access workspace
PF-->>RSC: return (skip silently)
else user has access
par workflows
PF->>DB: listWorkflowsForUser
DB-->>PF: workflow rows → mapWorkflow[]
and chats
PF->>DB: listMothershipChats
DB-->>PF: chat rows → mapChat[]
and folders (NEW)
PF->>DB: listFoldersForWorkspace (active)
DB-->>PF: folder rows → FolderApi[] → mapFolder[]
and permissions (NEW)
PF->>DB: getWorkspacePermissionsForViewer
note over PF,DB: getWorkspaceWithOwner + getEffectiveWorkspacePermission + getUsersWithPermissions
DB-->>PF: WorkspacePermissionsForViewer
end
PF->>PF: queryClient.setQueryData for all 4 caches
PF-->>RSC: return (caches populated)
end
RSC->>RSC: dehydrate(queryClient) → HydrationBoundary
note over RSC: Client receives pre-populated cache — no flash
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant RSC as RSC Page (server)
participant PF as prefetchWorkspaceSidebar
participant DB as Database
RSC->>PF: prefetchWorkspaceSidebar(queryClient, workspaceId, userId)
PF->>DB: checkWorkspaceAccess (getWorkspaceWithOwner + getEffectiveWorkspacePermission)
DB-->>PF: access result
alt user cannot access workspace
PF-->>RSC: return (skip silently)
else user has access
par workflows
PF->>DB: listWorkflowsForUser
DB-->>PF: workflow rows → mapWorkflow[]
and chats
PF->>DB: listMothershipChats
DB-->>PF: chat rows → mapChat[]
and folders (NEW)
PF->>DB: listFoldersForWorkspace (active)
DB-->>PF: folder rows → FolderApi[] → mapFolder[]
and permissions (NEW)
PF->>DB: getWorkspacePermissionsForViewer
note over PF,DB: getWorkspaceWithOwner + getEffectiveWorkspacePermission + getUsersWithPermissions
DB-->>PF: WorkspacePermissionsForViewer
end
PF->>PF: queryClient.setQueryData for all 4 caches
PF-->>RSC: return (caches populated)
end
RSC->>RSC: dehydrate(queryClient) → HydrationBoundary
note over RSC: Client receives pre-populated cache — no flash
Loading

Reviews (2): Last reviewed commit: "fix(folders): reference FOLDER_LIST_STAL..." | Re-trigger Greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot 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 2a10b88. Configure here.

waleedlatif1 added a commit that referenced this pull request Jul 5, 2026
…nstant (#5427)
* refactor(react-query): hoist every staleTime into a named exported constant
Adds a repo-wide convention (documented in .claude/rules/sim-queries.md,
CLAUDE.md, and the react-query-best-practices skill/command) that every
staleTime value must come from a named exported constant instead of an
inline numeric literal. This prevents a server-side prefetch and its
client hook from independently duplicating the same duration and
drifting out of sync, which Greptile caught on PR #5426.
Applies the convention across all of hooks/queries/** and their
prefetch.ts consumers.
* refactor(react-query): use FOLDER_LIST_STALE_TIME in folders.ts hooks
useFolders and useFolderMap still used inline 60 * 1000 despite folders.ts
already exporting FOLDER_LIST_STALE_TIME — the same prefetch-drift gap this
refactor closes everywhere else. Same value, no behavior change.
…olders-permissions
# Conflicts:
#	apps/sim/hooks/queries/workspace.ts
@waleedlatif1
waleedlatif1 merged commit 4fa1e04 into stagingJul 5, 2026
10 of 11 checks passed
@waleedlatif1
waleedlatif1 deleted the sidebar-prefetch-folders-permissions branch July 5, 2026 18:05
Sign up for freeto 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

@waleedlatif1