Repository navigation
photos: send the archive JPG progressive on the LOCAL upload path - #128
Merged
Merged
Conversation
…SOOC file /images/full/<stem>.jpg is the one image surface here with no AVIF tier and no <picture> around it: the worker streams it from R2 as bare image/jpeg, ~10MB, and that single file is the whole payload. Scan order is therefore the entire loading experience, and cameras write baseline. Of the 158 archive originals, the 112 from HIF sources already go up progressive because zenc sets ProgressiveScanMode unconditionally; the 46 straight-from-camera JPGs did not. phase 3 now runs those through jpegtran -progressive -copy all on the way to R2. jpegtran reorders existing DCT coefficients and never decodes to pixels, so this is not a re-encode and carries no generational loss. Measured on a 9.95MB Leica original: SOF0 -> SOF2, 3.05% smaller (progressive Huffman packs better), decoded pixels byte-identical, EXIF including Orientation and ISO preserved. The conversion writes to a copy under $TMP and the source folder is never opened for writing. The bytes on disk stay exactly what the camera wrote, which is the point of keeping a SOOC archive; only the serving copy is rearranged. Falls back to uploading the untouched original if jpegtran fails, so a bad file costs the optimisation rather than the upload. This only affects photos added from here on. The 46 already in R2 stay baseline until someone backfills them, which needs an ARCHIVE_VERSION bump and a Purge Everything to get past caches.default (PR #50) and is deliberately not bundled in. Prompted by Kornel Lesiński's perf.now() 2018 image-optimization talk, which makes the case that progressive scan order is worth more than it looks precisely when one large image is the payload. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Understanding-first reviewThis is a prompt, not a score or merge gate. Before approving, reconstruct the change:
For docs and dependency changes, a compact answer is enough. For behavior or production changes, include the causal path and one concrete counterexample. The author’s claim card belongs in the PR description; this prompt is for the reviewer’s independent reconstruction. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
aadhar-sh | fb83193 | Jul 27 2026, 04:13 PM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
/images/full/<stem>.jpgis the one image surface on the site with no AVIF tier and no<picture>around it. The worker streams it from R2 as bareimage/jpeg, roughly 10MB, and that single file is the whole payload. Scan order is therefore the entire loading experience there — the case Kornel Lesiński argues for in his perf.now() 2018 talk, unlike a 32KB thumbnail where it's invisible.Cameras write baseline. Of the 158 archive originals, the 112 from HIF sources already go up progressive (
zencsetsProgressiveScanMode::ProgressiveSearchunconditionally); the 46 straight-from-camera Leica JPGs did not.The change
Phase 3 runs JPG-source originals through
jpegtran -progressive -copy allon the way to R2.This is not a re-encode.
jpegtranreorders existing DCT coefficients and never decodes to pixels, so there's no generational loss. Measured on a real 9.95MB Leica original:1a7ece28cea8a4f9both)The SOOC file is not touched
The conversion writes to a copy under
$TMP; the source folder is never opened for writing. Verified by sha256 before and after. The bytes on disk stay exactly what the camera wrote, which is the point of a SOOC archive. Only the serving copy is rearranged.-copy allis load-bearing: it keeps EXIF including Orientation, which gotchas 3 and 4 in CLAUDE.md are both about. Falls back to uploading the untouched original ifjpegtranfails.What this does NOT cover
Phase 3 is inside
if [ "${REMOTE_RENDER_ONLY:-0}" = "1" ]; then ... else, andphoto-pipeline.yml:115setsREMOTE_RENDER_ONLY: "1". So on the canonical remote path this code does not run.That is not fixable inside CI. Per
PHOTO-PIPELINE.md, the remote input contract is to upload the source object to R2 by hand (dashboard or other remote path); the workflow then downloads it read-only, builds derived tiers, and opens an artifact PR. It never rewrites the archive object — and it must not be able to, because the release design forbids GitHub from holding a Cloudflare token with write scope. The archive bytes are therefore fixed at hand-upload time.So the honest scope is:
npm run photos— covered by this PR, progressive from here on.jpegtran -progressive -copy allrun on the file before it is uploaded to R2, which is a change to the documented input contract rather than to any script here.ARCHIVE_VERSIONbump and a Purge Everything to clearcaches.default(the trap from fix(images): version-scope the /images/full Cache API key (ARCHIVE_VERSION) #50), and is a workstation-only operation since CI cannot write to R2.Follow-up worth doing: fold the
jpegtranstep into the input contract inPHOTO-PIPELINE.mdso the remote path stops being the one that silently skips it.Verification
bash -nparses clean.prep_originalagainst a real baseline Leica file: correct temp path returned, source sha256 unchanged, output SOF2, pixels identical, EXIF intact.jpegtran(mozjpeg) is already required for the lossless orientation step.🤖 Generated with Claude Code