Skip to content

feat: merge upstream 2.63.2#101

Merged
laurynasgadl merged 127 commits intomasterfrom
feat/merge-upstream-2.63.1
Apr 17, 2026
Merged

feat: merge upstream 2.63.2#101
laurynasgadl merged 127 commits intomasterfrom
feat/merge-upstream-2.63.1

Conversation

@laurynasgadl
Copy link
Copy Markdown
Member

@laurynasgadl laurynasgadl commented Apr 17, 2026

Summary by CodeRabbit

  • New Features

    • Conflict-resolution modal for move/copy/upload with per-item decisions.
    • Current-password prompt for sensitive actions (save/delete).
    • CSV viewer: encoding selector and column-separator controls.
    • "Open direct" action for image/audio/video previews.
    • Hook-based authentication option and optional Redis upload cache/flag.
  • Bug Fixes

    • Prevent deletion of the sole admin (role-based).
    • Video player arrow keys now seek in-player.
    • Sharing now requires download permission.
  • Improvements

    • Asynchronous CSV parsing and better encoding handling.

AndyBodnar and others added 30 commits January 17, 2026 22:57
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>
renovate bot and others added 24 commits March 28, 2026 19:07
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: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Introduces 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

Cohort / File(s) Summary
CI / Release
.github/workflows/ci.yaml, .goreleaser.yml, transifex.yml
PNPM action bumped; added frontend test job; Go toolchain bump; added OpenBSD build target with ignores; added language mappings.
Changelog / README
CHANGELOG.md, README.md
Inserted multiple release entries; updated project status wording and reference link.
Compose / Infrastructure
compose.yaml
Added Docker Compose with filebrowser + redis, network, volume, and Redis ACL startup.
Go modules / deps
go.mod
Bumped go directive and multiple dependencies; added redis client and other indirect requirements changes.
Upload Cache
http/upload_cache_memory.go, http/upload_cache_redis.go
Added UploadCache interface, in-memory TTL backend, and Redis-backed implementation with constructors.
TUS & HTTP wiring
http/tus_handlers.go, http/http.go, http/upload_cache_*.go
TUS handlers now accept UploadCache; lifecycle uses Register/GetLength/Touch/Complete; NewHandler accepts uploadCache; location/header/validation updates.
Auth & user defaults
auth/hook.go, auth/json.go, auth/proxy.go, auth/proxy_test.go, http/auth.go
Hook default DateFormat set; JSON auth uses dummy bcrypt hash to mitigate timing; proxy/self-created users force admin/execute false and empty commands; tests added; signup forces those overrides.
Users & storage
users/storage.go, storage/bolt/users.go
Added CountAdmins(), Storage.IsUniqueAdmin(), and switched root-deletion guard from hardcoded id to admin-count logic.
HTTP handlers / permissions
http/users.go, http/share.go, http/public.go, http/public_test.go, http/resource.go, http/raw.go, http/raw_test.go
User delete now accepts current password and verifies for JSON auth; share gating requires download permission; public share auth checks share+download; resource endpoints support X-Encoding and binary streaming; Content-Disposition inline uses RFC6266 filename; tests added/updated.
Static assets & utils
http/static.go, http/utils.go, rules/rules.go, rules/rules_test.go
Static handler respects Accept-Encoding and streams/decompresses gz assets; JSON injection uses template.JS; stripPrefix issues redirect for exact prefix; rule matching tightened and tested.
CLI / config
cmd/root.go, cmd/config.go, cmd/config_import.go, cmd/users.go, cmd/utils.go
Added redisCacheUrl flag and uploadCache initialization; printSettings shows Defaults.DateFormat; removed interface-map normalization; viper parse error handling updated; user defaults dateFormat parsing added.
Errors / version
errors/errors.go, version/version.go
Updated ErrRootUserDeletion message; added ErrShareRequiresDownload; added nolint in version file.
Frontend deps / build
frontend/package.json, frontend/vite.config.ts
Added vitest, csv-parse, marked-katex-extension; upgraded router and build tooling; compression preserves originals.
Front API & types
frontend/src/api/files.ts, frontend/src/api/users.ts, frontend/src/types/*.d.ts
X-Encoding negotiation and makeRawResource handling; users.remove accepts optional currentPassword; added ConflictingResource/CsvData/rawContent and other type updates.
Encodings & utils
frontend/src/utils/encodings.ts, frontend/src/utils/upload.ts, frontend/src/utils/csv.ts, frontend/src/utils/clipboard.ts, frontend/src/utils/index.ts, frontend/src/utils/__tests__/upload.test.ts
New encodings module, decode and makeRawResource; upload.checkConflict returns ConflictingResource[] and per-file logic; removed old CSV parser; added clipboard.read(); added vClickOutside directive; upload tests added.
Conflict resolution UI & prompts
frontend/src/components/prompts/ResolveConflict.vue, frontend/src/components/prompts/*, frontend/src/components/files/*
Added ResolveConflict component; replaced replace-rename flows with per-item conflict arrays; added CurrentPassword prompt; refactored many prompt/modal flows and components.
Modal system & styles
frontend/src/components/DropdownModal.vue, frontend/src/components/prompts/BaseModal.vue, frontend/src/components/prompts/Prompts.vue, frontend/src/css/*, frontend/src/main.ts
Replaced vue-final-modal with custom modal and dropdown; removed vue-final-modal CSS import; adjusted z-index/focus styles; stopped registering vfm.
Editor / preview / listing
frontend/src/views/files/Editor.vue, frontend/src/views/files/Preview.vue, frontend/src/views/files/FileListing.vue
Editor: delayed init, KaTeX marked plugin, custom clipboard paste/read; Preview: open direct, CSV rawContent/encoding and keyboard changes; Listing: conflict flow updates, permission gating for share requires download.
Prompts / rename / new dir
frontend/src/components/prompts/*
Rename validation tightened; NewDir uses layoutStore prompt props; CreateFilePath got path prop; ReplaceRename component removed.
Settings / permissions
frontend/src/views/settings/User.vue, frontend/src/components/settings/Permissions.vue
Moved current-password to modal flow for save/delete; enabling share forces download; delete sends current password and logs out on self-delete.
I18n
frontend/src/i18n/*
Added/updated many locale keys for conflict resolution, file encoding, current-password prompts, openDirect, and share→download wording across locales.
Docs
www/docs/authentication.md, www/docs/cli/filebrowser.md
Added Hook Authentication docs and strengthened proxy header guidance; documented --redisCacheUrl flag.

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

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/merge-upstream-2.63.1

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

LockPassword no longer triggers current-password verification.

The lookup lowercases field, but the map still stores lockPassword. Updates that only touch LockPassword will 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 | 🟡 Minor

Normalize 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Reject with an explicit Error in 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 | 🟡 Minor

Handle async close rejection 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 | 🟡 Minor

Guard binding.value before 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 | 🟡 Minor

Restore a pt alias to avoid Portuguese fallback to English.

Keeping only "pt-pt" may miss generic lang="pt" and fall back to English. Add a pt mapping 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 | 🟡 Minor

Fix the fence languages in the new auth examples.

The PowerShell sample is fenced as sh, and the new key=value blocks have no language tag, which is already tripping markdownlint. Use powershell for auth.ps1 and text/ini for 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 | 🟡 Minor

Translate the new pt_PT strings 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 | 🟡 Minor

The large-CSV error path is now unreachable.

isCsv already requires size <= CSV_MAX_SIZE, so Line 418 can never run. Large .csv files now fall through to the generic blob/no-preview state instead of showing files.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 | 🟡 Minor

Untranslated 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 | 🟡 Minor

Untranslated 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 | 🟡 Minor

Inline 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 | 🟡 Minor

Hardcoded credentials in environment variable.

The Redis password filebrowser is hardcoded in plaintext. For development this may be acceptable, but consider:

  1. Adding a comment noting this should be changed in production
  2. 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 | 🟡 Minor

Multiple untranslated strings in Russian locale.

These conflict resolution and password prompt strings are in English and should be translated to Russian:

  • replaceOrSkip, resolveConflict, singleConflictResolve, fastConflictResolve
  • uploadingFiles, filesInOrigin, filesInDest
  • override, skip, forbiddenError
  • currentPassword, 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 | 🟡 Minor

Grammatical 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 | 🟡 Minor

Missing return statement causes dead code.

Line 65 if (!modal) null; is a no-op — the null expression is evaluated but not returned. The function will return undefined instead of the intended null.

🐛 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 | 🟡 Minor

Missing Spanish translations for new strings.

Several new strings in es_AR.json are in English rather than Spanish. This includes:

  • buttons.openDirect, buttons.overrideAll, buttons.skipAll, buttons.renameAll, buttons.singleDecision
  • files.fileEncoding
  • Multiple entries in prompts section (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 | 🟡 Minor

Top positioning may cause dropdown to overlap the trigger.

When position includes "top", the dropdown's top is set to rect.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 / bottom anchoring. 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 | 🟡 Minor

Remove 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 | 🟡 Minor

Selection change listener not cleaned up.

The changeSelection listener is added to the selection object but never removed in onBeforeUnmount. While editor.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 | 🟡 Minor

Multiple 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.share

These 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 | 🟡 Minor

TextDecoder constructor can throw for invalid encodings.

If an invalid encoding string is passed, new TextDecoder(encoding) throws a RangeError. Consider wrapping in try-catch or validating against availableEncodings first.

🛡️ 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 | 🟡 Minor

Potential undefined value for conflict ref.

If currentPrompt?.props.conflict is undefined, conflict.value will 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 the revive suppression scope.

At Line 1, //nolint:revive disables all revive checks 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 newDir prompt 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 no current_password is 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 403 cases only work because the first saved user happens to become ID 1. Since the share already points at UserID: 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 getFile helper 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 a Map<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 :latest tags for both filebrowser/filebrowser and redis makes builds non-reproducible. Consider pinning to specific versions (e.g., redis:7.2 or 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 outer action function parameter, making the code confusing to follow. Consider renaming the inner variable to apiAction or 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() and performAction(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: Unused cancel method.

The cancel method is defined but never called in the template—the cancel button directly invokes closeHovers. 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 with setTimeout(50ms) for editor initialization.

Using a fixed 50ms delay to wait for DOM readiness is brittle and may fail on slower devices. Consider using nextTick or 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: Inconsistent authMethod source between new and existing user paths.

For new users (line 97), isCurrentPasswordRequired uses the imported authMethod constant. For existing users (line 108-109), it fetches authMethod from settings.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 accepting context.Context for 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, and Touch. This would require updating the UploadCache interface 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.Touch returns 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 subsequent GetLength calls 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 from os.Remove on 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: isEncondingDropdownOpen should be isEncodingDropdownOpen.

The variable name has a typo (EncondingEncoding). 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 watchEffect triggers a new parse() 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: toogleCheckAll should be toggleCheckAll.

The function name has a typo (toogletoggle). 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

📥 Commits

Reviewing files that changed from the base of the PR and between a4887cd and caabdc1.

⛔ Files ignored due to path filters (2)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (82)
  • .github/workflows/ci.yaml
  • .goreleaser.yml
  • CHANGELOG.md
  • README.md
  • auth/hook.go
  • auth/json.go
  • auth/proxy.go
  • auth/proxy_test.go
  • cmd/config.go
  • cmd/config_import.go
  • cmd/root.go
  • cmd/users.go
  • cmd/utils.go
  • compose.yaml
  • errors/errors.go
  • frontend/package.json
  • frontend/src/api/files.ts
  • frontend/src/api/users.ts
  • frontend/src/components/DropdownModal.vue
  • frontend/src/components/files/CsvViewer.vue
  • frontend/src/components/files/ListingItem.vue
  • frontend/src/components/files/VideoPlayer.vue
  • frontend/src/components/prompts/BaseModal.vue
  • frontend/src/components/prompts/Copy.vue
  • frontend/src/components/prompts/CreateFilePath.vue
  • frontend/src/components/prompts/CurrentPassword.vue
  • frontend/src/components/prompts/FileList.vue
  • frontend/src/components/prompts/Move.vue
  • frontend/src/components/prompts/NewDir.vue
  • frontend/src/components/prompts/Prompts.vue
  • frontend/src/components/prompts/Rename.vue
  • frontend/src/components/prompts/ReplaceRename.vue
  • frontend/src/components/prompts/ResolveConflict.vue
  • frontend/src/components/prompts/Upload.vue
  • frontend/src/components/settings/Permissions.vue
  • frontend/src/css/base.css
  • frontend/src/css/styles.css
  • frontend/src/i18n/en.json
  • frontend/src/i18n/es_AR.json
  • frontend/src/i18n/pt_PT.json
  • frontend/src/i18n/ru_RU.json
  • frontend/src/i18n/zh_CN.json
  • frontend/src/main.ts
  • frontend/src/stores/layout.ts
  • frontend/src/types/file.d.ts
  • frontend/src/types/global.d.ts
  • frontend/src/types/hostinger.d.ts
  • frontend/src/types/upload.d.ts
  • frontend/src/utils/__tests__/upload.test.ts
  • frontend/src/utils/clipboard.ts
  • frontend/src/utils/csv.ts
  • frontend/src/utils/encodings.ts
  • frontend/src/utils/index.ts
  • frontend/src/utils/upload.ts
  • frontend/src/views/files/Editor.vue
  • frontend/src/views/files/FileListing.vue
  • frontend/src/views/files/Preview.vue
  • frontend/src/views/settings/User.vue
  • frontend/vite.config.ts
  • go.mod
  • http/auth.go
  • http/http.go
  • http/public.go
  • http/public_test.go
  • http/raw.go
  • http/raw_test.go
  • http/resource.go
  • http/share.go
  • http/static.go
  • http/tus_handlers.go
  • http/upload_cache_memory.go
  • http/upload_cache_redis.go
  • http/users.go
  • http/utils.go
  • rules/rules.go
  • rules/rules_test.go
  • storage/bolt/users.go
  • transifex.yml
  • users/storage.go
  • version/version.go
  • www/docs/authentication.md
  • www/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

Comment thread auth/hook.go
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),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment thread auth/json.go
Comment on lines 58 to 70
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +2 to +5
<div id="modal-background" @click="backgroundClick">
<div ref="modalContainer">
<slot></slot>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +32 to +37
window.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
event.stopImmediatePropagation();
emit("closed");
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +96 to 100
size: this.req.items[item].size,
modified: this.req.items[item].modified,
overwrite: false,
rename: this.$route.path === this.dest,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment thread http/resource.go
Comment on lines 324 to 332
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment thread http/resource.go
Comment on lines +341 to +343
srcInfo, _ := d.user.Fs.Stat(src)
dstInfo, _ := d.user.Fs.Stat(dst)
same := os.SameFile(srcInfo, dstInfo)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment thread http/static.go
Comment on lines +153 to 156
f, err := assetsFs.Open(r.URL.Path + ".gz")
if err != nil {
return http.StatusNotFound, err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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).

Comment thread http/static.go
Comment on lines +159 to +162
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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment thread storage/bolt/users.go
Comment on lines +108 to +116
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++
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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:


🏁 Script executed:

git ls-files storage/bolt/users.go

Repository: hostinger/filebrowser

Length of output: 86


🏁 Script executed:

sed -n '100,125p' storage/bolt/users.go

Repository: 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.

Suggested change
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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 | 🟡 Minor

Fix 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 | 🟡 Minor

Untranslated 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 | 🟡 Minor

Untranslated string in French locale (Line 90).

"fileEncoding" is still in English, which breaks localization consistency for fr_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 | 🟡 Minor

Translate the newly added Arabic locale keys that are still in English.

files.fileEncoding and prompts.currentPasswordMessage are 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 | 🟡 Minor

Use 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.resolution is 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 | 🟡 Minor

Translate 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 | 🟡 Minor

Use 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 | 🟡 Minor

Keep 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 | 🟡 Minor

Improve 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 | 🟡 Minor

Use 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Translate 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 | 🟡 Minor

Localize files.fileEncoding for uk_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 | 🟡 Minor

Correct 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 | 🟡 Minor

Translate 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 | 🟡 Minor

Localize 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

fileEncoding is 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 | 🟡 Minor

Password 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

discardChanges is 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 | 🟡 Minor

Abort 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 | 🟡 Minor

Correct 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 | 🟡 Minor

Avoid 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/su and 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, but Parar busca/Interromper busca usually 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), Sobrescrever or Substituir is 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

📥 Commits

Reviewing files that changed from the base of the PR and between caabdc1 and 2a89f1b.

📒 Files selected for processing (15)
  • frontend/src/i18n/ar_AR.json
  • frontend/src/i18n/en_GB.json
  • frontend/src/i18n/es_AR.json
  • frontend/src/i18n/es_CO.json
  • frontend/src/i18n/es_ES.json
  • frontend/src/i18n/es_MX.json
  • frontend/src/i18n/fr_FR.json
  • frontend/src/i18n/id_ID.json
  • frontend/src/i18n/lt_LT.json
  • frontend/src/i18n/pt_BR.json
  • frontend/src/i18n/pt_PT.json
  • frontend/src/i18n/ru_RU.json
  • frontend/src/i18n/tr_TR.json
  • frontend/src/i18n/uk_UA.json
  • frontend/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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
"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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
"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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
"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.

@laurynasgadl laurynasgadl merged commit b54aa86 into master Apr 17, 2026
5 checks passed
@laurynasgadl laurynasgadl deleted the feat/merge-upstream-2.63.1 branch April 17, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.