Conversation
Added a dummy bcrypt hash to prevent user enumeration timing attacks in JSON authentication.
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…ilebrowser#5738) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…rs (filebrowser#5890) Co-authored-by: Henrique Dias <mail@hacdias.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
📝 WalkthroughWalkthroughIntroduces a pluggable UploadCache (memory + Redis), integrates it into TUS handlers, refactors per-file conflict resolution in frontend (personalized/batch modes), adds password-confirmed-sensitive operations, enforces share→download permission, and updates encoding/preview handling, CI, docs, and various tooling versions. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant FileBrowser as FileBrowser API
participant UploadCache
participant Storage as File Storage
participant Redis
Client->>FileBrowser: POST /api/tus (start upload)
FileBrowser->>UploadCache: Register(filePath, fileSize)
alt Using Redis
UploadCache->>Redis: SET filebrowser:upload:path fileSize<br/>EX 180
Redis-->>UploadCache: OK
else Using Memory
UploadCache->>UploadCache: Store in TTL cache
end
UploadCache-->>FileBrowser: OK
FileBrowser-->>Client: 201 Location: /api/tus/path
Client->>FileBrowser: PATCH /api/tus/path (upload chunks)
FileBrowser->>UploadCache: Touch(filePath)
UploadCache->>Redis: EXPIRE filebrowser:upload:path 180
FileBrowser->>Storage: Write chunk
Storage-->>FileBrowser: OK
FileBrowser-->>Client: 204 No Content
Client->>FileBrowser: DELETE /api/tus/path (complete)
FileBrowser->>UploadCache: Complete(filePath)
UploadCache->>Redis: DEL filebrowser:upload:path
FileBrowser->>Storage: Finalize file
Storage-->>FileBrowser: OK
FileBrowser-->>Client: 204 No Content
sequenceDiagram
participant User
participant UI as Frontend UI
participant API as FileBrowser API
participant Storage
User->>UI: Drag/drop files to destination
UI->>API: GET /api/resources (fetch dest contents)
API->>Storage: List destination
Storage-->>API: [existing files]
API-->>UI: [existing files]
alt Conflicts detected
UI->>UI: Build ConflictingResource[] array
UI->>UI: Show ResolveConflict modal<br/>(personalized mode)
User->>UI: Select per-item decisions<br/>(origin/dest or both)
UI->>UI: Process result array<br/>Mark overwrite/rename per item
User->>UI: Confirm
UI->>API: POST /api/resources<br/>(move/copy with per-item flags)
API->>Storage: Move/copy files<br/>applying per-item decisions
Storage-->>API: OK
else No conflicts
UI->>API: POST /api/resources<br/>(move/copy no overrides)
API->>Storage: Move/copy files
Storage-->>API: OK
end
API-->>UI: Success
UI->>UI: Refresh listing
UI-->>User: Operation complete
Estimated code review effort🎯 4 (Complex) | ⏱️ ~90 minutes ✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
http/users.go (1)
187-199:⚠️ Potential issue | 🟠 Major
LockPasswordno longer triggers current-password verification.The lookup lowercases
field, but the map still storeslockPassword. Updates that only touchLockPasswordwill miss the sensitive-field check entirely.🛠️ Suggested fix
var sensibleFields = map[string]struct{}{ "all": {}, "username": {}, "password": {}, "scope": {}, - "lockPassword": {}, + "lockpassword": {}, "commands": {}, "perm": {}, }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@http/users.go` around lines 187 - 199, The sensitive-field check fails for "LockPassword" because the map sensibleFields uses "lockPassword" but the lookup lowercases field via strings.ToLower(field); make the keys and lookup consistent by storing all keys in sensibleFields as lowercase (e.g., "lockpassword", "username", "password", etc.) or by removing the lowercase call and comparing case-sensitively; update the map declaration (sensibleFields) and ensure the loop in for _, field := range req.Which uses the same normalization before checking, so the branch that calls users.CheckPwd(req.CurrentPassword, d.user.Password) correctly executes for LockPassword changes.
🟡 Minor comments (23)
rules/rules.go-34-43 (1)
34-43:⚠️ Potential issue | 🟡 MinorNormalize trailing-slash variants before exact-match check.
Line 34 compares raw strings, so
"/uploads/"won’t match"/uploads"even though child paths match via Line 43. This creates inconsistent rule behavior for directory rules.🔧 Proposed fix
func (r *Rule) Matches(path string) bool { if r.Regex { return r.Regexp.MatchString(path) } - if path == r.Path { + normalize := func(p string) string { + if p == "/" { + return "/" + } + return strings.TrimSuffix(p, "/") + } + + normalizedPath := normalize(path) + normalizedRulePath := normalize(r.Path) + + if normalizedPath == normalizedRulePath { return true } - prefix := r.Path + prefix := normalizedRulePath if prefix != "/" && !strings.HasSuffix(prefix, "/") { prefix += "/" } - return strings.HasPrefix(path, prefix) + return strings.HasPrefix(normalizedPath, prefix) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rules/rules.go` around lines 34 - 43, Normalize trailing-slash variants for both operands before the equality check: trim any trailing "/" from r.Path and path (but keep "/" as-is) then perform the exact-match (r.Path == path) using those normalized values; afterwards rebuild the prefix logic from the normalized r.Path (add a trailing "/" only when prefix != "/") and use strings.HasPrefix(path, prefix) for the child-path check so "/uploads" and "/uploads/" behave consistently. Reference symbols: r.Path, path, strings.HasPrefix.frontend/src/types/hostinger.d.ts-1-4 (1)
1-4:⚠️ Potential issue | 🟡 MinorFix trailing semicolon and consider adding
export.The static analysis flagged a formatting issue on line 4. Additionally, if this interface needs to be importable from other files, it should be explicitly exported.
🔧 Proposed fix
-interface DiskUsage { +export interface DiskUsage { size: number; inodes: number; -}; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/types/hostinger.d.ts` around lines 1 - 4, The interface declaration for DiskUsage has a trailing semicolon and may need to be exported; remove the trailing semicolon after the closing brace of interface DiskUsage and, if the type should be usable elsewhere, add an export modifier (export interface DiskUsage) so other modules can import it.frontend/src/utils/clipboard.ts-54-66 (1)
54-66:⚠️ Potential issue | 🟡 MinorReject with an explicit
Errorin unsupported clipboard-read path.At Line 63,
reject()is called without a reason, which makes failure handling ambiguous for callers.💡 Proposed fix
-export function read() { +export function read() { return new Promise<string>((resolve, reject) => { @@ if ( @@ ) { navigator.clipboard.readText().then(resolve).catch(reject); } else { - reject(); + reject( + new Error("Clipboard read is not supported in this browser/context") + ); } }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/clipboard.ts` around lines 54 - 66, The read function's Promise rejects with no reason when clipboard access is unsupported; update the unsupported-path rejection in read() to call reject with an explicit Error (e.g., new Error("Clipboard API not available or insecure context")) so callers receive a meaningful rejection reason; keep the existing navigator.clipboard.readText().then(resolve).catch(reject) flow unchanged but ensure all reject(...) calls pass an Error instance.frontend/src/stores/layout.ts-79-79 (1)
79-79:⚠️ Potential issue | 🟡 MinorHandle async
closerejection when dismissing prompts.At Line 79,
close?.()can reject and currently goes unhandled.💡 Proposed fix
closeHovers() { - this.prompts.pop()?.close?.(); + const prompt = this.prompts.pop(); + void prompt?.close?.().catch(() => { + // no-op: prompt close failures should not surface as unhandled rejections + }); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/stores/layout.ts` at line 79, The call this.prompts.pop()?.close?.() can return a rejected promise that is currently unhandled; change it to capture the popped prompt and call its close function wrapped in Promise.resolve and handle rejections (e.g., await Promise.resolve(close()).catch(err => /* log or swallow */)) so both sync and async close implementations are covered and any rejection is handled; reference the popped object from this.prompts and its close method when making the change.frontend/src/utils/index.ts-9-16 (1)
9-16:⚠️ Potential issue | 🟡 MinorGuard
binding.valuebefore invoking to prevent runtime TypeError.If the directive is ever bound to a non-function, this will throw on click. Add a function check before calling it.
💡 Proposed fix
created(el: HTMLElement, binding: any) { el.clickOutsideEvent = (event: Event) => { const target = event.target; if (target instanceof Node) { if (!el.contains(target)) { - binding.value(event); + if (typeof binding.value === "function") { + binding.value(event); + } } } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/index.ts` around lines 9 - 16, The click-outside directive's created hook assigns el.clickOutsideEvent and calls binding.value without guarding its type; update the created function so before invoking binding.value(event) you check that binding.value is a function (e.g., typeof binding.value === "function") and only call it if true, otherwise skip or optionally log a warning; adjust any related teardown/unbind logic to assume el.clickOutsideEvent may not have invoked a handler.frontend/src/components/files/VideoPlayer.vue-170-170 (1)
170-170:⚠️ Potential issue | 🟡 MinorRestore a
ptalias to avoid Portuguese fallback to English.Keeping only
"pt-pt"may miss genericlang="pt"and fall back to English. Add aptmapping alias.💡 Proposed fix
"pt-br": () => import("video.js/dist/lang/pt-BR.json"), + pt: () => import("video.js/dist/lang/pt-PT.json"), "pt-pt": () => import("video.js/dist/lang/pt-PT.json"),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/files/VideoPlayer.vue` at line 170, The language map in VideoPlayer.vue only registers "pt-pt", causing browsers/pages with lang="pt" to fall back to English; add a "pt" alias that points to the same resource (e.g., register "pt": () => import("video.js/dist/lang/pt-PT.json")) alongside the existing "pt-pt" entry so both "pt" and "pt-pt" resolve to Portuguese for the Video.js player.www/docs/authentication.md-51-53 (1)
51-53:⚠️ Potential issue | 🟡 MinorFix the fence languages in the new auth examples.
The PowerShell sample is fenced as
sh, and the newkey=valueblocks have no language tag, which is already tripping markdownlint. Usepowershellforauth.ps1andtext/inifor the hook output snippets.Also applies to: 57-97, 103-105, 132-155
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@www/docs/authentication.md` around lines 51 - 53, Update the markdown fences for the hook auth examples: change the PowerShell example fence from sh to powershell around the auth.ps1 invocation (the line with filebrowser config set --auth.method=hook --auth.command="powershell.exe -File C:\route\to\your\script\auth.ps1"), and add appropriate language tags for the hook output/key=value blocks (use text or ini) wherever those blocks appear (occurrences noted around lines 57-97, 103-105, 132-155) so markdownlint stops flagging missing/incorrect fence languages.frontend/src/i18n/pt_PT.json-56-60 (1)
56-60:⚠️ Potential issue | 🟡 MinorTranslate the new
pt_PTstrings before shipping.These additions are still in English (
openDirect, conflict-resolution copy, current-password prompt text, share-permission help, etc.), so the Portuguese UI will switch languages in the new flows.Also applies to: 105-105, 200-211, 274-274
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/pt_PT.json` around lines 56 - 60, The new pt_PT localization contains untranslated English keys—update the Portuguese translations for "openDirect", "overrideAll", "skipAll", "renameAll", and "singleDecision" in frontend/src/i18n/pt_PT.json and also translate the other untranslated strings noted in the review (the current-password prompt, share-permission help text, and any conflict-resolution copy found in the other changed entries) so all UI strings are in Portuguese; replace the English values with proper pt_PT phrasing while preserving the same JSON keys and punctuation.frontend/src/views/files/Preview.vue-313-317 (1)
313-317:⚠️ Potential issue | 🟡 MinorThe large-CSV error path is now unreachable.
isCsvalready requiressize <= CSV_MAX_SIZE, so Line 418 can never run. Large.csvfiles now fall through to the generic blob/no-preview state instead of showingfiles.csvTooLarge.Also applies to: 418-420
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/views/files/Preview.vue` around lines 313 - 317, The preview logic currently makes the large-CSV error path unreachable because isCsv includes the size check (isCsv uses fileStore.req?.extension and fileStore.req.size <= CSV_MAX_SIZE), so remove the size condition from that computed and instead add/use a separate check for oversized CSVs (e.g., isCsvTooLarge that checks fileStore.req?.extension.toLowerCase() === ".csv" && fileStore.req.size > CSV_MAX_SIZE) and update the template to show files.csvTooLarge when isCsvTooLarge is true; reference the existing isCsv computed, CSV_MAX_SIZE, fileStore.req.size and the large-CSV error UI branch to implement this change.frontend/src/i18n/ru_RU.json-274-274 (1)
274-274:⚠️ Potential issue | 🟡 MinorUntranslated string in Russian locale.
"share": "Share files (require download permission)"should be translated to Russian.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/ru_RU.json` at line 274, The Russian locale file contains an untranslated string for the key "share" in ru_RU.json; replace the English value "Share files (require download permission)" with a proper Russian translation (e.g., "Поделиться файлами (требуется разрешение на скачивание)") ensuring the JSON key "share" remains unchanged and the file stays valid JSON.frontend/src/i18n/ru_RU.json-105-105 (1)
105-105:⚠️ Potential issue | 🟡 MinorUntranslated string in Russian locale.
"fileEncoding": "File Encoding"should be translated to Russian (e.g., "Кодировка файла").🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/ru_RU.json` at line 105, The Russian locale file has an untranslated entry for the key "fileEncoding" in ru_RU.json; replace the English value "File Encoding" with the proper Russian translation (for example "Кодировка файла") so the "fileEncoding" key contains the localized Russian string.compose.yaml-19-26 (1)
19-26:⚠️ Potential issue | 🟡 MinorInline ACL creation exposes credentials in process listing.
The Redis password appears in the shell command, which may be visible in process listings. For a development example this is acceptable, but consider noting this limitation or using a mounted ACL file for production.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compose.yaml` around lines 19 - 26, The inline ACL creation in the compose.yaml command block writes credentials into a shell command (users.acl) and then starts redis-server with "--aclfile /tmp/users.acl", which can expose secrets in process listings; fix by switching to a mounted ACL file or Docker secret: create the ACL file outside the container and mount it into the service (or use docker secrets) and update the redis-server invocation to reference the mounted path (replace the inline here-doc that creates /tmp/users.acl), or if you must keep this development pattern add an explicit comment in compose.yaml noting that the current inline ACL method is for development only and is insecure for production.compose.yaml-11-12 (1)
11-12:⚠️ Potential issue | 🟡 MinorHardcoded credentials in environment variable.
The Redis password
filebrowseris hardcoded in plaintext. For development this may be acceptable, but consider:
- Adding a comment noting this should be changed in production
- Using Docker secrets or environment variable substitution (
${REDIS_PASSWORD:-filebrowser})🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compose.yaml` around lines 11 - 12, The REDIS_CACHE_URL environment value currently embeds a plaintext password ("filebrowser") which should not be hardcoded; change it to use an environment variable or Docker secret (e.g., reference ${REDIS_PASSWORD:-filebrowser} or a Docker secret) and update the REDIS_CACHE_URL value to interpolate that variable (keeping the redis:// scheme), and add an inline comment next to REDIS_CACHE_URL/REDIS_PASSWORD stating that the default is for development only and must be replaced with a secure secret in production.frontend/src/i18n/ru_RU.json-200-211 (1)
200-211:⚠️ Potential issue | 🟡 MinorMultiple untranslated strings in Russian locale.
These conflict resolution and password prompt strings are in English and should be translated to Russian:
replaceOrSkip,resolveConflict,singleConflictResolve,fastConflictResolveuploadingFiles,filesInOrigin,filesInDestoverride,skip,forbiddenErrorcurrentPassword,currentPasswordMessage🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/ru_RU.json` around lines 200 - 211, The listed localization keys in ru_RU.json are still in English and must be replaced with Russian translations: update the values for "replaceOrSkip", "resolveConflict", "singleConflictResolve", "fastConflictResolve", "uploadingFiles", "filesInOrigin", "filesInDest", "override", "skip", "forbiddenError", "currentPassword", and "currentPasswordMessage" to their correct Russian equivalents; make sure to only change the string values (preserve the keys and JSON structure), keep any placeholders like {count} intact, and ensure proper escaping/quoting and punctuation in the translated strings.frontend/src/i18n/en.json-206-206 (1)
206-206:⚠️ Potential issue | 🟡 MinorGrammatical error in English text.
"The destination folder there are {count} files with same name."should be"In the destination folder, there are {count} files with the same name."or similar.📝 Proposed fix
- "fastConflictResolve": "The destination folder there are {count} files with same name.", + "fastConflictResolve": "In the destination folder, there are {count} files with the same name.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/en.json` at line 206, The translation string for the key "fastConflictResolve" is grammatically incorrect; update its value to a correct English sentence such as "In the destination folder, there are {count} files with the same name." by editing the entry for "fastConflictResolve" in the en.json file so it reads the corrected sentence and preserves the {count} placeholder.frontend/src/components/prompts/Prompts.vue-63-68 (1)
63-68:⚠️ Potential issue | 🟡 MinorMissing
returnstatement causes dead code.Line 65
if (!modal) null;is a no-op — thenullexpression is evaluated but not returned. The function will returnundefinedinstead of the intendednull.🐛 Proposed fix
const modal = computed(() => { const modal = components.get(currentPromptName.value!); - if (!modal) null; + if (!modal) return null; return modal; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/prompts/Prompts.vue` around lines 63 - 68, The computed getter named "modal" incorrectly evaluates `null` without returning it, causing an unintended undefined return; update the computed function (modal) so that when components.get(currentPromptName.value!) yields a falsy value it explicitly returns null (e.g., replace the `if (!modal) null;` no-op with `return null;` or use a fallback like `return components.get(...) || null;`) to ensure the computed always returns either the component or null.frontend/src/i18n/es_AR.json-55-60 (1)
55-60:⚠️ Potential issue | 🟡 MinorMissing Spanish translations for new strings.
Several new strings in
es_AR.jsonare in English rather than Spanish. This includes:
buttons.openDirect,buttons.overrideAll,buttons.skipAll,buttons.renameAll,buttons.singleDecisionfiles.fileEncoding- Multiple entries in
promptssection (replaceOrSkip,resolveConflict,singleConflictResolve, etc.)Consider translating these for a consistent Spanish user experience, or tracking this as a follow-up task.
Would you like me to open an issue to track the missing translations?
Also applies to: 105-105, 200-211
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_AR.json` around lines 55 - 60, The JSON file has several keys still in English; update the Spanish translations for buttons.openDirect, buttons.overrideAll, buttons.skipAll, buttons.renameAll, buttons.singleDecision, files.fileEncoding and the prompts group (e.g., prompts.replaceOrSkip, prompts.resolveConflict, prompts.singleConflictResolve, etc.) in frontend/src/i18n/es_AR.json so all values are Spanish and consistent (use proper accents and regional phrasing), and also verify and translate the entries referenced around the other ranges (lines ~105 and ~200-211) to keep the locale complete; if you can't translate now, create a tracked issue listing these exact keys instead.frontend/src/components/DropdownModal.vue-29-31 (1)
29-31:⚠️ Potential issue | 🟡 MinorTop positioning may cause dropdown to overlap the trigger.
When
positionincludes "top", the dropdown'stopis set torect.top, which places the dropdown starting at the trigger's top edge rather than above it. This likely causes the dropdown to overlap or obscure the trigger button.🔧 Suggested fix to position above trigger
You'll need to measure the dropdown's height after it renders, or use CSS
transform/bottomanchoring. A simpler approach if the dropdown height is predictable:top: props.position.includes("bottom") ? `${rect.bottom + 2}px` - : `${rect.top}px`, + : "auto", + bottom: props.position.includes("top") + ? `${window.innerHeight - rect.top + 2}px` + : "auto",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/DropdownModal.vue` around lines 29 - 31, The dropdown top calculation uses rect.top when props.position includes "top", causing overlap; update the positioning logic in DropdownModal.vue to account for the dropdown height: after mount/read DOM (use the dropdown element ref and getBoundingClientRect().height or measure clientHeight), compute top as rect.top - dropdownHeight (or set a bottom anchor using window inner height) so the dropdown sits above the trigger; adjust the code that currently sets top: props.position.includes("bottom") ? `${rect.bottom + 2}px` : `${rect.top}px` to use the measured dropdown height (or bottom anchoring) when position includes "top".frontend/src/types/file.d.ts-77-77 (1)
77-77:⚠️ Potential issue | 🟡 MinorRemove trailing whitespace.
Static analysis flagged trailing whitespace on this line.
🧹 Proposed fix
} - + interface CsvData {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/types/file.d.ts` at line 77, Remove the trailing whitespace on the flagged line in the TypeScript declaration file: edit the affected declaration line in the types .d.ts module and delete any trailing spaces so the line ends cleanly with no extra whitespace.frontend/src/views/files/Editor.vue-237-241 (1)
237-241:⚠️ Potential issue | 🟡 MinorSelection change listener not cleaned up.
The
changeSelectionlistener is added to the selection object but never removed inonBeforeUnmount. Whileeditor.value?.destroy()may handle this, explicitly removing the listener ensures no memory leaks.🧹 Suggested improvement
Store a reference to the handler and remove it on unmount:
+const selectionHandler = ref<(() => void) | null>(null); + const initEditor = (fileContent: string) => { // ... editor setup ... const selection = editor.value?.getSelection(); - selection.on("changeSelection", function () { + selectionHandler.value = function () { isSelectionEmpty.value = selection.isEmpty(); - }); + }; + selection.on("changeSelection", selectionHandler.value); };Then in
onBeforeUnmount:onBeforeUnmount(() => { window.removeEventListener("keydown", keyEvent); window.removeEventListener("beforeunload", handlePageChange); + if (selectionHandler.value && editor.value) { + editor.value.getSelection().off("changeSelection", selectionHandler.value); + } editor.value?.destroy(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/views/files/Editor.vue` around lines 237 - 241, The selection change listener added via selection.on("changeSelection", ...) is never removed; store the handler function (e.g., const onChange = () => { isSelectionEmpty.value = selection.isEmpty(); }) when calling selection.on and then remove it in onBeforeUnmount by calling the selection.off("changeSelection", onChange) (or the selection.removeListener equivalent) after checking selection exists; ensure you reference the same handler variable and the same selection instance returned by editor.value?.getSelection() so the listener is properly cleaned up.frontend/src/i18n/zh_CN.json-57-60 (1)
57-60:⚠️ Potential issue | 🟡 MinorMultiple strings missing Chinese translations.
Several newly added strings are in English rather than Chinese:
- Lines 57-60:
overrideAll,skipAll,renameAll,singleDecision- Line 105:
fileEncoding- Lines 200-211: All conflict resolution prompts
- Line 273:
settings.perm.shareThese should be translated to maintain consistency with the rest of the Chinese localization file.
Also applies to: 105-105, 200-211, 273-273
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/zh_CN.json` around lines 57 - 60, Several localization entries are still in English; replace the English values with proper Simplified Chinese translations for these JSON keys: "overrideAll", "skipAll", "renameAll", "singleDecision", "fileEncoding", the conflict-resolution prompt keys in the block covering lines ~200-211, and "settings.perm.share". Open the zh_CN JSON, locate those keys (e.g., overrideAll/skipAll/renameAll/singleDecision/fileEncoding/settings.perm.share and the conflict prompt keys), and update their string values to accurate Chinese phrases consistent with the existing style and tone of the file; ensure proper escaping and valid JSON formatting after changes.frontend/src/utils/encodings.ts-215-218 (1)
215-218:⚠️ Potential issue | 🟡 MinorTextDecoder constructor can throw for invalid encodings.
If an invalid encoding string is passed,
new TextDecoder(encoding)throws aRangeError. Consider wrapping in try-catch or validating againstavailableEncodingsfirst.🛡️ Proposed fix to handle invalid encoding
export function decode(content: ArrayBuffer, encoding: string): string { - const decoder = new TextDecoder(encoding); - return decoder.decode(content); + try { + const decoder = new TextDecoder(encoding); + return decoder.decode(content); + } catch (e) { + // Fallback to UTF-8 if encoding is invalid + console.warn(`Invalid encoding "${encoding}", falling back to UTF-8`); + return new TextDecoder("utf-8").decode(content); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/encodings.ts` around lines 215 - 218, The decode function can throw when given an invalid encoding; update the decode(content: ArrayBuffer, encoding: string) implementation to validate the encoding or wrap the TextDecoder construction/decoding in a try-catch: check the encoding against availableEncodings (or default to 'utf-8') and if invalid either use the fallback encoding or rethrow a clearer error, and ensure decoder.decode(content) is also guarded so decode always returns a string or throws a controlled error; reference the decode function and availableEncodings when making the change.frontend/src/components/prompts/ResolveConflict.vue-159-159 (1)
159-159:⚠️ Potential issue | 🟡 MinorPotential undefined value for
conflictref.If
currentPrompt?.props.conflictis undefined,conflict.valuewill be undefined, which could cause runtime errors when iterating in the template. Consider providing a default empty array.🛡️ Proposed fix to add default value
-const conflict = ref<ConflictingResource[]>(currentPrompt?.props.conflict); +const conflict = ref<ConflictingResource[]>(currentPrompt?.props.conflict ?? []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/prompts/ResolveConflict.vue` at line 159, The ref "conflict" initialized from currentPrompt?.props.conflict can be undefined and cause runtime errors when iterated; update the initialization of the conflict ref (the symbol conflict and its type ConflictingResource[]) to default to an empty array when currentPrompt?.props.conflict is falsy so conflict.value is always an array usable by the template and components that iterate over it.
🧹 Nitpick comments (19)
version/version.go (1)
1-1: Narrow therevivesuppression scope.At Line 1,
//nolint:revivedisables allrevivechecks for the entire file. Prefer a targeted suppression on the exact declaration that needs it (or add a brief reason) so future lint findings in this file aren’t silently masked.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@version/version.go` at line 1, Remove the file-level "//nolint:revive" at the top and move a targeted suppression to only the exact declaration that requires it (or replace it with an inline suppression that includes a short justification), i.e., delete the top-of-file nolint and add "//nolint:revive // reason: <brief justification>" immediately after the offending declaration identifier so only that symbol is exempt from revive.frontend/src/components/prompts/FileList.vue (1)
171-177: Consider edge case for URL parsing.The directory name extraction assumes the URL ends with a trailing slash (e.g.,
/files/newdir/). If the URL format ever changes to not include a trailing slash,paths[paths.length - 2]would return the wrong segment.If the
newDirprompt guarantees trailing slashes, this is fine. Otherwise, consider a more robust approach:💡 More robust name extraction
confirm: (url) => { - const paths = url.split("/"); + const paths = url.split("/").filter(Boolean); this.items.push({ - name: paths[paths.length - 2], + name: paths[paths.length - 1], url: url, }); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/prompts/FileList.vue` around lines 171 - 177, The confirm handler in FileList.vue currently derives the directory name using paths[paths.length - 2], which breaks if the URL lacks a trailing slash; update the logic in the confirm: (url) => { ... } block to normalize the URL (trim any trailing slash) then extract the last path segment (e.g., split on "/" and use the final non-empty segment) before pushing into this.items so the name is correct whether or not the URL ends with a slash.frontend/src/api/users.ts (1)
51-53: Prefer omitting DELETE body when nocurrent_passwordis provided.This keeps the old no-body semantics and avoids needless
{}payloads.💡 Proposed refactor
export async function remove( id: number, currentPassword: string | null = null ) { + const body = + currentPassword != null + ? JSON.stringify({ current_password: currentPassword }) + : undefined; + await fetchURL(`/api/users/${id}`, { method: "DELETE", - body: JSON.stringify({ - ...(currentPassword != null ? { current_password: currentPassword } : {}), - }), + ...(body ? { body } : {}), }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/api/users.ts` around lines 51 - 53, The DELETE request currently always sends a JSON body (JSON.stringify({ ...(currentPassword != null ? { current_password: currentPassword } : {}) })), producing an empty {} when no currentPassword is given; update the delete user request (the code constructing the fetch/axios call in the deleteUser flow where currentPassword is used) to only set the body when currentPassword != null (i.e., omit the body entirely instead of JSON.stringify({})), and ensure any "Content-Type: application/json" header is only added when a body is present. This touches the currentPassword check, the body assignment, and the surrounding fetch request construction.frontend/src/css/base.css (1)
221-223: Avoid brittle style-substring selectors for modal stacking.Targeting
body > div[style*="z-index: 9990"]is fragile; a minor upstream style format change can break stacking. Prefer a stable class/attribute hook from the modal container.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/css/base.css` around lines 221 - 223, The CSS rule using the brittle selector body > div[style*="z-index: 9990"] should be replaced with a stable class/attribute hook on the modal container; update the modal markup to add a predictable class/attribute (for example .modal-root or data-modal-root) to the modal wrapper and change the selector to that class (e.g., .modal-root { z-index: 10000 !important; }) so stacking no longer depends on inline style content.http/public_test.go (1)
113-120: Make the owner/user mapping explicit in the fixture.The new
403cases only work because the first saved user happens to become ID1. Since the share already points atUserID: 1, set the persisted user's ID explicitly here so the test does not depend on Storm's auto-ID behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@http/public_test.go` around lines 113 - 120, The test relies on Storm auto-assigning the first saved user's ID to 1 which makes the new 403 cases fragile; update the fixture where storage.Users.Save is called to persist a users.User with an explicit ID (set the ID field to 1) so the saved user's ID matches the Share's UserID used in the test, ensuring the owner/user mapping is deterministic; locate the users.User construction passed to storage.Users.Save and add the ID field (or otherwise set the persisted user's ID to 1) to make the mapping explicit.frontend/src/utils/upload.ts (1)
16-22: Consider using a Map for O(1) lookups.The
getFilehelper performs a linear search O(n) for each file, resulting in O(n×m) complexity overall. For large uploads with many destination files, consider building aMap<string, ResourceItem>once for O(1) lookups.♻️ Proposed optimization
+ const destMap = new Map(dest.map(item => [item.name, item])); + - function getFile(name: string): ResourceItem | null { - for (const item of dest) { - if (item.name == name) return item; - } - - return null; - } + function getFile(name: string): ResourceItem | null { + return destMap.get(name) ?? null; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/upload.ts` around lines 16 - 22, The linear-search helper getFile iterates over dest for every lookup causing O(n×m) cost; replace it by building a Map<string, ResourceItem> from dest once (e.g., destMap) and change getFile (or callers) to use destMap.get(name) for O(1) lookups; ensure the Map is constructed where dest is available/updated so subsequent calls to getFile or other helpers use the prebuilt map and keep return semantics (null when missing).compose.yaml (1)
4-4: Consider pinning image versions for reproducibility.Using
:latesttags for bothfilebrowser/filebrowserandredismakes builds non-reproducible. Consider pinning to specific versions (e.g.,redis:7.2or a specific SHA digest).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compose.yaml` at line 4, The docker-compose uses floating tags like "image: filebrowser/filebrowser:latest" (and an unpinned "redis" service) which makes builds non-reproducible; update the compose entries to pin images to specific version tags or immutable digests (e.g., replace image: filebrowser/filebrowser:latest with filebrowser/filebrowser:<stable-version-or-sha> and set redis to a specific tag like redis:7.2 or a sha256 digest), and ensure any other service image lines follow the same pinning practice (search for "image: filebrowser/filebrowser:latest" and the "redis" service to apply the changes).frontend/src/components/files/ListingItem.vue (1)
262-272: Variable shadowing reduces readability.The inner
const action(line 263) shadows the outeractionfunction parameter, making the code confusing to follow. Consider renaming the inner variable toapiActionor similar.♻️ Proposed refactor for clarity
- const action = (overwrite?: boolean, rename?: boolean) => { - const action = + const performAction = (overwrite?: boolean, rename?: boolean) => { + const apiAction = (event as KeyboardEvent).ctrlKey || (event as KeyboardEvent).metaKey ? api.copy : api.move; - action(items, overwrite, rename) + apiAction(items, overwrite, rename) .then(() => { fileStore.reload = true; }) .catch($showError); };Then update call sites at lines 296 and 304 to use
performAction()andperformAction(false, false).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/files/ListingItem.vue` around lines 262 - 272, The issue is that the inner const action shadows the outer action function parameter, reducing readability; to fix, rename the inner variable (the one selecting between api.copy and api.move) to a non-conflicting name like apiAction or rename the outer wrapper to performAction, update its references accordingly, and ensure calls that currently invoke action(...) (the wrapper) are updated to the new name (e.g., performAction() and performAction(false, false)); specifically change the inner selection from const action = ((event as KeyboardEvent)... ? api.copy : api.move) to const apiAction = ... (or keep apiAction and call apiAction(items, overwrite, rename)), or rename the outer wrapper to performAction and keep inner apiAction, and preserve the existing .then(() => { fileStore.reload = true; }).catch($showError) flow so functionality with api.copy/api.move and fileStore.reload/$showError remains unchanged.frontend/src/components/prompts/Rename.vue (1)
86-88: Unusedcancelmethod.The
cancelmethod is defined but never called in the template—the cancel button directly invokescloseHovers. Consider removing this dead code.🧹 Proposed fix
methods: { ...mapActions(useLayoutStore, ["closeHovers"]), - cancel: function () { - this.closeHovers(); - }, submit: async function () {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/prompts/Rename.vue` around lines 86 - 88, The cancel method in the Rename component is dead code: remove the unused cancel function to clean up the component; locate the cancel method definition (cancel: function () { this.closeHovers(); }) and delete it, leaving the existing closeHovers usage in the template intact so behavior is unchanged.frontend/src/views/files/Editor.vue (1)
183-192: Fragile timing withsetTimeout(50ms)for editor initialization.Using a fixed 50ms delay to wait for DOM readiness is brittle and may fail on slower devices. Consider using
nextTickor a more reliable approach.♻️ Suggested improvement using nextTick
+import { inject, onBeforeUnmount, onMounted, ref, watchEffect, nextTick } from "vue"; // ... const unwatch = watchEffect(() => { if (!layoutStore.loading) { - setTimeout(() => { + nextTick(() => { initEditor(fileContent); unwatch(); - }, 50); + }); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/views/files/Editor.vue` around lines 183 - 192, The current watchEffect uses a brittle setTimeout(50) to wait for DOM readiness before calling initEditor(fileContent) and unwatch; replace that timing hack by awaiting Vue's nextTick (or using nextTick().then()) once layoutStore.loading becomes false, then call initEditor(fileContent) and unwatch(); locate the watchEffect block and remove the setTimeout wrapper so initEditor is invoked after nextTick to ensure the DOM is ready rather than relying on a fixed delay.frontend/src/views/settings/User.vue (1)
96-97: InconsistentauthMethodsource between new and existing user paths.For new users (line 97),
isCurrentPasswordRequireduses the importedauthMethodconstant. For existing users (line 108-109), it fetchesauthMethodfromsettings.get(). This inconsistency could cause different behavior if the constant and API response diverge.♻️ Suggested fix for consistency
Use the API response for both paths:
if (isNew.value) { - const { defaults, createUserDir: _createUserDir } = await settings.get(); - isCurrentPasswordRequired.value = authMethod == "json"; + const { defaults, createUserDir: _createUserDir, authMethod: _authMethod } = await settings.get(); + isCurrentPasswordRequired.value = _authMethod == "json"; createUserDir.value = _createUserDir;And remove the unused import if no longer needed:
-import { authMethod } from "@/utils/constants";Also applies to: 108-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/views/settings/User.vue` around lines 96 - 97, The new-user branch is using the imported authMethod constant while the existing-user branch reads authMethod from settings.get(), causing inconsistent behavior; update the new-user logic to use the authMethod returned by await settings.get() (the same value used in the existing-user branch) when setting isCurrentPasswordRequired.value, replace any usage of the imported authMethod with the settings response field, and remove the now-unused authMethod import if it is no longer referenced (references: isCurrentPasswordRequired, settings.get()).http/upload_cache_redis.go (2)
42-47: Consider acceptingcontext.Contextfor cache operations.All cache methods use
context.Background(), which prevents timeout/cancellation propagation from the HTTP request context. This could cause requests to hang if Redis is slow or unresponsive.♻️ Suggested interface change
-func (c *redisUploadCache) Register(filePath string, fileSize int64) { - err := c.client.Set(context.Background(), c.filePathKey(filePath), fileSize, uploadCacheTTL).Err() +func (c *redisUploadCache) Register(ctx context.Context, filePath string, fileSize int64) { + err := c.client.Set(ctx, c.filePathKey(filePath), fileSize, uploadCacheTTL).Err()Apply similarly to
Complete,GetLength, andTouch. This would require updating theUploadCacheinterface and all call sites.Also applies to: 49-54, 56-73, 75-80
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@http/upload_cache_redis.go` around lines 42 - 47, Update the cache methods to accept a context so cancellations/timeouts propagate: change redisUploadCache.Register (and sibling methods Complete, GetLength, Touch) to take ctx context.Context and use that instead of context.Background() when calling c.client.Set/Get/Expire; update the UploadCache interface signature accordingly and adjust all call sites to pass the incoming request context (or an appropriate derived context) so Redis operations respect request cancellation and deadlines; ensure helper methods like filePathKey remain unchanged.
82-84: Close ignores potential error from Redis client.
client.Close()can return an error that is currently discarded. Consider logging it for observability.🧹 Suggested fix
func (c *redisUploadCache) Close() { - c.client.Close() + if err := c.client.Close(); err != nil { + log.Printf("failed to close redis client: %v", err) + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@http/upload_cache_redis.go` around lines 82 - 84, The Close method on redisUploadCache currently discards the error from c.client.Close(); change it to capture the returned error and log it for observability (e.g., err := c.client.Close(); if err != nil { c.logger.Errorf("redisUploadCache.Close: %v", err) } ), using the existing logger on the redisUploadCache struct (or falling back to the standard log package if no logger field exists), so the Close function logs any error instead of ignoring it.http/upload_cache_memory.go (2)
68-70: Touch silently ignores missing keys.
ttlcache.Touchreturns a boolean indicating success, but it's discarded here. If the entry was evicted or completed between ticker intervals, the caller won't know. This is likely acceptable since subsequentGetLengthcalls will fail, but consider returning a boolean or error for callers that need to know.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@http/upload_cache_memory.go` around lines 68 - 70, The Touch method on memoryUploadCache currently discards the boolean result from ttlcache.Touch, silently hiding whether the key existed; change memoryUploadCache.Touch to return the boolean (or an error) from c.cache.Touch so callers can detect a missing/evicted entry, update the memoryUploadCache.Touch signature and all call sites to handle the returned value, and ensure related callers like GetLength (or any code expecting to act when an entry is missing) use the returned boolean to decide how to proceed.
41-46: Consider logging errors fromos.Removeon eviction.The eviction callback silently discards the error from
os.Remove. If file deletion fails (e.g., permissions, file already removed), this could leave orphaned files without any indication to operators.🔧 Proposed fix to log removal errors
cache.OnEviction(func(_ context.Context, reason ttlcache.EvictionReason, item *ttlcache.Item[string, int64]) { if reason == ttlcache.EvictionReasonExpired { fmt.Printf("deleting incomplete upload file: \"%s\"\n", item.Key()) - os.Remove(item.Key()) + if err := os.Remove(item.Key()); err != nil && !os.IsNotExist(err) { + fmt.Printf("failed to delete incomplete upload file: \"%s\": %v\n", item.Key(), err) + } } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@http/upload_cache_memory.go` around lines 41 - 46, The eviction callback registered via cache.OnEviction currently ignores errors from os.Remove; update the callback in upload_cache_memory.go so that when reason == ttlcache.EvictionReasonExpired you call os.Remove(item.Key()) and check its returned error, logging any failure (including the filename from item.Key()) via the project logger or fmt.Errorf/print so operators can see if deletion failed; keep the existing printf for successful deletes and ensure the log message includes context (operation and filename).frontend/src/components/files/CsvViewer.vue (2)
112-112: Typo:isEncondingDropdownOpenshould beisEncodingDropdownOpen.The variable name has a typo (
Enconding→Encoding). This appears in multiple places.✏️ Proposed fix for typo
-const isEncondingDropdownOpen = ref(false); +const isEncodingDropdownOpen = ref(false);And update all template references accordingly.
Also applies to: 25-25, 162-162
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/files/CsvViewer.vue` at line 112, Rename the misspelled reactive variable isEncondingDropdownOpen to isEncodingDropdownOpen everywhere in CsvViewer.vue: update the ref declaration (const isEncondingDropdownOpen = ref(false) → const isEncodingDropdownOpen = ref(false)) and change all usages/references (template bindings, computed/Watcher code, .value access, event handlers) to the new name so there are no remaining references to isEncondingDropdownOpen.
134-159: Potential race condition with rapid encoding/separator changes.The
watchEffecttriggers a newparse()call on every change, but the callback-based parsing doesn't cancel previous operations. If the user rapidly changes settings, an older parse result could overwrite a newer one.♻️ Proposed fix using a generation counter
+let parseGeneration = 0; + watchEffect(() => { if (props.content !== "" && columnSeparator.value.length > 0) { + const currentGeneration = ++parseGeneration; const content = isEncodedContent.value ? decode(props.content as ArrayBuffer, selectedEncoding.value) : props.content; parse( content as string, { delimiter: columnSeparator.value, skip_empty_lines: true }, (error, output) => { + // Ignore stale results + if (currentGeneration !== parseGeneration) return; + if (error) { // ... error handling } else { // ... success handling } } ); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/files/CsvViewer.vue` around lines 134 - 159, The watchEffect launches parse(...) on every change and older parse callbacks can overwrite newer results; add a generation counter (e.g., local numeric token inside the component) that is incremented just before calling parse in the watchEffect, capture the current generation in the parse callback, and only assign to parsed.value and displayError.value when the callback's captured generation equals the latest generation; reference watchEffect, parse, parsed.value, displayError.value, isEncodedContent, selectedEncoding, and columnSeparator to locate where to increment the counter and perform the generation check so stale callbacks are ignored.frontend/src/views/files/FileListing.vue (1)
779-791: Consider extracting shared conflict resolution logic.The conflict resolution handler appears three times (paste, drop, uploadInput) with similar patterns. While there are slight variations (upload doesn't support rename), a shared helper could reduce duplication.
Also applies to: 904-917, 960-972
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/views/files/FileListing.vue` around lines 779 - 791, The conflict resolution logic duplicated in FileListing.vue (in the paste, drop, and uploadInput handlers) should be extracted into a single helper (e.g., resolveConflictItems(items, result, {allowRename: boolean}) ) that iterates result backwards, sets items[index].rename or .overwrite or splices out removed items, and returns the filtered items; update the three call sites (paste, drop, uploadInput) to call this helper with allowRename true for paste/drop and false for uploadInput, then invoke the existing action() only when the helper returns a non-empty items array, preserving the current behaviors (uploadInput must not set rename).frontend/src/components/prompts/ResolveConflict.vue (1)
22-22: Typo:toogleCheckAllshould betoggleCheckAll.The function name has a typo (
toogle→toggle). While this doesn't affect functionality, it impacts code readability and searchability.✏️ Proposed fix for typo
- `@change`="toogleCheckAll" + `@change`="toggleCheckAll"And rename the function:
-const toogleCheckAll = (e: Event) => { +const toggleCheckAll = (e: Event) => {Also applies to: 35-35, 204-224
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/prompts/ResolveConflict.vue` at line 22, Rename the misspelled method toogleCheckAll to toggleCheckAll across the component: update the template bindings (e.g., `@change`="toogleCheckAll"), the methods block where toogleCheckAll is defined, and any internal calls or emitted events that reference toogleCheckAll so they use toggleCheckAll instead; run a project search to replace all occurrences (including related references in the same component and tests) and ensure the renamed method is exported or referenced consistently so no runtime errors occur.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e81728b2-18c8-43b0-8dc6-6bec323a3b49
⛔ Files ignored due to path filters (2)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlgo.sumis excluded by!**/*.sum
📒 Files selected for processing (82)
.github/workflows/ci.yaml.goreleaser.ymlCHANGELOG.mdREADME.mdauth/hook.goauth/json.goauth/proxy.goauth/proxy_test.gocmd/config.gocmd/config_import.gocmd/root.gocmd/users.gocmd/utils.gocompose.yamlerrors/errors.gofrontend/package.jsonfrontend/src/api/files.tsfrontend/src/api/users.tsfrontend/src/components/DropdownModal.vuefrontend/src/components/files/CsvViewer.vuefrontend/src/components/files/ListingItem.vuefrontend/src/components/files/VideoPlayer.vuefrontend/src/components/prompts/BaseModal.vuefrontend/src/components/prompts/Copy.vuefrontend/src/components/prompts/CreateFilePath.vuefrontend/src/components/prompts/CurrentPassword.vuefrontend/src/components/prompts/FileList.vuefrontend/src/components/prompts/Move.vuefrontend/src/components/prompts/NewDir.vuefrontend/src/components/prompts/Prompts.vuefrontend/src/components/prompts/Rename.vuefrontend/src/components/prompts/ReplaceRename.vuefrontend/src/components/prompts/ResolveConflict.vuefrontend/src/components/prompts/Upload.vuefrontend/src/components/settings/Permissions.vuefrontend/src/css/base.cssfrontend/src/css/styles.cssfrontend/src/i18n/en.jsonfrontend/src/i18n/es_AR.jsonfrontend/src/i18n/pt_PT.jsonfrontend/src/i18n/ru_RU.jsonfrontend/src/i18n/zh_CN.jsonfrontend/src/main.tsfrontend/src/stores/layout.tsfrontend/src/types/file.d.tsfrontend/src/types/global.d.tsfrontend/src/types/hostinger.d.tsfrontend/src/types/upload.d.tsfrontend/src/utils/__tests__/upload.test.tsfrontend/src/utils/clipboard.tsfrontend/src/utils/csv.tsfrontend/src/utils/encodings.tsfrontend/src/utils/index.tsfrontend/src/utils/upload.tsfrontend/src/views/files/Editor.vuefrontend/src/views/files/FileListing.vuefrontend/src/views/files/Preview.vuefrontend/src/views/settings/User.vuefrontend/vite.config.tsgo.modhttp/auth.gohttp/http.gohttp/public.gohttp/public_test.gohttp/raw.gohttp/raw_test.gohttp/resource.gohttp/share.gohttp/static.gohttp/tus_handlers.gohttp/upload_cache_memory.gohttp/upload_cache_redis.gohttp/users.gohttp/utils.gorules/rules.gorules/rules_test.gostorage/bolt/users.gotransifex.ymlusers/storage.goversion/version.gowww/docs/authentication.mdwww/docs/cli/filebrowser.md
💤 Files with no reviewable changes (4)
- frontend/src/css/styles.css
- frontend/src/main.ts
- frontend/src/components/prompts/ReplaceRename.vue
- frontend/src/utils/csv.ts
| By: a.Fields.GetString("user.sorting.by", d.Sorting.By), | ||
| }, | ||
| Commands: a.Fields.GetArray("user.commands", d.Commands), | ||
| DateFormat: a.Fields.GetBoolean("user.dateFormat", d.DateFormat), |
There was a problem hiding this comment.
user.dateFormat hook value is currently ignored due to whitelist filtering.
Line 237 reads user.dateFormat, but GetValues drops unknown keys and validHookFields does not include this field. So hook-provided date format updates never apply.
Proposed fix
var validHookFields = []string{
"hook.action",
"user.scope",
"user.locale",
"user.viewMode",
"user.singleClick",
"user.redirectAfterCopyMove",
"user.sorting.by",
"user.sorting.asc",
"user.commands",
+ "user.dateFormat",
"user.hideDotfiles",
"user.perm.admin",
"user.perm.execute",
"user.perm.create",
"user.perm.rename",
"user.perm.modify",
"user.perm.delete",
"user.perm.share",
"user.perm.download",
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| DateFormat: a.Fields.GetBoolean("user.dateFormat", d.DateFormat), | |
| var validHookFields = []string{ | |
| "hook.action", | |
| "user.scope", | |
| "user.locale", | |
| "user.viewMode", | |
| "user.singleClick", | |
| "user.redirectAfterCopyMove", | |
| "user.sorting.by", | |
| "user.sorting.asc", | |
| "user.commands", | |
| "user.dateFormat", | |
| "user.hideDotfiles", | |
| "user.perm.admin", | |
| "user.perm.execute", | |
| "user.perm.create", | |
| "user.perm.rename", | |
| "user.perm.modify", | |
| "user.perm.delete", | |
| "user.perm.share", | |
| "user.perm.download", | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@auth/hook.go` at line 237, The hook's "user.dateFormat" value is being
dropped because the whitelist in validHookFields (used by GetValues) doesn't
include DateFormat, so a.Fields.GetBoolean("user.dateFormat", d.DateFormat)
never gets a value from hooks; update the whitelist to include the
"user.dateFormat" key (or rename to match the existing allowed key) and ensure
the value is handled in the same code path that reads other hook fields
(referencing validHookFields, GetValues, and the struct field DateFormat) so
hook-provided date format updates are accepted and applied.
| u, err := usr.Get(srv.Root, cred.Username) | ||
| if err != nil || !users.CheckPwd(cred.Password, u.Password) { | ||
|
|
||
| hash := dummyHash | ||
| if err == nil { | ||
| hash = u.Password | ||
| } | ||
|
|
||
| if !users.CheckPwd(cred.Password, hash) { | ||
| return nil, os.ErrPermission | ||
| } | ||
|
|
||
| if err != nil { | ||
| return nil, os.ErrPermission |
There was a problem hiding this comment.
Store failures are being masked as invalid credentials.
Current flow turns all usr.Get failures into os.ErrPermission. That also hides real backend failures (DB/network/store issues) as auth failures, which hurts reliability and incident visibility.
Proposed fix (keep anti-enumeration for not-found, preserve real errors)
import (
+ "errors"
"encoding/json"
"net/http"
"net/url"
"os"
"strings"
+ fberrors "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
)
@@
u, err := usr.Get(srv.Root, cred.Username)
+ if err != nil && !errors.Is(err, fberrors.ErrNotExist) {
+ return nil, err
+ }
hash := dummyHash
- if err == nil {
+ if err == nil && u != nil {
hash = u.Password
}
if !users.CheckPwd(cred.Password, hash) {
return nil, os.ErrPermission
}
- if err != nil {
+ if err != nil || u == nil {
return nil, os.ErrPermission
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| u, err := usr.Get(srv.Root, cred.Username) | |
| if err != nil || !users.CheckPwd(cred.Password, u.Password) { | |
| hash := dummyHash | |
| if err == nil { | |
| hash = u.Password | |
| } | |
| if !users.CheckPwd(cred.Password, hash) { | |
| return nil, os.ErrPermission | |
| } | |
| if err != nil { | |
| return nil, os.ErrPermission | |
| u, err := usr.Get(srv.Root, cred.Username) | |
| if err != nil && !errors.Is(err, fberrors.ErrNotExist) { | |
| return nil, err | |
| } | |
| hash := dummyHash | |
| if err == nil && u != nil { | |
| hash = u.Password | |
| } | |
| if !users.CheckPwd(cred.Password, hash) { | |
| return nil, os.ErrPermission | |
| } | |
| if err != nil || u == nil { | |
| return nil, os.ErrPermission | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@auth/json.go` around lines 58 - 70, The code currently maps every usr.Get
error to os.ErrPermission, hiding real backend failures; change the logic to
only mask "user not found" errors for anti-enumeration while returning real
errors for other failures: call usr.Get(srv.Root, cred.Username), if err == nil
use u.Password, if errors.Is(err, <usr not-found sentinel like usr.ErrNotFound
or store.ErrNotFound>) set hash = dummyHash and continue to CheckPwd, but if err
!= nil and not a not-found sentinel return the original err immediately; use
errors.Is to compare sentinel errors and keep the dummyHash/timing protection
when user is missing.
| <div id="modal-background" @click="backgroundClick"> | ||
| <div ref="modalContainer"> | ||
| <slot></slot> | ||
| </div> |
There was a problem hiding this comment.
Make the modal container focusable and announce it as a dialog.
When #focus-prompt is absent, Line 21 focuses a plain <div>, which is not keyboard-focusable, so focus can remain behind the overlay. Please give the fallback target tabindex="-1" and modal semantics such as role="dialog" / aria-modal="true".
Also applies to: 16-23
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/components/prompts/BaseModal.vue` around lines 2 - 5, The
modal's fallback focus target is a plain div (ref="modalContainer") when
`#focus-prompt` is absent, so make the container keyboard-focusable and announce
it as a dialog: add tabindex="-1", role="dialog" and aria-modal="true" to the
<div ref="modalContainer"> element and ensure the existing focus logic (the code
that focuses the fallback at the point where it currently focuses the div)
targets this ref so focus will land on the dialog when `#focus-prompt` is not
found.
| window.addEventListener("keydown", (event) => { | ||
| if (event.key === "Escape") { | ||
| event.stopImmediatePropagation(); | ||
| emit("closed"); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Tear down the global Escape handler on unmount.
Each modal instance registers a new window listener and none are removed, so reopening prompts will stack handlers and can emit closed multiple times for one keypress.
♻️ Proposed fix
-import { onMounted, ref } from "vue";
+import { onMounted, onUnmounted, ref } from "vue";
@@
-window.addEventListener("keydown", (event) => {
+const onKeydown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
event.stopImmediatePropagation();
emit("closed");
}
-});
+};
+
+onMounted(() => {
+ window.addEventListener("keydown", onKeydown);
+});
+
+onUnmounted(() => {
+ window.removeEventListener("keydown", onKeydown);
+});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/components/prompts/BaseModal.vue` around lines 32 - 37, The
Escape key listener added with window.addEventListener currently never gets
removed, causing multiple handlers to accumulate and emit("closed") multiple
times; change the implementation to register a named handler (e.g., const
onKeydown = (event) => { if (event.key === "Escape") {
event.stopImmediatePropagation(); emit("closed"); } }) and attach it in the
component lifecycle (onMounted or during setup) and remove it in onBeforeUnmount
using window.removeEventListener with the same handler reference so each
BaseModal instance cleans up its global listener on unmount.
| size: this.req.items[item].size, | ||
| modified: this.req.items[item].modified, | ||
| overwrite: false, | ||
| rename: this.$route.path === this.dest, | ||
| }); |
There was a problem hiding this comment.
Clear the opposite flag when applying a conflict choice.
Same-folder copies start with rename: true. If the user selects overwrite, this callback only flips overwrite, so the request can go out with both rename and overwrite enabled.
🛠️ Suggested fix
if (item.checked.length == 2) {
items[item.index].rename = true;
+ items[item.index].overwrite = false;
} else if (
item.checked.length == 1 &&
item.checked[0] == "origin"
) {
+ items[item.index].rename = false;
items[item.index].overwrite = true;
} else {Also applies to: 139-147
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/components/prompts/Copy.vue` around lines 96 - 100, When
applying a conflict choice in the Copy.vue callback, ensure you clear the
opposite flag so both overwrite and rename cannot be true simultaneously: when
setting overwrite to true set rename to false, and when setting rename to true
set overwrite to false. Locate where the request object is built (the properties
overwrite and rename in the callback that currently sets overwrite: false and
rename: this.$route.path === this.dest) and update the logic so choosing one
conflict action unsets the other; apply the same fix to the other occurrence
around lines 139-147.
| dst, err := url.QueryUnescape(dst) | ||
| if err != nil { | ||
| return errToStatus(err), err | ||
| dst = path.Clean("/" + dst) | ||
| src = path.Clean("/" + src) | ||
| if !d.Check(src) || !d.Check(dst) { | ||
| return http.StatusForbidden, nil | ||
| } | ||
|
|
||
| err = checkSrcDstAccess(src, dst, d) | ||
| if err != nil { | ||
| return errToStatus(err), err | ||
| } |
There was a problem hiding this comment.
Error checked after value is already used.
The QueryUnescape error (line 324) is checked at line 330, but dst is already used in path.Clean (line 325) and d.Check (line 327) before the error check. If unescaping fails, the subsequent operations use a potentially malformed path.
🐛 Proposed fix
dst, err := url.QueryUnescape(dst)
+ if err != nil {
+ return errToStatus(err), err
+ }
dst = path.Clean("/" + dst)
src = path.Clean("/" + src)
if !d.Check(src) || !d.Check(dst) {
return http.StatusForbidden, nil
}
- if err != nil {
- return errToStatus(err), err
- }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| dst, err := url.QueryUnescape(dst) | |
| if err != nil { | |
| return errToStatus(err), err | |
| dst = path.Clean("/" + dst) | |
| src = path.Clean("/" + src) | |
| if !d.Check(src) || !d.Check(dst) { | |
| return http.StatusForbidden, nil | |
| } | |
| err = checkSrcDstAccess(src, dst, d) | |
| if err != nil { | |
| return errToStatus(err), err | |
| } | |
| dst, err := url.QueryUnescape(dst) | |
| if err != nil { | |
| return errToStatus(err), err | |
| } | |
| dst = path.Clean("/" + dst) | |
| src = path.Clean("/" + src) | |
| if !d.Check(src) || !d.Check(dst) { | |
| return http.StatusForbidden, nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@http/resource.go` around lines 324 - 332, The QueryUnescape error is checked
after using the unescaped dst value; call url.QueryUnescape(dst) and immediately
check err before using dst (i.e., move the if err != nil { return
errToStatus(err), err } right after url.QueryUnescape), then perform
path.Clean("/"+dst) and path.Clean("/"+src) and the d.Check(src)/d.Check(dst)
checks; update the block around url.QueryUnescape, path.Clean, d.Check and
errToStatus accordingly so dst is never used when QueryUnescape failed.
| srcInfo, _ := d.user.Fs.Stat(src) | ||
| dstInfo, _ := d.user.Fs.Stat(dst) | ||
| same := os.SameFile(srcInfo, dstInfo) |
There was a problem hiding this comment.
Stat errors ignored before os.SameFile call.
Both Stat calls discard their errors. If either fails (e.g., file doesn't exist), srcInfo or dstInfo will be nil, causing os.SameFile to panic or return incorrect results.
🐛 Proposed fix
- srcInfo, _ := d.user.Fs.Stat(src)
- dstInfo, _ := d.user.Fs.Stat(dst)
- same := os.SameFile(srcInfo, dstInfo)
+ srcInfo, srcErr := d.user.Fs.Stat(src)
+ dstInfo, dstErr := d.user.Fs.Stat(dst)
+ same := srcErr == nil && dstErr == nil && os.SameFile(srcInfo, dstInfo)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| srcInfo, _ := d.user.Fs.Stat(src) | |
| dstInfo, _ := d.user.Fs.Stat(dst) | |
| same := os.SameFile(srcInfo, dstInfo) | |
| srcInfo, srcErr := d.user.Fs.Stat(src) | |
| dstInfo, dstErr := d.user.Fs.Stat(dst) | |
| same := srcErr == nil && dstErr == nil && os.SameFile(srcInfo, dstInfo) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@http/resource.go` around lines 341 - 343, The code calls d.user.Fs.Stat twice
and ignores both errors before passing srcInfo/dstInfo to os.SameFile, which can
panic if either is nil; update the logic around the two Stat calls (the calls to
d.user.Fs.Stat that produce srcInfo and dstInfo) to check and handle errors: if
either Stat returns an error, return or propagate that error (or handle the
"file not found" case as appropriate) instead of proceeding, and only call
os.SameFile when both srcInfo and dstInfo are non-nil and no Stat error
occurred; ensure any returned error includes context about which path (src or
dst) failed.
| f, err := assetsFs.Open(r.URL.Path + ".gz") | ||
| if err != nil { | ||
| return http.StatusNotFound, err | ||
| } |
There was a problem hiding this comment.
Add fallback to plain .js when .gz is missing.
Line 155 returns 404 immediately if the precompressed file is unavailable. That makes static JS serving fragile in deployments where .gz artifacts are absent but plain assets exist.
🔧 Proposed fix
f, err := assetsFs.Open(r.URL.Path + ".gz")
if err != nil {
- return http.StatusNotFound, err
+ // Fallback to plain asset when precompressed variant is unavailable.
+ http.FileServer(http.FS(assetsFs)).ServeHTTP(w, r)
+ return 0, nil
}
defer f.Close()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@http/static.go` around lines 153 - 156, The handler currently opens
assetsFs.Open(r.URL.Path + ".gz") and immediately returns http.StatusNotFound on
any error; change this to try a fallback to the plain asset: if
assetsFs.Open(r.URL.Path + ".gz") fails with a not-found error, attempt
assetsFs.Open(r.URL.Path) instead, serve the plain file when present, and only
set the Content-Encoding: gzip header when the gzip file is actually used;
ensure you still return the original error (or 404) only if both opens fail
(referencing assetsFs.Open, r.URL.Path + ".gz", r.URL.Path and the code path
that currently returns http.StatusNotFound, err).
| acceptEncoding := r.Header.Get("Accept-Encoding") | ||
| if strings.Contains(acceptEncoding, "gzip") { | ||
| w.Header().Set("Content-Encoding", "gzip") | ||
| w.Header().Set("Content-Type", "application/javascript; charset=utf-8") |
There was a problem hiding this comment.
Fix content-negotiation correctness and cache safety.
On Line 160, strings.Contains(acceptEncoding, "gzip") can wrongly select gzip for headers like gzip;q=0. Also, negotiated responses should set Vary: Accept-Encoding (Line 161 path) to prevent cache mix-ups.
🔧 Proposed fix
- acceptEncoding := r.Header.Get("Accept-Encoding")
- if strings.Contains(acceptEncoding, "gzip") {
+ acceptEncoding := strings.ToLower(r.Header.Get("Accept-Encoding"))
+ w.Header().Add("Vary", "Accept-Encoding")
+
+ supportsGzip := false
+ for _, part := range strings.Split(acceptEncoding, ",") {
+ token := strings.TrimSpace(part)
+ if strings.HasPrefix(token, "gzip") && !strings.Contains(token, "q=0") && !strings.Contains(token, "q=0.0") {
+ supportsGzip = true
+ break
+ }
+ }
+
+ if supportsGzip {
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| acceptEncoding := r.Header.Get("Accept-Encoding") | |
| if strings.Contains(acceptEncoding, "gzip") { | |
| w.Header().Set("Content-Encoding", "gzip") | |
| w.Header().Set("Content-Type", "application/javascript; charset=utf-8") | |
| acceptEncoding := strings.ToLower(r.Header.Get("Accept-Encoding")) | |
| w.Header().Add("Vary", "Accept-Encoding") | |
| supportsGzip := false | |
| for _, part := range strings.Split(acceptEncoding, ",") { | |
| token := strings.TrimSpace(part) | |
| if strings.HasPrefix(token, "gzip") && !strings.Contains(token, "q=0") && !strings.Contains(token, "q=0.0") { | |
| supportsGzip = true | |
| break | |
| } | |
| } | |
| if supportsGzip { | |
| w.Header().Set("Content-Encoding", "gzip") | |
| w.Header().Set("Content-Type", "application/javascript; charset=utf-8") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@http/static.go` around lines 159 - 162, The Accept-Encoding check using
strings.Contains may misdetect encodings like "gzip;q=0" and you must also mark
responses with Vary: Accept-Encoding to avoid caching problems; update the logic
in the handler that reads acceptEncoding to properly parse the comma-separated
tokens (split on ',' then for each token trim space and split on ';' to read
optional q value) and only treat gzip as acceptable when the token name equals
"gzip" and its q value is not "0"; when gzip is accepted set Content-Encoding:
gzip and Content-Type as before and ensure you set Vary: Accept-Encoding (e.g.,
w.Header().Add("Vary", "Accept-Encoding")) so caches differentiate encoded and
unencoded responses.
| for _, v := c.First(); v != nil; _, v = c.Next() { | ||
| var u users.User | ||
| if err := st.db.Codec().Unmarshal(v, &u); err != nil { | ||
| return err | ||
| } | ||
| if u.Perm.Admin { | ||
| count++ | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In go.etcd.io/bbolt, when iterating a bucket with Cursor.First()/Next(), can the returned value be nil for nested bucket keys, and should loops terminate on key==nil rather than value==nil?
💡 Result:
Yes, when iterating a bucket with Cursor.First or Next, the returned value can be nil for nested bucket keys (while the key is non-nil). Loops should terminate on key == nil, not value == nil, to properly handle both end-of-iteration (key==nil, value==nil) and nested buckets (key!=nil, value==nil).
Citations:
- 1: https://github.com/etcd-io/bbolt/blob/main/cursor.go
- 2: https://github.com/etcd-io/bbolt/blob/v1.4.3/cursor.go
- 3: https://github.com/etcd-io/bbolt/blob/v1.3.3/cursor.go
- 4: https://pkg.go.dev/go.etcd.io/bbolt
- 5: https://github.com/etcd-io/bbolt
- 6: https://github.com/etcd-io/bbolt/blob/main/cursor_test.go
- 7: https://github.com/boltdb/bolt/blob/master/cursor.go
🏁 Script executed:
git ls-files storage/bolt/users.goRepository: hostinger/filebrowser
Length of output: 86
🏁 Script executed:
sed -n '100,125p' storage/bolt/users.goRepository: hostinger/filebrowser
Length of output: 478
Cursor loop may stop early and undercount admins.
At Line 108, iterating while v != nil can end the scan prematurely if the cursor encounters a nested bucket (where the key is non-nil but the value is nil). In bbolt, loops should terminate on key == nil to properly iterate all entries. Iterate on key instead and skip nil values.
💡 Proposed fix
- c := bucket.Cursor()
- for _, v := c.First(); v != nil; _, v = c.Next() {
+ c := bucket.Cursor()
+ for k, v := c.First(); k != nil; k, v = c.Next() {
+ if v == nil {
+ continue
+ }
var u users.User
if err := st.db.Codec().Unmarshal(v, &u); err != nil {
return err
}
if u.Perm.Admin {
count++
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for _, v := c.First(); v != nil; _, v = c.Next() { | |
| var u users.User | |
| if err := st.db.Codec().Unmarshal(v, &u); err != nil { | |
| return err | |
| } | |
| if u.Perm.Admin { | |
| count++ | |
| } | |
| } | |
| for k, v := c.First(); k != nil; k, v = c.Next() { | |
| if v == nil { | |
| continue | |
| } | |
| var u users.User | |
| if err := st.db.Codec().Unmarshal(v, &u); err != nil { | |
| return err | |
| } | |
| if u.Perm.Admin { | |
| count++ | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/bolt/users.go` around lines 108 - 116, The cursor loop in
storage/bolt/users.go currently iterates on value (`for _, v := c.First(); v !=
nil; _, v = c.Next()`) which can stop early when the cursor encounters a nested
bucket (non-nil key, nil value); change the loop to iterate on key instead
(e.g., `k, v := c.First(); k != nil; k, v = c.Next()`) and inside the loop skip
entries where `v == nil` before calling `st.db.Codec().Unmarshal(v, &u)` and
counting `u.Perm.Admin`, keeping the rest of the logic (variable `u`,
`st.db.Codec().Unmarshal`, and `count++`) the same.
There was a problem hiding this comment.
Actionable comments posted: 3
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (23)
frontend/src/i18n/en_GB.json-159-159 (1)
159-159:⚠️ Potential issue | 🟡 MinorFix grammar in conflict prompt text.
Line 159 is grammatically incorrect and reads awkwardly in UI.
✏️ Proposed text fix
- "fastConflictResolve": "The destination folder there are {count} files with same name.", + "fastConflictResolve": "There are {count} files with the same name in the destination folder.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/en_GB.json` at line 159, Update the JSON value for the key "fastConflictResolve" to correct the grammar and clarity: replace the current awkward sentence with a grammatically correct message such as "There are {count} files in the destination folder with the same name." so the UI prompt reads naturally; locate the "fastConflictResolve" entry in en_GB.json and update its string value accordingly.frontend/src/i18n/fr_FR.json-144-144 (1)
144-144:⚠️ Potential issue | 🟡 MinorUntranslated prompt message in French locale (Line 144).
"currentPasswordMessage"is in English and should be translated for the French bundle.Proposed fix
- "currentPasswordMessage": "Enter your password to validate this action.", + "currentPasswordMessage": "Saisissez votre mot de passe pour valider cette action.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/fr_FR.json` at line 144, The French locale JSON contains an English value for the key "currentPasswordMessage"; update the value of "currentPasswordMessage" in frontend/src/i18n/fr_FR.json to a proper French translation (e.g., "Entrez votre mot de passe pour valider cette action.") so the French bundle is fully localized.frontend/src/i18n/fr_FR.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 MinorUntranslated string in French locale (Line 90).
"fileEncoding"is still in English, which breaks localization consistency forfr_FR.Proposed fix
- "fileEncoding": "File Encoding", + "fileEncoding": "Encodage du fichier",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/fr_FR.json` at line 90, Update the fr_FR.json locale entry for the "fileEncoding" key so it's translated to French (replace the English value "File Encoding" with a French string, e.g. "Encodage du fichier"), preserving the JSON key, quotes and trailing comma to maintain valid JSON.frontend/src/i18n/ar_AR.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 MinorTranslate the newly added Arabic locale keys that are still in English.
files.fileEncodingandprompts.currentPasswordMessageare not localized, causing mixed-language UI in Arabic.Suggested fix
- "fileEncoding": "File Encoding", + "fileEncoding": "ترميز الملف", ... - "currentPasswordMessage": "Enter your password to validate this action.", + "currentPasswordMessage": "أدخل كلمة المرور لتأكيد هذا الإجراء.",Also applies to: 144-144
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/ar_AR.json` at line 90, Update the Arabic locale JSON to translate the remaining English keys: replace the value for "files.fileEncoding" and for "prompts.currentPasswordMessage" in the ar_AR locale with appropriate Arabic translations (ensure wording matches existing locale style and encoding), and verify the same two keys are translated at the other occurrence mentioned so both places use the localized strings instead of English.frontend/src/i18n/ar_AR.json-5-5 (1)
5-5:⚠️ Potential issue | 🟡 MinorUse an action verb for
buttons.clear.
"واضح"is an adjective; for a button label this should be an imperative verb.Suggested fix
- "clear": "واضح", + "clear": "مسح",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/ar_AR.json` at line 5, The translation for the buttons.clear key uses an adjective ("واضح"); replace it with an imperative action verb appropriate for a button (e.g., "مسح" or "امسح") so the label reads as a clear action; update the "clear" entry under the buttons namespace in ar_AR.json (buttons.clear) to the chosen imperative verb and ensure quotation/JSON formatting remains valid.frontend/src/i18n/ar_AR.json-190-190 (1)
190-190:⚠️ Potential issue | 🟡 Minor
prompts.resolutionis mistranslated for conflict context.Using
"دقة"reads as image/screen resolution, not conflict resolution. This should reflect “resolve/decision” semantics.Suggested fix
- "resolution": "دقة", + "resolution": "حلّ التعارض",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/ar_AR.json` at line 190, The key prompts.resolution currently maps to "دقة" (which means image/screen resolution) and should be changed to reflect conflict/decision semantics; update the value for prompts.resolution to an Arabic term like "حل" or "تسوية" (e.g., set prompts.resolution to "حل") so it conveys "resolve/decision" rather than technical resolution.frontend/src/i18n/zh_CN.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 MinorTranslate remaining English strings in zh_CN entries.
Line 90 and Line 144 are still English, and Line 143 can be more precise for “current password”.
🌐 Suggested localization fix
- "fileEncoding": "File Encoding", + "fileEncoding": "文件编码", ... - "currentPassword": "您的密码", - "currentPasswordMessage": "Enter your password to validate this action.", + "currentPassword": "当前密码", + "currentPasswordMessage": "请输入您的密码以确认此操作。",Also applies to: 143-144
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/zh_CN.json` at line 90, Translate the remaining English strings in zh_CN.json: replace the "fileEncoding" value with its Chinese translation, update the entry currently labeled as the English phrase for "current password" to a more precise Chinese equivalent for "current password" (locate the key named like "currentPassword"), and translate the following entry on the next line (line 144) into Chinese as well; ensure you modify the values only in frontend/src/i18n/zh_CN.json so keys remain unchanged.frontend/src/i18n/zh_CN.json-165-165 (1)
165-165:⚠️ Potential issue | 🟡 MinorUse a more natural translation for forbidden error text.
Line 165 (
"禁止错误") reads awkwardly in Chinese UI. Prefer a user-facing permission message.💬 Suggested wording
- "forbiddenError": "禁止错误", + "forbiddenError": "权限不足",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/zh_CN.json` at line 165, The "forbiddenError" translation value is awkward ("禁止错误"); update the JSON entry for the key "forbiddenError" to a natural, user-facing Chinese permission message such as "权限不足" or "无访问权限" so the UI reads correctly; locate the "forbiddenError" key in the zh_CN.json and replace the string value accordingly.frontend/src/i18n/zh_CN.json-210-210 (1)
210-210:⚠️ Potential issue | 🟡 MinorKeep zh_CN terminology in Simplified Chinese.
Line 210 uses Traditional Chinese wording (
上载档案), which is inconsistent with the rest of this locale.🈶 Suggested wording
- "uploadingFiles": "上载档案", + "uploadingFiles": "上传文件",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/zh_CN.json` at line 210, The value for the localization key "uploadingFiles" uses Traditional Chinese ("上载档案"); update the JSON entry for "uploadingFiles" in zh_CN.json to use Simplified Chinese wording (e.g., "上传文件") to match the rest of the zh_CN locale.frontend/src/i18n/es_ES.json-165-165 (1)
165-165:⚠️ Potential issue | 🟡 MinorImprove natural Spanish for forbidden errors.
"Error prohibido"sounds literal/unnatural in UI. Prefer a clearer phrasing like"Acceso denegado".Proposed fix
- "forbiddenError": "Error prohibido", + "forbiddenError": "Acceso denegado",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_ES.json` at line 165, Replace the literal translation for the "forbiddenError" key with a more natural Spanish UI message; locate the "forbiddenError" entry in the i18n JSON (key: forbiddenError) and change its value from "Error prohibido" to "Acceso denegado".frontend/src/i18n/es_ES.json-197-197 (1)
197-197:⚠️ Potential issue | 🟡 MinorUse consistent terminology for skip actions.
"skip": "Saltar"is inconsistent with the rest of the conflict flow wording ("Omitir"/"Omitir todos...").Proposed fix
- "skip": "Saltar", + "skip": "Omitir",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_ES.json` at line 197, The translation key "skip" currently has the value "Saltar" which is inconsistent with the rest of the conflict flow; update the "skip" value to use the same terminology as the other conflict strings (e.g., change "skip" from "Saltar" to "Omitir" so it matches "Omitir" / "Omitir todos..." and maintains consistent UX wording).frontend/src/i18n/uk_UA.json-16-16 (1)
16-16:⚠️ Potential issue | 🟡 MinorFix mixed-language button labels in Ukrainian locale.
Line 16 (
"Отменить") and Line 37 ("Превью") are Russian, not Ukrainian.Suggested patch
- "discardChanges": "Отменить", + "discardChanges": "Скасувати зміни", ... - "preview": "Превью", + "preview": "Попередній перегляд",Also applies to: 37-37
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/uk_UA.json` at line 16, The Ukrainian locale file contains Russian labels: replace the Russian strings for the keys "discardChanges" (currently "Отменить") and the preview key (currently "Превью") with correct Ukrainian translations (e.g., "Скасувати" for discardChanges and "Попередній перегляд" for preview); update the values in frontend/src/i18n/uk_UA.json for the "discardChanges" key and the preview-related key so they are proper Ukrainian strings and keep JSON formatting intact.frontend/src/i18n/uk_UA.json-144-144 (1)
144-144:⚠️ Potential issue | 🟡 MinorTranslate password confirmation prompt message to Ukrainian.
Line 144 is English and appears in a sensitive-action flow.
Suggested patch
- "currentPasswordMessage": "Enter your password to validate this action.", + "currentPasswordMessage": "Введіть пароль, щоб підтвердити цю дію.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/uk_UA.json` at line 144, Replace the English value for the JSON key "currentPasswordMessage" with the Ukrainian translation; update the value from "Enter your password to validate this action." to "Введіть свій пароль, щоб підтвердити цю дію." so the sensitive-action flow shows the correct localized prompt in uk_UA.json for the currentPasswordMessage key.frontend/src/i18n/uk_UA.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 MinorLocalize
files.fileEncodingforuk_UA.Line 90 is still English (
"File Encoding"), which creates inconsistent UI language.Suggested patch
- "fileEncoding": "File Encoding", + "fileEncoding": "Кодування файлу",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/uk_UA.json` at line 90, Replace the English value for the localization key files.fileEncoding in uk_UA.json with the Ukrainian translation; open uk_UA.json, find the "fileEncoding" entry under the files namespace and change "File Encoding" to the appropriate Ukrainian string (e.g., "Кодування файлу"), preserving valid JSON syntax and surrounding punctuation.frontend/src/i18n/uk_UA.json-190-190 (1)
190-190:⚠️ Potential issue | 🟡 MinorCorrect semantic translation of conflict “resolution”.
Line 190 (
"Роздільна здатність") means screen resolution, not conflict decision/resolution.Suggested patch
- "resolution": "Роздільна здатність", + "resolution": "Вирішення",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/uk_UA.json` at line 190, The translation for the JSON key "resolution" is currently "Роздільна здатність" (screen resolution); update the value for the "resolution" key to reflect conflict/decision resolution—for example replace it with "Вирішення" or "Вирішення конфлікту" so the semantics match the intended meaning in the UI.frontend/src/i18n/pt_BR.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 MinorTranslate new pt-BR entries that are still in English.
Line 90 (
files.fileEncoding) and Line 144 (prompts.currentPasswordMessage) are untranslated and will break language consistency in UI prompts.Suggested text updates
- "fileEncoding": "File Encoding", + "fileEncoding": "Codificação do arquivo", ... - "currentPasswordMessage": "Enter your password to validate this action.", + "currentPasswordMessage": "Digite sua senha para validar esta ação.",Also applies to: 144-144
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/pt_BR.json` at line 90, The keys files.fileEncoding and prompts.currentPasswordMessage are still English in the pt-BR translation; replace their values with correct Brazilian Portuguese strings (e.g., "Codificação de arquivo" for files.fileEncoding and an appropriate Portuguese prompt for prompts.currentPasswordMessage such as "Por favor, insira sua senha atual") by updating the JSON values for those keys so the UI is consistent.frontend/src/i18n/pt_PT.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 MinorLocalize remaining placeholders and normalize pt-PT wording.
Line 90 (
files.fileEncoding) and Line 144 (prompts.currentPasswordMessage) are still English.
Also, Line 143 (senha) and Line 210 (Carregando) are pt-BR forms in a pt-PT file.Suggested text updates
- "fileEncoding": "File Encoding", + "fileEncoding": "Codificação do ficheiro", ... - "currentPassword": "A sua senha", - "currentPasswordMessage": "Enter your password to validate this action.", + "currentPassword": "A sua palavra-passe", + "currentPasswordMessage": "Introduza a sua palavra-passe para validar esta ação.", ... - "uploadingFiles": "Carregando os ficheiros", + "uploadingFiles": "A carregar ficheiros",Also applies to: 143-144, 210-210
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/pt_PT.json` at line 90, Update the pt_PT JSON to localize and normalize PT‑PT wording: change the value for the key files.fileEncoding from English to the Portuguese (Portugal) translation, update prompts.currentPasswordMessage to a PT‑PT phrasing, replace any pt‑BR "senha" values with the PT‑PT term "palavra-passe" (or the agreed PT‑PT label used across the file), and change the loading text "Carregando" to the PT‑PT form ("A carregar" or the project’s chosen PT‑PT variant). Ensure you update the keys files.fileEncoding, prompts.currentPasswordMessage, any "senha" key occurrences, and the loading key to keep wording consistent across the file.frontend/src/i18n/lt_LT.json-90-90 (1)
90-90:⚠️ Potential issue | 🟡 Minor
fileEncodingis not localized in the Lithuanian locale.This will leak English text in the LT UI.
Suggested fix
- "fileEncoding": "File Encoding", + "fileEncoding": "Failo koduotė",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/lt_LT.json` at line 90, The key "fileEncoding" in the lt_LT.json locale is still English; open lt_LT.json, locate the "fileEncoding" entry and replace its value "File Encoding" with the Lithuanian translation (suggested: "Failo koduotė"), preserving JSON quoting and trailing comma, then run the i18n/l10n validation or build to ensure the locale compiles correctly.frontend/src/i18n/lt_LT.json-143-144 (1)
143-144:⚠️ Potential issue | 🟡 MinorPassword confirmation strings are partially untranslated and semantically mismatched.
Line 144 is still English, and Line 143 doesn’t convey “current password” as the key name implies.
Suggested fix
- "currentPassword": "Jūsų slaptažodis", - "currentPasswordMessage": "Enter your password to validate this action.", + "currentPassword": "Jūsų dabartinis slaptažodis", + "currentPasswordMessage": "Įveskite slaptažodį, kad patvirtintumėte šį veiksmą.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/lt_LT.json` around lines 143 - 144, Update the Lithuanian translations for the keys currentPassword and currentPasswordMessage: replace the value for "currentPassword" with a proper Lithuanian equivalent like "Dabartinis slaptažodis" and translate "currentPasswordMessage" to Lithuanian, e.g. "Įveskite savo slaptažodį, kad patvirtintumėte šį veiksmą."; ensure both keys (currentPassword, currentPasswordMessage) are updated in frontend/src/i18n/lt_LT.json and keep JSON syntax valid (quotes and trailing commas).frontend/src/i18n/lt_LT.json-16-16 (1)
16-16:⚠️ Potential issue | 🟡 Minor
discardChangesis mistranslated as delete.
"Ištrinti"means “Delete”, not “Discard changes”, and can mislead users in non-destructive editor flows.Suggested fix
- "discardChanges": "Ištrinti", + "discardChanges": "Atmesti pakeitimus",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/lt_LT.json` at line 16, The translation for the key "discardChanges" is incorrect ("Ištrinti" means "Delete"); update the value for the "discardChanges" key in lt_LT.json to a non-destructive Lithuanian phrase such as "Atsisakyti pakeitimų" (or "Atmesti pakeitimus") so the UI conveys "Discard changes" rather than "Delete". Ensure you only change the string value for the "discardChanges" key.frontend/src/i18n/es_CO.json-344-344 (1)
344-344:⚠️ Potential issue | 🟡 MinorAbort confirmation text should be neutral and consistent in tone.
Line 344 says
"¿Está segura...?"(gendered/formal feminine). This is inconsistent with the rest of the file and excludes other users.💡 Suggested fix
- "abortUpload": "¿Está segura de que desea abortar?" + "abortUpload": "¿Seguro que deseas cancelar la carga?"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_CO.json` at line 344, The abort confirmation is gendered ("¿Está segura de que desea abortar?") and should be neutral and consistent; update the "abortUpload" i18n value to a gender-neutral, clear phrase (for example "¿Desea abortar la carga?" or "¿Desea cancelar la carga?") so it matches the rest of the file's tone and avoids excluding users—change the value associated with the "abortUpload" key accordingly.frontend/src/i18n/es_MX.json-188-188 (1)
188-188:⚠️ Potential issue | 🟡 MinorCorrect typo in conflict message text.
Line 188 has
"ue intentas"instead of"que intentas".💡 Suggested fix
- "replaceMessage": "Uno de los archivos ue intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n", + "replaceMessage": "Uno de los archivos que intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_MX.json` at line 188, Fix the typo in the Spanish localization string with key "replaceMessage": change "ue intentas" to "que intentas" so the value reads "Uno de los archivos que intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n"; update the string associated with "replaceMessage" accordingly.frontend/src/i18n/es_MX.json-344-344 (1)
344-344:⚠️ Potential issue | 🟡 MinorAvoid gendered phrasing in confirmation prompt.
Line 344 uses
"¿Está segura...?", which is gendered. A neutral phrasing is safer for shared UI copy.💡 Suggested fix
- "abortUpload": "¿Está segura de que desea abortar?" + "abortUpload": "¿Desea abortar la carga?"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_MX.json` at line 344, The key "abortUpload" currently uses gendered phrasing ("¿Está segura de que desea abortar?"); change its value to a gender-neutral Spanish prompt such as "¿Desea abortar la carga?" (or another neutral option like "¿Está seguro/a de que desea abortar?") by updating the "abortUpload" string in the es_MX.json so the UI copy avoids gendered language.
🧹 Nitpick comments (8)
frontend/src/i18n/en_GB.json (1)
34-34: Consider using consistent action terminology for conflict resolution.Line 34 uses “Replace” while Line 180 uses “Overwrite” for closely related actions. Aligning one term across the flow would reduce cognitive friction.
Also applies to: 180-180
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/en_GB.json` at line 34, The label "overrideAll" currently uses the verb "Replace" while a related label at line 180 uses "Overwrite"; make the action terminology consistent across the flow by choosing one verb and updating the JSON value for "overrideAll" (and the counterpart at line 180 where the value reads "Overwrite") so both strings use the same term (either "Replace" or "Overwrite") and match other related keys in this file.frontend/src/i18n/fr_FR.json (1)
159-159: Improve French grammar for conflict prompt (Line 159).
"contient {count} des fichiers"is awkward French. A cleaner phrasing improves UX polish.Suggested wording
- "fastConflictResolve": "Le dossier de destination contient {count} des fichiers portant le même nom.", + "fastConflictResolve": "Le dossier de destination contient {count} fichiers portant le même nom.",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/fr_FR.json` at line 159, Update the translation value for the key "fastConflictResolve": replace the awkward phrase "Le dossier de destination contient {count} des fichiers portant le même nom." with a cleaner French string such as "Le dossier de destination contient {count} fichier(s) portant le même nom." (or another grammatically correct variant like "Le dossier de destination contient {count} fichiers portant le même nom."). Ensure the change is applied to the "fastConflictResolve" entry in frontend/src/i18n/fr_FR.json.frontend/src/i18n/es_ES.json (1)
143-144: Normalize pronoun/register and avoid gendered confirmation copy.Current strings mix
tu/suand include feminine-only"segura". Consider one consistent neutral style across prompts/settings.Example normalization (informal “tú”)
- "currentPassword": "Tu contraseña", + "currentPassword": "Tu contraseña actual", ... - "currentPassword": "Su contraseña actual", + "currentPassword": "Tu contraseña actual", ... - "abortUpload": "¿Está segura de que desea abortar?" + "abortUpload": "¿Seguro que deseas cancelar la subida?"Also applies to: 242-242, 344-344
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_ES.json` around lines 143 - 144, Normalize the copy to a consistent informal "tú" register and remove gendered wording: update the value for "currentPassword" and "currentPasswordMessage" to use "tu" consistently and replace any gendered adjective (e.g., "segura") with a neutral phrasing such as "confirmar" or "para validar esta acción" reworded to "para confirmar esta acción"; apply the same normalization to the other translation keys flagged in the review (the two additional keys mentioned) so all prompts/settings use the same neutral, informal tone.frontend/src/i18n/pt_BR.json (1)
54-54: Consider a more natural pt-BR label for stopping search.Line 54 (
Pare de procurar) is understandable, butParar busca/Interromper buscausually reads more naturally in pt-BR UI copy.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/pt_BR.json` at line 54, The current pt-BR string for the "stopSearch" key ("Pare de procurar") is a bit awkward; update the value for the "stopSearch" JSON key in frontend translations to a more natural UI copy such as "Parar busca" or "Interromper busca" (choose one and keep consistent with other labels), ensuring you preserve valid JSON quoting and comma placement around the "stopSearch" entry.frontend/src/i18n/pt_PT.json (1)
180-180: Prefer a clearer overwrite term for conflict outcomes.Line 180 uses
Reescrever, which can read as “rewrite text.” In the conflict flow (frontend/src/components/prompts/ResolveConflict.vue, overwrite branch),SobrescreverorSubstituiris less ambiguous.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/pt_PT.json` at line 180, The translation for the "override" key currently uses "Reescrever" which can be ambiguous; update the value of the "override" key in frontend/src/i18n/pt_PT.json to a clearer term used in the ResolveConflict component's overwrite branch (e.g., "Sobrescrever" or "Substituir") so the conflict UI in ResolveConflict.vue shows an unambiguous action label.frontend/src/i18n/es_CO.json (1)
53-53: Use consistent terminology for skip actions.Line 53 uses
"Omitir"while Line 197 uses"Saltar"for the same conflict-resolution concept. Keep one verb to avoid confusion.💡 Suggested fix
- "skip": "Saltar", + "skip": "Omitir",Also applies to: 197-197
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_CO.json` at line 53, The translations use inconsistent verbs for the skip action (e.g., the "skipAll" key uses "Omitir" while another skip-related entry uses "Saltar"); standardize all skip-related keys to a single verb across the file by choosing one term ("Omitir" or "Saltar") and updating every skip-related JSON key value (for example "skipAll" and the other skip* entries) to use that same verb so terminology is consistent.frontend/src/i18n/es_MX.json (2)
165-165: Prefer clearer wording for forbidden error.Line 165 (
"Error prohibido") sounds unnatural in Spanish UI."Acceso prohibido"is clearer.💡 Suggested fix
- "forbiddenError": "Error prohibido", + "forbiddenError": "Acceso prohibido",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_MX.json` at line 165, Update the Spanish locale string for the "forbiddenError" key to use clearer UI phrasing: replace the current value "Error prohibido" with "Acceso prohibido" in the es_MX.json entry for "forbiddenError" so the message reads naturally in Spanish.
197-197: Use consistent verb for skip actions in conflict resolution.Line 197 uses
"Saltar"while Line 53 already uses"Omitir"(skipAll). Keeping both as"Omitir"avoids mixed semantics in the same flow.💡 Suggested fix
- "skip": "Saltar", + "skip": "Omitir",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/i18n/es_MX.json` at line 197, The "skip" translation key uses "Saltar" which conflicts with the existing "skipAll" translation "Omitir"; update the "skip" value to "Omitir" so both skip-related keys ("skip" and "skipAll") use the same verb and maintain consistent semantics in the conflict resolution flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/i18n/es_CO.json`:
- Line 180: The translation key "override" is incorrect and may imply
exaggeration; update the value for the "override" JSON key to a clear
destructive-action phrasing such as "Sobrescribir" (or "Reemplazar") so users
understand it will overwrite files—locate the "override" entry and replace
"Exagerar" with "Sobrescribir" (or "Reemplazar") in
frontend/src/i18n/es_CO.json.
In `@frontend/src/i18n/es_ES.json`:
- Line 180: The translation for the conflict action key "override" is incorrect;
update the JSON entry for the "override" key from "Exagerar" to a correct
Spanish term like "Sobrescribir" (or "Reemplazar" if that matches product
terminology) so the label properly conveys file replacement in destructive
flows; also scan other i18n keys for consistency with the chosen term.
In `@frontend/src/i18n/es_MX.json`:
- Line 180: The translation for the JSON key "override" in es_MX.json is
incorrect ("Exagerar"); update the value to the correct Spanish term for a
destructive file action (e.g., "Sobrescribir" or "Reemplazar") so the "override"
key maps to the intended meaning; edit the "override" entry in the JSON to the
chosen correct string and keep JSON formatting unchanged.
---
Minor comments:
In `@frontend/src/i18n/ar_AR.json`:
- Line 90: Update the Arabic locale JSON to translate the remaining English
keys: replace the value for "files.fileEncoding" and for
"prompts.currentPasswordMessage" in the ar_AR locale with appropriate Arabic
translations (ensure wording matches existing locale style and encoding), and
verify the same two keys are translated at the other occurrence mentioned so
both places use the localized strings instead of English.
- Line 5: The translation for the buttons.clear key uses an adjective ("واضح");
replace it with an imperative action verb appropriate for a button (e.g., "مسح"
or "امسح") so the label reads as a clear action; update the "clear" entry under
the buttons namespace in ar_AR.json (buttons.clear) to the chosen imperative
verb and ensure quotation/JSON formatting remains valid.
- Line 190: The key prompts.resolution currently maps to "دقة" (which means
image/screen resolution) and should be changed to reflect conflict/decision
semantics; update the value for prompts.resolution to an Arabic term like "حل"
or "تسوية" (e.g., set prompts.resolution to "حل") so it conveys
"resolve/decision" rather than technical resolution.
In `@frontend/src/i18n/en_GB.json`:
- Line 159: Update the JSON value for the key "fastConflictResolve" to correct
the grammar and clarity: replace the current awkward sentence with a
grammatically correct message such as "There are {count} files in the
destination folder with the same name." so the UI prompt reads naturally; locate
the "fastConflictResolve" entry in en_GB.json and update its string value
accordingly.
In `@frontend/src/i18n/es_CO.json`:
- Line 344: The abort confirmation is gendered ("¿Está segura de que desea
abortar?") and should be neutral and consistent; update the "abortUpload" i18n
value to a gender-neutral, clear phrase (for example "¿Desea abortar la carga?"
or "¿Desea cancelar la carga?") so it matches the rest of the file's tone and
avoids excluding users—change the value associated with the "abortUpload" key
accordingly.
In `@frontend/src/i18n/es_ES.json`:
- Line 165: Replace the literal translation for the "forbiddenError" key with a
more natural Spanish UI message; locate the "forbiddenError" entry in the i18n
JSON (key: forbiddenError) and change its value from "Error prohibido" to
"Acceso denegado".
- Line 197: The translation key "skip" currently has the value "Saltar" which is
inconsistent with the rest of the conflict flow; update the "skip" value to use
the same terminology as the other conflict strings (e.g., change "skip" from
"Saltar" to "Omitir" so it matches "Omitir" / "Omitir todos..." and maintains
consistent UX wording).
In `@frontend/src/i18n/es_MX.json`:
- Line 188: Fix the typo in the Spanish localization string with key
"replaceMessage": change "ue intentas" to "que intentas" so the value reads "Uno
de los archivos que intentas subir está creando conflicto por su nombre.
¿Quieres cambiar el nombre del ya existente?\n"; update the string associated
with "replaceMessage" accordingly.
- Line 344: The key "abortUpload" currently uses gendered phrasing ("¿Está
segura de que desea abortar?"); change its value to a gender-neutral Spanish
prompt such as "¿Desea abortar la carga?" (or another neutral option like "¿Está
seguro/a de que desea abortar?") by updating the "abortUpload" string in the
es_MX.json so the UI copy avoids gendered language.
In `@frontend/src/i18n/fr_FR.json`:
- Line 144: The French locale JSON contains an English value for the key
"currentPasswordMessage"; update the value of "currentPasswordMessage" in
frontend/src/i18n/fr_FR.json to a proper French translation (e.g., "Entrez votre
mot de passe pour valider cette action.") so the French bundle is fully
localized.
- Line 90: Update the fr_FR.json locale entry for the "fileEncoding" key so it's
translated to French (replace the English value "File Encoding" with a French
string, e.g. "Encodage du fichier"), preserving the JSON key, quotes and
trailing comma to maintain valid JSON.
In `@frontend/src/i18n/lt_LT.json`:
- Line 90: The key "fileEncoding" in the lt_LT.json locale is still English;
open lt_LT.json, locate the "fileEncoding" entry and replace its value "File
Encoding" with the Lithuanian translation (suggested: "Failo koduotė"),
preserving JSON quoting and trailing comma, then run the i18n/l10n validation or
build to ensure the locale compiles correctly.
- Around line 143-144: Update the Lithuanian translations for the keys
currentPassword and currentPasswordMessage: replace the value for
"currentPassword" with a proper Lithuanian equivalent like "Dabartinis
slaptažodis" and translate "currentPasswordMessage" to Lithuanian, e.g.
"Įveskite savo slaptažodį, kad patvirtintumėte šį veiksmą."; ensure both keys
(currentPassword, currentPasswordMessage) are updated in
frontend/src/i18n/lt_LT.json and keep JSON syntax valid (quotes and trailing
commas).
- Line 16: The translation for the key "discardChanges" is incorrect ("Ištrinti"
means "Delete"); update the value for the "discardChanges" key in lt_LT.json to
a non-destructive Lithuanian phrase such as "Atsisakyti pakeitimų" (or "Atmesti
pakeitimus") so the UI conveys "Discard changes" rather than "Delete". Ensure
you only change the string value for the "discardChanges" key.
In `@frontend/src/i18n/pt_BR.json`:
- Line 90: The keys files.fileEncoding and prompts.currentPasswordMessage are
still English in the pt-BR translation; replace their values with correct
Brazilian Portuguese strings (e.g., "Codificação de arquivo" for
files.fileEncoding and an appropriate Portuguese prompt for
prompts.currentPasswordMessage such as "Por favor, insira sua senha atual") by
updating the JSON values for those keys so the UI is consistent.
In `@frontend/src/i18n/pt_PT.json`:
- Line 90: Update the pt_PT JSON to localize and normalize PT‑PT wording: change
the value for the key files.fileEncoding from English to the Portuguese
(Portugal) translation, update prompts.currentPasswordMessage to a PT‑PT
phrasing, replace any pt‑BR "senha" values with the PT‑PT term "palavra-passe"
(or the agreed PT‑PT label used across the file), and change the loading text
"Carregando" to the PT‑PT form ("A carregar" or the project’s chosen PT‑PT
variant). Ensure you update the keys files.fileEncoding,
prompts.currentPasswordMessage, any "senha" key occurrences, and the loading key
to keep wording consistent across the file.
In `@frontend/src/i18n/uk_UA.json`:
- Line 16: The Ukrainian locale file contains Russian labels: replace the
Russian strings for the keys "discardChanges" (currently "Отменить") and the
preview key (currently "Превью") with correct Ukrainian translations (e.g.,
"Скасувати" for discardChanges and "Попередній перегляд" for preview); update
the values in frontend/src/i18n/uk_UA.json for the "discardChanges" key and the
preview-related key so they are proper Ukrainian strings and keep JSON
formatting intact.
- Line 144: Replace the English value for the JSON key "currentPasswordMessage"
with the Ukrainian translation; update the value from "Enter your password to
validate this action." to "Введіть свій пароль, щоб підтвердити цю дію." so the
sensitive-action flow shows the correct localized prompt in uk_UA.json for the
currentPasswordMessage key.
- Line 90: Replace the English value for the localization key files.fileEncoding
in uk_UA.json with the Ukrainian translation; open uk_UA.json, find the
"fileEncoding" entry under the files namespace and change "File Encoding" to the
appropriate Ukrainian string (e.g., "Кодування файлу"), preserving valid JSON
syntax and surrounding punctuation.
- Line 190: The translation for the JSON key "resolution" is currently
"Роздільна здатність" (screen resolution); update the value for the "resolution"
key to reflect conflict/decision resolution—for example replace it with
"Вирішення" or "Вирішення конфлікту" so the semantics match the intended meaning
in the UI.
In `@frontend/src/i18n/zh_CN.json`:
- Line 90: Translate the remaining English strings in zh_CN.json: replace the
"fileEncoding" value with its Chinese translation, update the entry currently
labeled as the English phrase for "current password" to a more precise Chinese
equivalent for "current password" (locate the key named like "currentPassword"),
and translate the following entry on the next line (line 144) into Chinese as
well; ensure you modify the values only in frontend/src/i18n/zh_CN.json so keys
remain unchanged.
- Line 165: The "forbiddenError" translation value is awkward ("禁止错误"); update
the JSON entry for the key "forbiddenError" to a natural, user-facing Chinese
permission message such as "权限不足" or "无访问权限" so the UI reads correctly; locate
the "forbiddenError" key in the zh_CN.json and replace the string value
accordingly.
- Line 210: The value for the localization key "uploadingFiles" uses Traditional
Chinese ("上载档案"); update the JSON entry for "uploadingFiles" in zh_CN.json to
use Simplified Chinese wording (e.g., "上传文件") to match the rest of the zh_CN
locale.
---
Nitpick comments:
In `@frontend/src/i18n/en_GB.json`:
- Line 34: The label "overrideAll" currently uses the verb "Replace" while a
related label at line 180 uses "Overwrite"; make the action terminology
consistent across the flow by choosing one verb and updating the JSON value for
"overrideAll" (and the counterpart at line 180 where the value reads
"Overwrite") so both strings use the same term (either "Replace" or "Overwrite")
and match other related keys in this file.
In `@frontend/src/i18n/es_CO.json`:
- Line 53: The translations use inconsistent verbs for the skip action (e.g.,
the "skipAll" key uses "Omitir" while another skip-related entry uses "Saltar");
standardize all skip-related keys to a single verb across the file by choosing
one term ("Omitir" or "Saltar") and updating every skip-related JSON key value
(for example "skipAll" and the other skip* entries) to use that same verb so
terminology is consistent.
In `@frontend/src/i18n/es_ES.json`:
- Around line 143-144: Normalize the copy to a consistent informal "tú" register
and remove gendered wording: update the value for "currentPassword" and
"currentPasswordMessage" to use "tu" consistently and replace any gendered
adjective (e.g., "segura") with a neutral phrasing such as "confirmar" or "para
validar esta acción" reworded to "para confirmar esta acción"; apply the same
normalization to the other translation keys flagged in the review (the two
additional keys mentioned) so all prompts/settings use the same neutral,
informal tone.
In `@frontend/src/i18n/es_MX.json`:
- Line 165: Update the Spanish locale string for the "forbiddenError" key to use
clearer UI phrasing: replace the current value "Error prohibido" with "Acceso
prohibido" in the es_MX.json entry for "forbiddenError" so the message reads
naturally in Spanish.
- Line 197: The "skip" translation key uses "Saltar" which conflicts with the
existing "skipAll" translation "Omitir"; update the "skip" value to "Omitir" so
both skip-related keys ("skip" and "skipAll") use the same verb and maintain
consistent semantics in the conflict resolution flow.
In `@frontend/src/i18n/fr_FR.json`:
- Line 159: Update the translation value for the key "fastConflictResolve":
replace the awkward phrase "Le dossier de destination contient {count} des
fichiers portant le même nom." with a cleaner French string such as "Le dossier
de destination contient {count} fichier(s) portant le même nom." (or another
grammatically correct variant like "Le dossier de destination contient {count}
fichiers portant le même nom."). Ensure the change is applied to the
"fastConflictResolve" entry in frontend/src/i18n/fr_FR.json.
In `@frontend/src/i18n/pt_BR.json`:
- Line 54: The current pt-BR string for the "stopSearch" key ("Pare de
procurar") is a bit awkward; update the value for the "stopSearch" JSON key in
frontend translations to a more natural UI copy such as "Parar busca" or
"Interromper busca" (choose one and keep consistent with other labels), ensuring
you preserve valid JSON quoting and comma placement around the "stopSearch"
entry.
In `@frontend/src/i18n/pt_PT.json`:
- Line 180: The translation for the "override" key currently uses "Reescrever"
which can be ambiguous; update the value of the "override" key in
frontend/src/i18n/pt_PT.json to a clearer term used in the ResolveConflict
component's overwrite branch (e.g., "Sobrescrever" or "Substituir") so the
conflict UI in ResolveConflict.vue shows an unambiguous action label.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a5faa3f5-f65d-44fc-ab67-e302794f4bb9
📒 Files selected for processing (15)
frontend/src/i18n/ar_AR.jsonfrontend/src/i18n/en_GB.jsonfrontend/src/i18n/es_AR.jsonfrontend/src/i18n/es_CO.jsonfrontend/src/i18n/es_ES.jsonfrontend/src/i18n/es_MX.jsonfrontend/src/i18n/fr_FR.jsonfrontend/src/i18n/id_ID.jsonfrontend/src/i18n/lt_LT.jsonfrontend/src/i18n/pt_BR.jsonfrontend/src/i18n/pt_PT.jsonfrontend/src/i18n/ru_RU.jsonfrontend/src/i18n/tr_TR.jsonfrontend/src/i18n/uk_UA.jsonfrontend/src/i18n/zh_CN.json
✅ Files skipped from review due to trivial changes (2)
- frontend/src/i18n/id_ID.json
- frontend/src/i18n/tr_TR.json
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/i18n/ru_RU.json
- frontend/src/i18n/es_AR.json
| "numberFiles": "Número de archivos", | ||
| "optionalPassword": "Contraseña opcional", | ||
| "others": "Otros", | ||
| "override": "Exagerar", |
There was a problem hiding this comment.
override is mistranslated and can mislead a destructive action.
Line 180 uses "Exagerar", which does not mean overwrite/replace. In this conflict flow, users may not understand they are overwriting files.
💡 Suggested fix
- "override": "Exagerar",
+ "override": "Sobrescribir",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "override": "Exagerar", | |
| "override": "Sobrescribir", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/i18n/es_CO.json` at line 180, The translation key "override" is
incorrect and may imply exaggeration; update the value for the "override" JSON
key to a clear destructive-action phrasing such as "Sobrescribir" (or
"Reemplazar") so users understand it will overwrite files—locate the "override"
entry and replace "Exagerar" with "Sobrescribir" (or "Reemplazar") in
frontend/src/i18n/es_CO.json.
| "numberFiles": "Número de archivos", | ||
| "optionalPassword": "Contraseña opcional", | ||
| "others": "Otros", | ||
| "override": "Exagerar", |
There was a problem hiding this comment.
Fix mistranslation of the conflict action label (override).
"override": "Exagerar" is semantically incorrect for file conflict resolution and can mislead users in a destructive flow.
Proposed fix
- "override": "Exagerar",
+ "override": "Sobrescribir",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "override": "Exagerar", | |
| "override": "Sobrescribir", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/i18n/es_ES.json` at line 180, The translation for the conflict
action key "override" is incorrect; update the JSON entry for the "override" key
from "Exagerar" to a correct Spanish term like "Sobrescribir" (or "Reemplazar"
if that matches product terminology) so the label properly conveys file
replacement in destructive flows; also scan other i18n keys for consistency with
the chosen term.
| "numberFiles": "Número de archivos", | ||
| "optionalPassword": "Contraseña opcional", | ||
| "others": "Otros", | ||
| "override": "Exagerar", |
There was a problem hiding this comment.
Fix incorrect translation of the override action.
Line 180 maps override to "Exagerar", which changes the meaning and can confuse a destructive-file-action choice.
💡 Suggested fix
- "override": "Exagerar",
+ "override": "Sobrescribir",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "override": "Exagerar", | |
| "override": "Sobrescribir", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/i18n/es_MX.json` at line 180, The translation for the JSON key
"override" in es_MX.json is incorrect ("Exagerar"); update the value to the
correct Spanish term for a destructive file action (e.g., "Sobrescribir" or
"Reemplazar") so the "override" key maps to the intended meaning; edit the
"override" entry in the JSON to the chosen correct string and keep JSON
formatting unchanged.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements