fix(files): read the Files prefetch from the data layer and bound invitation previews - #6415
Conversation
…itation previews Two unrelated load-time fixes. 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 not reaching the client. Each read went to its own route over an internal HTTP request; prefetchQuery swallows a rejection and shouldDehydrateQuery drops the errored entry, so a failure there silently shipped a page with that list missing, and the files read is the heavier of the two. Those two reads now call the data layer. Note the staging logs show no errors from that route, so this removes the failure mode without proving it was the one firing — the request it drops from the render path, and the shape fix below, stand on their own. listWorkspaceFilesWithShares is shared by the route and the prefetch and shapes its result through the route contract's response schema. listWorkspaceFiles returns contentUpdatedAt, which the schema neither declares nor passes through, so the prefetch was caching a field a client fetch never has and that vanished on the next refetch. The reads carry no authorization of their own now that they bypass the route, so the prefetch proves the viewer first. It reuses the layout's cached host-context lookup rather than re-deriving the permission, so the gate costs no extra queries. Separately, GET /api/invitations computed join previews in a serial loop and each preview issues up to three queries of its own, putting all of them on the critical path of the workspace switcher opening. Bounded with mapWithConcurrency; the mapper was already total, which is what that helper requires.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Files browser: Server prefetch for files and folders no longer hits internal Invitations: Tests cover preview ordering/degradation/concurrency, contract stripping, prefetch auth gate, and updated prefetch suite. Reviewed by Cursor Bugbot for commit c6b1ba3. Configure here. |
Greptile SummaryThe PR moves Files-page server prefetching from internal HTTP routes to authorized data-layer reads, normalizes file results through the route contract, and bounds invitation-preview concurrency.
Confidence Score: 5/5The PR appears safe to merge, with no concrete correctness or security regressions identified. The direct prefetch uses the same effective workspace-access semantics as the previous routes, file results are normalized through the shared response contract, and the bounded invitation mapper preserves ordering and per-row failure handling.
|
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/files/prefetch.ts | Replaces internal route fetches with workspace-authorized data-layer prefetches while retaining the existing query keys and stale times. |
| apps/sim/lib/workspace-files/queries.ts | Centralizes file/share joining and parses results through the files response contract to keep route and hydrated-cache shapes aligned. |
| apps/sim/app/api/workspaces/[id]/files/route.ts | Reuses the shared file/share query after the existing route-level membership check. |
| apps/sim/app/api/invitations/route.ts | Replaces serial preview generation with an order-preserving concurrency limit while retaining per-invitation error degradation. |
| apps/sim/app/workspace/[workspaceId]/files/page.tsx | Supplies the authenticated viewer identity required for the direct data-layer prefetch authorization gate. |
Sequence Diagram
sequenceDiagram
participant Browser
participant Page as Files Server Page
participant Auth as Session/Workspace Access
participant Data as Files/Folders Data Layer
participant Cache as React Query Cache
Browser->>Page: Hard refresh /workspace/:id/files
Page->>Auth: Resolve session and viewer access
alt Viewer has workspace access
Auth-->>Page: Authorized host context
par Prefetch files
Page->>Data: listWorkspaceFilesWithShares(active)
Data-->>Page: Contract-shaped files and shares
and Prefetch folders
Page->>Data: listWorkspaceFileFolders(active)
Data-->>Page: Active folders
end
Page->>Cache: Prefetch and dehydrate query entries
Cache-->>Browser: Hydrated initial lists
else Viewer lacks access
Auth-->>Page: No host context
Page-->>Browser: Render without protected cached data
end
Reviews (1): Last reviewed commit: "fix(files): read the Files prefetch from..." | Re-trigger Greptile
Summary
Two unrelated load-time fixes, 8 files.
Files page (
/workspace/[id]/files). On a hard refresh the folders painted first and the files a beat later. Both are prefetched and hydrated together, so the files entry was not reaching the client. Each read went to its own route over an internal HTTP request —prefetchQueryswallows a rejection andshouldDehydrateQuerydrops the errored entry, so a failure there silently ships a page with that list missing, and the files read is the heavier of the two (it joins share rows on top of the file rows). Those two reads now call the data layer, which also removes a server→server request and its duplicate auth from the render path.Root cause is not confirmed. Staging logs show zero errors from
WorkspaceFilesAPIover three days, so this removes the failure mode without proving it was the one firing. What the logs do show on the reported workspace is duplicate requests 13–42ms apart, consistent with the hydrated entry not being used — but those logs don't distinguish server-originated from browser-originated requests. The request dropped from the render path and the shape fix below stand on their own.Shape fix (real bug).
listWorkspaceFilesWithSharesis shared by the route and the prefetch and shapes its result through the route contract's response schema — the techniqueprefetchWorkspaceSidebaralready documents.listWorkspaceFilesreturnscontentUpdatedAt, whichworkspaceFileRecordSchemaneither declares nor passes through, so the prefetch was caching a field a client fetch never has and that vanished on the next refetch.Authorization. The reads carry none of their own now that they bypass the route, so the prefetch proves the viewer first. It reuses the layout's
cache()dgetWorkspaceHostContextForViewerrather than re-deriving the permission, so the gate costs no additional queries. No access → nothing cached → the client fetch reaches the route for the real 403.Invitations.
GET /api/invitationscomputed join previews in a serial loop, and each preview issues up to three queries of its own — all on the critical path of the workspace switcher opening. Bounded withmapWithConcurrency. Same fetch, same trigger, same payload; the mapper was already total, which is what that helper requires.Deliberately not included: no conversion of the home/tables/knowledge/chrome prefetches, no
lib/tablerefactor, no invitation prefetching. Those were explored and dropped — the tables conversion is blocked bycheck:tool-registry-boundary(lib/table/servicetransitively imports the executor), and prefetching invitations would have put these queries back on every workspace page render.Type of Change
Testing
Tested manually.
prefetchQuery's swallow behaviour rather than anything in this diff, and passed againstorigin/stagingunchanged.prefetch.test.tsrather than letting it rot, including its graceful-failure case, which was asserting against a fetcher the Files prefetch no longer uses and would have passed vacuously.bun run check:audits,type-check,lintall pass. Pre-existing and unrelated:check:realtime-prune,check:native-typecheckandcheck:desktop-bridgefail in a worktree with no localnode_modules— verified identical on cleanorigin/staging.Not browser-verified. Worth one hard refresh of
/fileswith the network tab open before merge: if/api/workspaces/<id>/filesstill fires as an XHR after load, the hydration issue is something other than what this fixes.Checklist