fix(prefetch): drop the internal HTTP hop from every resource list prefetch - #6404
fix(prefetch): drop the internal HTTP hop from every resource list prefetch#6404waleedlatif1 wants to merge 4 commits into
Conversation
…efetch On a hard refresh of /files the folders painted first and the files a beat later. Both are prefetched and hydrated together, so the files entry was never reaching the client: each prefetch reached its own API route over an internal HTTP request, prefetchQuery swallows a rejection, and shouldDehydrateQuery drops an errored entry. One failed request silently shipped a page with that list missing while its cheaper siblings hydrated fine — and nothing logged it. The files list is the heaviest of the pair, so it lost that race first. Every resource prefetch — files, home, tables, knowledge, and the shared pinned/members chrome — now calls the data layer directly, matching prefetchWorkspaceSidebar. Per page load that turns 5 internal HTTP round-trips, each re-running session and membership authz, into 1 authz read plus N direct reads. Extracts listWorkspaceFilesWithShares, listPinnedItemsForViewer and listKnowledgeBasesForViewer so each route and its prefetch fill the same query key from one function and cannot drift. listKnowledgeBasesForViewer serializes dates because the contract types them z.string(): reading the data layer directly would otherwise cache Date objects that violate the declared type and flip to strings on the first refetch. The routes each authorized their own read, so each prefetch now verifies membership once and caches nothing without access, leaving the client fetch to get the real 403. Also prefetches the viewer's pending invitations with the sidebar so the switcher's "View invitations" entry is present the frame the menu opens, and moves the invitation key factory into hooks/queries/utils so a server prefetch can hydrate it without importing the emcn toast surface.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Files, home, knowledge, pinned items, workspace files, and invitations now hydrate through shared Sidebar also prefetches pending invitations (with bounded join-preview concurrency) so “View invitations” appears when the switcher opens; invitation types/keys move to Tables still use Reviewed by Cursor Bugbot for commit 7e37f2f. Configure here. |
Greptile SummaryThe PR replaces resource-list prefetches that called internal API routes with authorized direct data-layer reads while retaining the table route boundary.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/files/prefetch.ts | Replaces files and folder HTTP prefetches with direct reads after the same effective workspace-permission check used by the files route. |
| apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts | Directly prefetches knowledge bases and folders after workspace authorization while preserving client query keys and mapped data shapes. |
| apps/sim/app/workspace/[workspaceId]/lib/prefetch-resource-list-chrome.ts | Uses shared data-layer functions for viewer-scoped pins and workspace member profiles under the caller's authorization guard. |
| apps/sim/app/workspace/[workspaceId]/prefetch.ts | Extends sidebar hydration to pending viewer invitations using the same key and array shape as the client query. |
| apps/sim/lib/invitations/pending.ts | Centralizes invitation listing, preview generation, failure isolation, and wire-date serialization for route and prefetch reuse. |
| apps/sim/lib/knowledge/queries.ts | Provides a shared viewer-aware knowledge-base query and serializes dates to match the declared API contract. |
| apps/sim/lib/pinned-items/queries.ts | Centralizes viewer/workspace filtering, active-resource filtering, enum narrowing, and date serialization for pinned-item lists. |
| apps/sim/lib/workspace-files/queries.ts | Centralizes workspace file/share assembly and validates the result through the API response schema. |
Sequence Diagram
sequenceDiagram
participant Page as Workspace page/layout
participant Auth as Session and workspace authz
participant Query as React Query cache
participant Data as Shared data layer
participant Browser as Browser hooks
Page->>Auth: Resolve viewer and workspace access
Auth-->>Page: Effective permission
alt Viewer is authorized
Page->>Query: Prefetch resource query
Query->>Data: Direct list read
Data-->>Query: Contract-aligned data
Page-->>Browser: Dehydrated query state
Browser->>Query: Read hydrated query key
else Viewer is unauthorized
Page-->>Browser: No resource data hydrated
Browser->>Browser: Client request receives route authorization result
end
Reviews (4): Last reviewed commit: "perf(invitations): bound the join-previe..." | Re-trigger Greptile
…eturns GET /api/table does not return listTables rows: it drops metadata, runs every column through normalizeColumn, serializes the three dates, and defaults the job fields. The prefetch called listTables directly, so a hydrated entry held un-normalized columns plus a field the client never sees, and swapped them out on the first refetch. Extracts listTablesForWorkspace so the route and the prefetch produce one shape, matching what files, knowledge and pinned items already do here.
|
@cursor review |
There was a problem hiding this comment.
✅ 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 1862a84. Configure here.
…re shape check:tool-registry-boundary rejected the tables page: lib/table/service transitively imports the executor, so reading listTables from a page.tsx pulls the tool registry (~4,700 modules) into that route's graph. Breaking the chain means relocating stripGroupDeps, latestJobsForTables and pendingDeleteMask out of modules that import the executor — a lib/table refactor that wants its own review rather than riding along here. The tables list therefore keeps going through its route, which reshapes every row anyway, while its folders and list chrome read the data layer. prefetchInternalJson comes back for that one caller and now logs before it throws: prefetchQuery swallows the rejection and shouldDehydrateQuery drops the errored entry, which is exactly how the original bug stayed invisible. Also parses listWorkspaceFilesWithShares through the route contract's response schema. listWorkspaceFiles returns contentUpdatedAt, which the schema does not declare and does not pass through, so the prefetch was caching a field a client fetch never has and that vanished on the next refetch.
|
@cursor review |
There was a problem hiding this comment.
✅ 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 eac836b. Configure here.
…ical path listPendingInvitationsForViewer computed join previews in a serial loop, which was affordable when the switcher dropdown was the only caller. The sidebar prefetch now calls it on every workspace page render, and each preview issues up to three sequential queries — so a viewer with pending invitations paid roughly three round-trips per invitation before the first byte of every route under the workspace layout. Bounds the fan-out with mapWithConcurrency instead. The preview already degrades to null on failure, which is what makes it safe under a mapper that fails all-or-nothing, and a bound keeps the pooled-connection ceiling the serial loop was protecting.
|
@cursor review |
|
Closing in favour of a minimal fix. This grew into a 28-file refactor of every resource prefetch, and it wasn't a strict improvement — the tables conversion had to be reverted for the tool-registry boundary, and prefetching invitations put join-preview queries on the render critical path. Reopening as two targeted fixes: the Files prefetch (the reported bug) and the invitation preview fan-out. |
There was a problem hiding this comment.
✅ 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 7e37f2f. Configure here.
Summary
/filesthe folders painted first and the files a beat later. Both are prefetched and hydrated together, so the files entry was never reaching the client: each prefetch reached its own API route over an internal HTTP request,prefetchQueryswallows a rejection, andshouldDehydrateQuerydrops an errored entry. One failed request silently shipped a page with that list missing while its cheaper siblings hydrated fine — and nothing logged it. The files list is the heaviest of the pair (it joins share rows on top of file rows), so it lost that race firstfiles,home,tables,knowledge, and the shared pinned/members chrome now call the data layer directly, matchingprefetchWorkspaceSidebar, whose TSDoc already documented this as the intended pattern.prefetch-internal-fetch.tsis deleted (zero callers left)listWorkspaceFilesWithShares,listPinnedItemsForViewerandlistKnowledgeBasesForViewerso each route and its prefetch fill the same query key from one function and cannot drift.listPinnedItemsForViewerlifts a query out of a route handler's inline drizzle, soGET /api/pinned-itemschanges toolistKnowledgeBasesForViewerserializes dates:knowledgeBaseDataSchematypes themz.string(), so reading the data layer directly would otherwise cacheDateobjects that violate the declared type and flip to strings on the first refetch. Tables deliberately needs no such mapper —TableDefinitiondeclaresDate | string, so both representations satisfy ithooks/queries/utils/so a server prefetch can hydrate it without pulling in the emcn toast surface. Renamed offmy/mineonto the repo's…ForViewerconventionType of Change
Testing
Tested manually.
resourceTypetest, and a regression test that a failing files read no longer takes the sibling folder list down with it — the exact shape of the bugfiles/prefetch.ts, watched the authorization test go red, restored itbunx turbo run type-check·bun run lint·check:client-boundary·check:react-query·check:api-validation— all passlib/table/*.test.tsfail to collect in a worktree on a[postcss] tailwindcsserror frompackages/emcn/**/*.module.css. Confirmed identical on cleanorigin/stagingwith these changes stashedChecklist