Skip to content

feat(files): folders, multiselect, vfs update#4572

Open
icecrasher321 wants to merge 49 commits into
stagingfrom
feat/workspace-files-folders
Open

feat(files): folders, multiselect, vfs update#4572
icecrasher321 wants to merge 49 commits into
stagingfrom
feat/workspace-files-folders

Conversation

@icecrasher321
Copy link
Copy Markdown
Collaborator

@icecrasher321 icecrasher321 commented May 12, 2026

Summary

Add folders, multiselect for files, update VFS to work with this.

Type of Change

  • New feature

Testing

Tested manually

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
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 14, 2026 4:49am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 12, 2026

PR Summary

High Risk
Adds new backend APIs that move/archive/download workspace files and folders (including zip generation) and changes upload/routing to persist folderId, which are data-sensitive operations where bugs could cause data loss or permission issues.

Overview
Adds first-class workspace file folders across the stack: new folder CRUD/restore endpoints plus item move and bulk-archive APIs with audit + PostHog events, and updates uploads/presigned/register flows to accept/persist folderId.

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 ModalDescription usage across many modals, and tweaks the shared Resource table to support leading header actions, proportional column widths, and row drag/drop hooks.

Reviewed by Cursor Bugbot for commit 90a6050. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR introduces folder support, multi-select, and ZIP download to the workspace file system, backed by a new workspace_file_folders table with soft-delete semantics and advisory locking for safe concurrent mutations.

  • Adds a workspace_file_folders table with a self-referential parent_id FK, soft-delete via deletedAt, and a partial unique index on (workspaceId, coalesce(parentId, ''), name) WHERE deletedAt IS NULL to enforce conflict-free naming per-location.
  • Introduces a suite of new API routes (folder CRUD, move, bulk-archive, download-as-ZIP) with pg_advisory_xact_lock held inside every mutation transaction, proper 409 mapping for both typed conflict errors and raw 23505 constraint violations, and consistent permission checks.
  • Updates the VFS materializeFiles path to return name and folderPath as separate fields so buildWorkspaceMd correctly groups files under folder headings in WORKSPACE.md."

Confidence Score: 5/5

Safe 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

Filename Overview
apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts Core folder mutation manager — advisory locks, soft-delete, path resolution, move/archive/restore. mapFolderWithPath still chains N per-ancestor queries for create/update (unlike the fixed getWorkspaceFileFolder).
apps/sim/app/api/workspaces/[id]/files/download/route.ts New ZIP download endpoint — path sanitization, deduplication, size/count limits, proper membership check. Clean implementation.
apps/sim/app/api/workspaces/[id]/files/folders/[folderId]/route.ts Folder PATCH and DELETE — correctly maps WorkspaceFileFolderConflictError and 23505 to 409, acquires advisory lock in transaction.
apps/sim/app/api/workspaces/[id]/files/folders/[folderId]/restore/route.ts Folder restore POST — checks 23505 → 409, proper permission guard. Restore logic correctly nulls out parentId when parent is still archived.
apps/sim/app/api/workspaces/[id]/files/bulk-archive/route.ts Bulk archive POST — correct write-permission guard, proper audit trail for both files and folders.
packages/db/schema.ts Adds workspace_file_folders table with self-referential FK (ON DELETE SET NULL), partial unique index, and folderId FK on workspace_files. Migration drops old name-only unique index and creates folder-aware replacement.
apps/sim/lib/api/contracts/workspace-file-folders.ts Route contracts for all new folder operations. downloadWorkspaceFileItemsQuerySchema lacks a minimum-1-item refine (bulkArchive has one), but handled gracefully in the route.
apps/sim/app/api/workspaces/[id]/files/move/route.ts Move route — correct conflict error mapping, write-permission guard, audit trail split by file/folder count.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (13): Last reviewed commit: "fix(files): apply activeSort to folders,..." | Re-trigger Greptile

Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts Outdated
Comment thread apps/sim/app/api/workspaces/[id]/files/move/route.ts
Comment thread packages/db/schema.ts
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/hooks/queries/workspace-file-folders.ts
Comment thread apps/sim/lib/uploads/client/download.ts Outdated
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts Outdated
Comment thread apps/sim/app/api/workspaces/[id]/files/folders/[folderId]/route.ts
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/app/api/workspaces/[id]/files/download/route.ts
Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts Outdated
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

@icecrasher321
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx Outdated
Comment thread apps/sim/app/api/workspaces/[id]/files/download/route.ts
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

bugbot run

@icecrasher321
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts
@icecrasher321
Copy link
Copy Markdown
Collaborator Author

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
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts Outdated
…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
@waleedlatif1
Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@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
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/lib/copilot/vfs/workspace-vfs.ts
…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)
@waleedlatif1
Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@cursor review

Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
…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
@waleedlatif1
Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 90a6050. 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.

3 participants