Changelog — March 6, 2026
A high-output day focused on download performance and reliability: six PRs shipped covering FTP sync improvements, annotation PDF auto-sync, faster batch downloads via direct R2 signed URLs, and better UX for non-retouched projects.
✨ New Features
- FTP Sync & Annotation PDF Auto-Sync — Resolved multiple FTP reliability issues (retouched image detection from
/retouchedfolder, folder-to-batch mapping, missing thumbnails, server crash from incorrectbuildR2PublicUrlimport) and introduced server-side annotation PDF generation using jsPDF + Sharp SVG composite. The PDF now auto-syncs to the FTP root on every annotation create, update, or delete event, with a 5-second debounce to handle rapid successive changes. The FTP import dialog also received UI improvements for clearer file selection.#PR-278
🐛 Bug Fixes
-
Contextual Download Button — The download button now displays "Download images" instead of "Download retouched" on projects where
retouchEnabledis false, preventing clients from being misled about the nature of the files they are downloading.#PR-282 -
Image Loading Skeletons — Fixed a no-op skeleton in the image detail dialog that always rendered
null. Added a guard ononLoadingCompleteto prevent theBLANK_IMAGEplaceholder from prematurely marking images as loaded, and introduced an opacity fade-in transition for a smoother image reveal experience.#PR-279 -
Retouched Files Served on Download — All three download endpoints (individual file, batch ZIP, and pre-signed URL flow) now query for the latest retouch version and serve that file instead of the original when one exists, ensuring clients always receive the most up-to-date retouched asset.
#PR-276 -
Batch Download Speed — Added a server-side streaming batch ZIP endpoint (
POST /images/download-batch) that pipes R2 → archiver → client with zero memory buffering. Fixed the individual download endpoint to stream the R2 response directly instead of buffering the entire file. Added real-time download progress tracking in toast notifications and a client-side fallback with 6 concurrent downloads + JSZip.#PR-273
♻️ Refactoring & Technical Improvements
- Direct R2 Downloads via Signed URLs — Eliminated the API proxy bottleneck for file downloads: the frontend now requests pre-signed R2 URLs from
/api/images/download-urlsand fetches files directly from the R2 CDN in parallel (10 concurrent requests), then builds the ZIP client-side with JSZip. The API batch endpoint also had its R2 fetches parallelized in batches of 10, reducing 100-file downloads from 100 sequential round-trips to ~10.#PR-274
By theodaguier