Session 19 - Legacy Photo Render Hardening
Date: 2026-08-14
Story Points: 3 SP
Trigger: Sheri Dudley's August 11 email, subject "Troubleshooting"
Source key: mail:ath:19ff14b2dfa241da
Issue Ingested
Sheri reported that while working on her mother's tribute book, one picture was oriented incorrectly and another appeared black. She asked whether the product could correct these cases or whether the original contributor had to edit them.
She also wants to make physical-book ordering more intuitive and relabel parts of the product for clarity. Those UX requests remain follow-up work because the email did not identify the screens, steps, or replacement labels.
Diagnosis
- Production data identified the active artifact as Sheri's published Happy 85th Birthday book for C.C.
- All seven stored originals are readable RGB JPEGs and visually intact. No customer object was changed.
- The legacy files predate upload-time resizing. Decoded together, they occupy about 259 MB before canvases and encoded copies are counted.
groupSubmissions()intentionally carries the same photo URL at both contributor and response level. The PDF normalization path launched both levels through nestedPromise.all()calls.- The old cache stored only completed strings, so duplicate requests already in flight were not deduplicated. Multiple full-resolution bitmaps and canvases could push peak browser memory toward 1 GB. On a constrained browser, that can surface as a black canvas or a normalization failure that falls back to the EXIF-sensitive original.
- The existing normalization fix was confirmed in the public production bundle, so this was not a missed deployment.
Resolution
src/lib/image-normalize.ts now:
- caches the in-flight promise for each URL, not only the finished result;
- serializes image normalization globally so only one decoded photo is live at once;
- caps the longest render edge at 2400 px, matching print-resolution needs;
- flattens transparent inputs against white before JPEG encoding;
- falls back to an HTML image decoder when a browser rejects the
createImageBitmap()orientation option; - releases bitmap, object URL, and canvas backing stores before the next photo.
This fixes the pipeline for legacy books without overwriting customer originals. New uploads continue to be normalized at upload time as well.
Verification
npm run buildpassed.- The dev-only PDF harness generated a 44-page PDF with Sheri's exact large stored photos substituted into the production render path: 4,753,081 bytes, no browser console or page errors, generation in 2.9 seconds.
- Rendered cover and interior pages were visually inspected: photos were upright, visible, correctly cropped, and free of black rectangles.
- The existing EXIF orientation-6 regression fixture was rerun after the patch. Cover and interior output remained upright.
Intermediate PDFs and rendered PNGs were removed after visual verification.
Files Modified
src/lib/image-normalize.tsSTATE.mddocs/sessions/20260814-Session-19-photo-render-hardening.md
Follow-up
- Sheri to identify the exact physical-ordering steps and labels she wants changed.
- Deploy the verified patch to the existing Netlify production site after target confirmation.