featurebugfixperformance

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 /retouched folder, folder-to-batch mapping, missing thumbnails, server crash from incorrect buildR2PublicUrl import) 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 retouchEnabled is 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 on onLoadingComplete to prevent the BLANK_IMAGE placeholder 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-urls and 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