Problem
Users on slow mobile connections experience 499 errors (client disconnects) when submitting the puzzle-add form with photos. Analysis of production Traefik logs (2026-03-28) shows:
- One user made 16 consecutive failed attempts — all 499 status codes
- Upload sizes: 1.5–6MB (phone photos)
- Total request duration: 11–73 seconds
- At ~1 Mbps mobile upload speed, a 5MB photo takes ~40 seconds just for the upload
- Server processing is only ~1-2 seconds — the client upload dominates 80-97% of the total time
The root cause is that modern phone photos (12MP+ JPEGs) are 3-6MB, and users on slow mobile connections can't upload them fast enough before the browser/user gives up.
Proposed Solution
Use the browser's Canvas API to resize and compress images before upload:
- When user selects a photo in the form's file input
- JavaScript reads the image into a Canvas element
- Resize to max 2000px (matching server-side
ImageOptimizer max dimension)
- Export as JPEG at quality 0.85
- Replace the file input with the compressed blob
Expected impact
- Phone photo: 4032x3024 JPEG (5MB) → 2000x1500 JPEG (300-500KB)
- Upload time at 1 Mbps: ~40 seconds → ~3 seconds
Considerations
- Mobile CPU: Canvas resize of a single 12MP image takes <2 seconds even on older phones. Much faster than waiting 40+ seconds for upload.
- HEIC handling: iOS Safari converts HEIC→JPEG when uploading via file input, so Canvas receives JPEG.
- Fallback: If Canvas compression fails (rare), submit the original file as-is.
- Quality: 2000px at 85% quality is identical to what the server-side
ImageOptimizer produces. imgproxy handles all display thumbnailing anyway.
- Both photo fields: Apply to both
puzzlePhoto and finishedPuzzlesPhoto inputs.
- Memory: Use
URL.createObjectURL() + Image element instead of FileReader to reduce memory pressure. Call URL.revokeObjectURL() after use.
UX
Consider showing a brief "Compressing..." indicator while the Canvas processes, though it should be fast enough (<2s) that a simple spinner suffices.
Related changes already done
ImageOptimizer now uses JPEG size hint for faster server-side decoding, auto-orients, strips EXIF, and compresses at quality 85
submit_prevention_controller now keeps the submit button disabled until turbo:submit-end (was re-enabling after 10s, causing users to re-submit and cancel in-flight uploads)
Problem
Users on slow mobile connections experience 499 errors (client disconnects) when submitting the puzzle-add form with photos. Analysis of production Traefik logs (2026-03-28) shows:
The root cause is that modern phone photos (12MP+ JPEGs) are 3-6MB, and users on slow mobile connections can't upload them fast enough before the browser/user gives up.
Proposed Solution
Use the browser's Canvas API to resize and compress images before upload:
ImageOptimizermax dimension)Expected impact
Considerations
ImageOptimizerproduces. imgproxy handles all display thumbnailing anyway.puzzlePhotoandfinishedPuzzlesPhotoinputs.URL.createObjectURL()+Imageelement instead of FileReader to reduce memory pressure. CallURL.revokeObjectURL()after use.UX
Consider showing a brief "Compressing..." indicator while the Canvas processes, though it should be fast enough (<2s) that a simple spinner suffices.
Related changes already done
ImageOptimizernow uses JPEG size hint for faster server-side decoding, auto-orients, strips EXIF, and compresses at quality 85submit_prevention_controllernow keeps the submit button disabled untilturbo:submit-end(was re-enabling after 10s, causing users to re-submit and cancel in-flight uploads)