Summary
Allow users to select files and folders from multiple directories as part of a single transfer job. Currently, navigating away from a directory clears the selection, forcing users to transfer one directory at a time.
Current behavior
In the GUI file browser, selections are stored in a selectedObjects map keyed by filename (row.name). When the user navigates into a subdirectory or back up to a parent, the selection is cleared. There is no way to accumulate a selection across multiple directories before starting a transfer.
On the backend, a Job has a single uploadBasePath field alongside a sources slice. The upload logic in src/cli/core/upload/upload.go resolves all source paths relative to this single base path. This means all selected files must share a common root directory — files from unrelated directories cannot be combined into one job without losing their path context.
Desired behavior
Users should be able to:
- Select files/folders in one directory
- Navigate to another directory and add more files/folders to the selection
- Start a single transfer job containing all selected items regardless of where they came from
File path integrity must be preserved — the S3 destination path for each file should reflect its original directory structure relative to the transfer destination, not be flattened.
Example
A user wants to upload:
/Volumes/Media/ProjectA/footage/scene1.mov
/Volumes/Media/ProjectA/audio/mix_v2.wav
/Volumes/Media/ProjectB/exports/final.mov
These come from three different directories. Today this requires three separate transfer jobs. With this feature, one job handles all three, and the S3 structure mirrors the source paths.
What needs to change
GUI (src/gui/src/app/components/layout/file-browser/):
- The
selectedObjects map is currently scoped to the active directory view and cleared on navigation. It needs to become a persistent cross-directory selection, keyed by full absolute path rather than just filename.
- A selection panel or indicator should show the user what is currently queued across directories, with the ability to remove individual items.
- Navigating directories should not clear the accumulated selection.
Backend (src/cli/types/jobmanagertypes/job.go and src/cli/core/upload/upload.go):
JobConfig.UploadBasePath is a single string representing the common root for all sources. This assumption breaks when sources come from different directories.
- The upload logic in
upload.go resolves relative paths against UploadBasePath using filepath.Join. For multi-directory selections, each source needs to carry its own base path context, or sources must always be passed as absolute paths.
- The CLI upload command already handles absolute paths correctly —
fs.LongestCommonDirectories(sources) is used to find a common base. This approach could be extended, but care is needed to ensure the S3 destination structure is preserved correctly when there is no meaningful common root.
File path integrity
When files from different directories are combined into one job, the S3 destination paths must preserve the original directory structure. For example, uploading the three files above to an S3 prefix projects/ should produce:
s3://bucket/projects/Volumes/Media/ProjectA/footage/scene1.mov
s3://bucket/projects/Volumes/Media/ProjectA/audio/mix_v2.wav
s3://bucket/projects/Volumes/Media/ProjectB/exports/final.mov
Or, if a common root is detected (/Volumes/Media/), strip it:
s3://bucket/projects/ProjectA/footage/scene1.mov
s3://bucket/projects/ProjectA/audio/mix_v2.wav
s3://bucket/projects/ProjectB/exports/final.mov
The chosen behaviour should be consistent and clearly communicated to the user before the transfer starts.
Relevant files
src/gui/src/app/components/layout/file-browser/file-browser.component.ts — selectedObjects map, clickFileBrowserRow, handleCmdClick, handleShiftClick, doubleClickRow
src/cli/types/jobmanagertypes/job.go — Job struct, JobConfig, uploadBasePath field
src/cli/core/upload/upload.go — path resolution logic using uploadBasePath
src/cli/cmd/s3.go — uploadFile, fs.LongestCommonDirectories usage for absolute path handling
Acceptance criteria
- Users can select files and folders from multiple directories before starting a transfer
- Navigating between directories does not clear the accumulated selection
- A persistent selection indicator shows what is queued, with the ability to remove individual items
- File path integrity is preserved in the S3 destination structure
- The existing single-directory selection behaviour is unchanged for users who do not navigate between directories
Summary
Allow users to select files and folders from multiple directories as part of a single transfer job. Currently, navigating away from a directory clears the selection, forcing users to transfer one directory at a time.
Current behavior
In the GUI file browser, selections are stored in a
selectedObjectsmap keyed by filename (row.name). When the user navigates into a subdirectory or back up to a parent, the selection is cleared. There is no way to accumulate a selection across multiple directories before starting a transfer.On the backend, a
Jobhas a singleuploadBasePathfield alongside asourcesslice. The upload logic insrc/cli/core/upload/upload.goresolves all source paths relative to this single base path. This means all selected files must share a common root directory — files from unrelated directories cannot be combined into one job without losing their path context.Desired behavior
Users should be able to:
File path integrity must be preserved — the S3 destination path for each file should reflect its original directory structure relative to the transfer destination, not be flattened.
Example
A user wants to upload:
These come from three different directories. Today this requires three separate transfer jobs. With this feature, one job handles all three, and the S3 structure mirrors the source paths.
What needs to change
GUI (
src/gui/src/app/components/layout/file-browser/):selectedObjectsmap is currently scoped to the active directory view and cleared on navigation. It needs to become a persistent cross-directory selection, keyed by full absolute path rather than just filename.Backend (
src/cli/types/jobmanagertypes/job.goandsrc/cli/core/upload/upload.go):JobConfig.UploadBasePathis a single string representing the common root for all sources. This assumption breaks when sources come from different directories.upload.goresolves relative paths againstUploadBasePathusingfilepath.Join. For multi-directory selections, each source needs to carry its own base path context, or sources must always be passed as absolute paths.fs.LongestCommonDirectories(sources)is used to find a common base. This approach could be extended, but care is needed to ensure the S3 destination structure is preserved correctly when there is no meaningful common root.File path integrity
When files from different directories are combined into one job, the S3 destination paths must preserve the original directory structure. For example, uploading the three files above to an S3 prefix
projects/should produce:Or, if a common root is detected (
/Volumes/Media/), strip it:The chosen behaviour should be consistent and clearly communicated to the user before the transfer starts.
Relevant files
src/gui/src/app/components/layout/file-browser/file-browser.component.ts—selectedObjectsmap,clickFileBrowserRow,handleCmdClick,handleShiftClick,doubleClickRowsrc/cli/types/jobmanagertypes/job.go—Jobstruct,JobConfig,uploadBasePathfieldsrc/cli/core/upload/upload.go— path resolution logic usinguploadBasePathsrc/cli/cmd/s3.go—uploadFile,fs.LongestCommonDirectoriesusage for absolute path handlingAcceptance criteria