feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(file-manager):folder downloads - #753

Merged
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads
Jan 30, 2026
Merged

feat(file-manager):folder downloads#753
coodos merged 2 commits into
mainfrom
feat/file-manager-folder-downloads

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 30, 2026

Copy link
Copy Markdown
Member

Description of change

adds folder download functionality to file manager

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)
  • Fix (a change which fixes an issue)

How the change has been tested

untested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Multi-item selection now supports both files and folders.
    • Batch downloads can include folders; nested files are gathered recursively.
    • Download progress and status reflect total files across folders and files.
    • ZIP creation preserves folder structure and avoids name collisions with sanitized, unique paths.
    • UI labels and row highlighting updated to indicate item (file/folder) selection.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change expands selection from files to items (files + folders), adds recursive folder traversal to collect nested files, updates the download/batch ZIP flow to handle mixed selections, and introduces filename/path sanitization and collision-resolution for ZIP entries.

Changes

Cohort / File(s)Summary
Selection, traversal & download logic
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Refactored selection to item-centric (fileIditemId, filesitems), added getAllFilesFromFolder() using getFolderContents to recursively gather nested files, split selected items into selectedFiles/selectedFolders, aggregated allFilesToDownload, and reworked batch download/progress logic to handle mixed selections and improved error messages.
ZIP path sanitization & collision handling
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Added sanitizeFilename, sanitizePath, and getUniqueFilePath utilities; replaced simple name-tracking with set-based collision resolution and ensured sanitized, unique paths when inserting files into ZIP.
UI text/behavior & imports
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Updated UI labels to reference “items”/folders, adjusted row highlighting/checkbox bindings for files and folders, and added import getFolderContents from "$lib/stores/folders".

Sequence Diagram(s)

sequenceDiagram
autonumber
participant UI as "Client UI\n(renders items, triggers download)"
participant Store as "FolderStore\n(getFolderContents)"
participant Service as "FileService\n(fetch file blobs)"
participant Zip as "ZipBuilder\n(sanitize & pack)"
UI->>Store: request folder contents (for each selected folder)
Store-->>UI: return file + nested folder list (with paths)
UI->>Service: request file blob (for each file in allFilesToDownload)
Service-->>UI: return file blob
UI->>Zip: add file blob with sanitized unique path
Zip-->>UI: update progress / report errors
UI->>UI: finalize & trigger ZIP download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix/file manager download button #746: Prior PR that introduced multi-file ZIP download and selection UI; this change extends that work with folder support, recursive traversal, and path sanitization.

Suggested reviewers

  • coodos

Poem

🐇 I hopped through folders, far and near,

Gathered each file, brought them here,
Paths made tidy, names made bright,
Packaged snug for one smooth flight—
A rabbit's zip, downloaded light. 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check nameStatusExplanationResolution
Description check❓ InconclusiveThe description follows the template structure and includes required sections, but marks testing as 'untested' which is concerning for a feature merge, and the author self-reported all checklist items as complete despite this limitation.Clarify what 'untested' means - whether manual testing, automated testing, or both are still needed before merging. Add specific details about how folder downloads were validated.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'feat(file-manager):folder downloads' directly describes the main feature being added - folder download functionality - which aligns with the primary changeset objective.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/file-manager-folder-downloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1537-1556: ⚠️ Potential issue | 🟡 Minor

Selection info bar text should say "items" instead of "files".

The selection count displays "X files selected" even when folders are included in the selection. For consistency with the new folder selection feature, consider updating the text.

✏️ Proposed fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🤖 Fix all issues with AI agents
In `@platforms/file-manager/src/routes/`(protected)/files/+page.svelte:
- Around line 969-1003: The recursive getAllFilesFromFolder has no depth limit
and silently swallows errors; modify getAllFilesFromFolder to accept an optional
depth and visited set (e.g., add parameters maxDepth:number = 100,
currentDepth:number = 0, visited:Set<string> = new Set()), check currentDepth
against maxDepth and return or throw a clear error when exceeded, skip recursion
into subfolders already present in visited to avoid cycles (add subfolder.id to
visited before recursing), and propagate errors instead of only console.error so
callers can show a user-facing failure (rethrow or return a rejected Promise
with context that includes folderId/folderName and depth).

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1600-1618: ⚠️ Potential issue | 🟡 Minor

UI text says "files" but selection can include folders.

Since the selection now supports both files and folders, the text should say "items" for accuracy. Currently, selecting 2 folders would display "2 files selected."

✏️ Suggested fix
 <span class="text-sm text-blue-700">
{selectedFileIds.size}
- {selectedFileIds.size === 1 ? "file" : "files"} selected+ {selectedFileIds.size === 1 ? "item" : "items"} selected
</span>
🧹 Nitpick comments (1)
platforms/file-manager/src/routes/(protected)/files/+page.svelte (1)

1305-1378: Consider extracting sanitization utilities to module scope.

The sanitizeFilename, sanitizePath, and getUniqueFilePath functions are recreated on every download invocation. Moving them outside downloadSelectedFiles would avoid this overhead and improve readability.

Additionally, getUniqueFilePath uses case-sensitive comparison for collision detection. On case-insensitive filesystems (Windows, macOS), paths like Report.pdf and report.pdf would be treated as distinct but collide when extracting.

🔧 Suggested extraction and case-insensitive fix
+// Move outside downloadSelectedFiles, at module scope:+function sanitizeFilename(rawName: string): string {+ let name = rawName;+ name = name.replace(/^[a-zA-Z]:/, '');+ name = name.replace(/\\/g, '/');+ name = name.replace(/\.\./g, '');+ const lastSlashIndex = name.lastIndexOf('/');+ if (lastSlashIndex !== -1) {+ name = name.slice(lastSlashIndex + 1);+ }+ name = name.replace(/[/\\]/g, '');+ name = name.replace(/^[.\s]+/, '');+ name = name.trim();+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional removal of control chars+ name = name.replace(/[\x00-\x1f\x7f]/g, '');+ if (!name) {+ name = 'file';+ }+ return name;+}++function sanitizePath(rawPath: string): string {+ if (!rawPath) return "";+ const parts = rawPath.split('/').filter(Boolean);+ const sanitizedParts = parts.map(part => {+ let p = part;+ p = p.replace(/\.\./g, '');+ p = p.replace(/[\\:*?"<>|]/g, '');+ p = p.replace(/^[.\s]+/, '');+ p = p.trim();+ return p || 'folder';+ });+ return sanitizedParts.join('/');+}

For case-insensitive collision detection:

-const usedPaths = new Set<string>();+const usedPaths = new Set<string>();+const usedPathsLower = new Set<string>(); // For case-insensitive check
function getUniqueFilePath(originalPath: string, originalName: string): string {
const sanitizedPath = sanitizePath(originalPath);
const sanitizedName = sanitizeFilename(originalName);
const fullPath = sanitizedPath ? `${sanitizedPath}/${sanitizedName}` : sanitizedName;
+ const fullPathLower = fullPath.toLowerCase();- if (!usedPaths.has(fullPath)) {+ if (!usedPathsLower.has(fullPathLower)) {
usedPaths.add(fullPath);
+ usedPathsLower.add(fullPathLower);
return fullPath;
}
// ... rest of collision handling with similar changes

@coodos
coodos merged commit f0271fa into mainJan 30, 2026
4 checks passed
@coodos
coodos deleted the feat/file-manager-folder-downloads branch January 30, 2026 16:28
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 30, 2026
6 tasks
Sign up for freeto 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.

2 participants

@sosweetham@coodos