fix(settings): never-silent avatar upload errors (LC-439) #438

Merged
longjacksonle merged 1 commit from fix/avatar-upload-error-feedback into main 2026-06-22 01:38:37 +02:00

Oversized avatar uploads failed silently on Settings > Profile (ref data/ref-profile.png). Subtask of LC-426.

Root cause

/settings/profile had no per-route body limit, so it used Axum's default ~2 MiB. The handler's own 1 MiB check returns a proper error fragment for files in the 1-2 MiB range, but a typical phone photo (>2 MiB) was 413'd by the body-limit layer BEFORE the handler ran. htmx doesn't swap non-2xx, so the request just no-op'd: no error, no success, no save (the client preview still showed the picked file, so it looked like it worked).

Fix - three layers, so a save can never be silent

  1. Client pre-validation (settings.js): validate size (<=1 MiB) + type (PNG/JPEG/WebP) on select, before previewing. An oversized/wrong-type pick shows an inline error next to the field, is dropped from the input, and disables Save until a valid file (or none) is chosen; the preview is suppressed so it never shows an unsaveable image. (Empty MIME passes the type check - the server byte-sniff is the real gate.)
  2. Raised route body limit: /settings/profile allows up to 6 MiB so the handler's 1 MiB check runs and returns the existing visible error fragment instead of a 413. The handler still rejects anything over 1 MiB.
  3. Never-silent net (settings.js): an htmx:responseError / htmx:sendError listener surfaces a generic error in the triggering form's status slot + a toast for any non-2xx / network failure - so EVERY Save button always ends in success or error (covers 413/500/network across the whole settings page).

New i18n: settings-avatar-err-size / -err-type, js-settings-save-error (en + es). New test oversized_avatar_hx_returns_error_fragment_not_413 (a 3 MiB body - over the old 2 MiB cap - now reaches the handler and returns 200 + .lc-status--err, not a 413). just check, just test, just test-saas pass; settings.js syntax-checked; Tailwind rebuilt.

🤖 Generated with Claude Code

Oversized avatar uploads failed silently on Settings > Profile (ref data/ref-profile.png). Subtask of LC-426. ## Root cause `/settings/profile` had no per-route body limit, so it used Axum's default ~2 MiB. The handler's own 1 MiB check returns a proper error fragment for files in the 1-2 MiB range, but a typical phone photo (>2 MiB) was 413'd by the body-limit layer BEFORE the handler ran. htmx doesn't swap non-2xx, so the request just no-op'd: no error, no success, no save (the client preview still showed the picked file, so it looked like it worked). ## Fix - three layers, so a save can never be silent 1. Client pre-validation (settings.js): validate size (<=1 MiB) + type (PNG/JPEG/WebP) on select, before previewing. An oversized/wrong-type pick shows an inline error next to the field, is dropped from the input, and disables Save until a valid file (or none) is chosen; the preview is suppressed so it never shows an unsaveable image. (Empty MIME passes the type check - the server byte-sniff is the real gate.) 2. Raised route body limit: `/settings/profile` allows up to 6 MiB so the handler's 1 MiB check runs and returns the existing visible error fragment instead of a 413. The handler still rejects anything over 1 MiB. 3. Never-silent net (settings.js): an `htmx:responseError` / `htmx:sendError` listener surfaces a generic error in the triggering form's status slot + a toast for any non-2xx / network failure - so EVERY Save button always ends in success or error (covers 413/500/network across the whole settings page). New i18n: settings-avatar-err-size / -err-type, js-settings-save-error (en + es). New test `oversized_avatar_hx_returns_error_fragment_not_413` (a 3 MiB body - over the old 2 MiB cap - now reaches the handler and returns 200 + `.lc-status--err`, not a 413). `just check`, `just test`, `just test-saas` pass; settings.js syntax-checked; Tailwind rebuilt. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(settings): never-silent avatar upload - client validation + raised body limit + htmx error net (LC-439)
All checks were successful
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 4s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 2m55s
2834e251af
Oversized avatar uploads failed silently: /settings/profile had no per-route body limit, so a typical >2 MiB photo was 413'd by Axum's default ~2 MiB cap BEFORE the handler's own 1 MiB check ran, and htmx ignores non-2xx responses - so Save just no-op'd (preview still showed the picked file, so it looked like it worked). Three layers so a save can never complete with no feedback:

- Client pre-validation (settings.js): on file select, validate size (<=1 MiB) and type (PNG/JPEG/WebP) before previewing. An oversized/wrong-type pick shows an inline error next to the field, is dropped from the input, and disables Save until a valid file (or none) is chosen; the preview is suppressed so it never shows an unsaveable image. An empty MIME passes the type check (the server byte-sniff is the real gate).
- Raised route body limit: /settings/profile now allows up to 6 MiB so the handler's 1 MiB avatar check runs and returns the existing visible error fragment instead of a body-limit 413. The handler still rejects anything over 1 MiB.
- Never-silent net (settings.js): an htmx:responseError / htmx:sendError listener surfaces a generic error in the triggering form's status slot + a toast for any non-2xx / network failure, so EVERY Save button always ends in success or error - covers 413/500/network across the whole settings page.

New i18n: settings-avatar-err-size / -err-type (en+es), js-settings-save-error (en+es). New test oversized_avatar_hx_returns_error_fragment_not_413 (3 MiB body reaches the handler -> 200 + .lc-status--err, not 413). just check, just test, just test-saas pass; settings.js syntax-checked; Tailwind rebuilt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch fix/avatar-upload-error-feedback 2026-06-22 01:38:37 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!438
No description provided.