feat(files): folders, multiselect, vfs update#4572
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Revamps the workspace Files UI to support folder navigation, multi-select (shift/range select, keyboard shortcuts), drag-and-drop moves, bulk actions (move/download/delete), and contextual menus/modals; also adds a zip download endpoint for selected files/folders with size/count limits and safe path handling. Separately improves modal accessibility by introducing Reviewed by Cursor Bugbot for commit 90a6050. Configure here. |
Greptile SummaryThis PR introduces folder support, multi-select, and ZIP download to the workspace file system, backed by a new
Confidence Score: 5/5Safe to merge — all mutation paths are guarded by advisory locks inside transactions, conflict errors map cleanly to 409, and authorization checks are consistent across every new route. The new folder CRUD, move, bulk-archive, and download routes all follow established patterns: advisory lock acquired inside the transaction, explicit WorkspaceFileFolderConflictError and 23505 catches returning clean 409 responses, and permission guards at the right level. The VFS fix is correct. Open items are style/performance nits that do not affect correctness. workspace-file-folder-manager.ts — the mapFolderWithPath call in createWorkspaceFileFolder and updateWorkspaceFileFolder is the only thing worth a second look before merge. Important Files Changed
Sequence DiagramsequenceDiagram
participant C as Client
participant R as Route Handler
participant M as FolderManager
participant DB as Database
Note over C,DB: Folder Mutation (create/update/move/archive/restore)
C->>R: POST/PATCH/DELETE request
R->>R: getSession() + permission check
R->>M: createWorkspaceFileFolder / updateWorkspaceFileFolder / etc.
M->>DB: BEGIN TRANSACTION
M->>DB: pg_advisory_xact_lock(workspace_id)
M->>DB: Conflict/existence checks (inside tx)
M->>DB: INSERT/UPDATE/DELETE
M->>DB: COMMIT
M->>DB: SELECT all active folders (build path map)
M-->>R: WorkspaceFileFolderRecord (with path)
R-->>C: "200 JSON { success, folder }"
Note over C,DB: ZIP Download
C->>R: "GET /files/download?fileIds=&folderIds="
R->>R: getSession() + verifyWorkspaceMembership
R->>DB: listWorkspaceFiles(workspaceId)
R->>DB: listWorkspaceFileFolders(workspaceId)
R->>R: collectDescendantFolderIds + filter filesToZip
R->>R: size/count limit checks
R->>DB: fetchWorkspaceFileBuffer (parallel, up to 100)
R->>R: JSZip.generateAsync()
R-->>C: application/zip binary
Reviews (13): Last reviewed commit: "fix(files): apply activeSort to folders,..." | Re-trigger Greptile |
|
@greptile |
|
bugbot run |
|
@greptile |
|
bugbot run |
|
@greptile |
|
bugbot run |
|
bugbot run |
|
@greptile |
|
bugbot run |
|
@greptile |
|
bugbot run |
…c update - splitWorkspaceFilePath: remove the unconditional .replace(/^files\//, '') that clobbered paths for files inside a folder literally named "files" - useUpdateWorkspaceFileFolder: when a name update is in flight, recompute the path field for the renamed folder (replace last segment) and propagate the new prefix to all descendant folders so breadcrumbs stay correct during the optimistic window
…d on orphaned restore - files.tsx: revert the activeDropTargetId ref optimization — the ref doesn't trigger re-renders so the drop-target highlight never updated during drag; activeDropTargetId is back in state and in the rowDragDropConfig deps - restore/route.ts: catch Postgres 23505 unique-constraint violation and return a clean 409 instead of leaking the raw error as 400 - restoreWorkspaceFileFolder: check if the parent folder is still archived before restoring; if it is, restore to root (parentId: null) so the folder is never orphaned under an archived parent
|
@greptile |
|
@cursor review |
…eous comments - FileItem interface with folderPath?: string[] added to search modal utils - MemoizedFileItem component renders folder breadcrumb identically to MemoizedWorkflowItem — truncated path segments on the right with / separators - FilesGroup rewritten as a dedicated memo component (was createIconGroup factory) so it accepts FileItem[] and includes folderPath segments in the search value - searchModalFiles in sidebar splits f.folderPath string into string[] segments - search-modal.tsx typed to FileItem and includes folderPath in filterAndSort - Remove self-explanatory "Phase 1" section label from download route - Remove redundant TSDoc on the unique index in db schema
…ct refinements, guards
…onsolidate emcn icon imports - Remove useCallback from 5 drag-event handlers in DataRow (passed to native <tr> elements, no observer) - Remove stable useCallback fns from 3 useMemo deps arrays in files.tsx (editingId/editValue remain) - Merge all @/components/emcn/icons subpath imports into barrel (files.tsx, action-bar, file-row-context-menu)
|
@greptile |
|
@cursor review |
…ent folder - visibleFolders now respects activeSort column (name/updated/created) and direction so folder ordering stays consistent with file ordering - isInvalidDropTarget now returns true when all dragged items are already direct children of the target folder, preventing a no-op move mutation
|
@greptile |
|
@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 90a6050. Configure here.
Summary
Add folders, multiselect for files, update VFS to work with this.
Type of Change
Testing
Tested manually
Checklist