Repository navigation
perf: a 200px tier, histograms on the tile, and one fewer copy of the photo data - #441
Merged
Merged
Conversation
… to everyone The grid tile is a fixed 184px column and every visitor got the 400px file. That is 2.3x what a 1x display can show, while a DPR-3 phone asks for 552 device pixels and was handed 400, so the one-tier choice was wrong at both ends. Measured on the built tree, 12 tiles, real browser at each DPR: DPR 1 200px tier 56.3 KiB was 131.2 -57% DPR 2 400px tier 131.2 KiB unchanged, and it was already right DPR 3 600px tier 249.8 KiB was the 400px file, ie soft Images were 81% of the homepage load, so the 1x case is roughly 40% off the whole page. The document pays ~200 brotli bytes for the extra candidates. THE 600px TIER ALREADY EXISTED. It is built, hashed and committed at 3,257 KiB across 158 files and nothing served it. reencode-thumbnails.sh's own header has described it as "the ~197px tile at DPR-3" the whole time. WHY srcset IS NOT THE <picture> THIS FILE TWICE REMOVED. <picture>/<source> select on TYPE and leave a loser to be instantiated: a Chromium hover capture on 2026-08-11 caught 13 JPEG loads for one tile. srcset selects on SIZE and resolves to a single candidate. I ran that exact control before writing any of this, then again on the finished page: 12 tiles, two full hover passes, DPR 1/2/3, ZERO image loads beyond the initial twelve, identical to a single-URL control. All three candidates are AVIF, so no type selection happens anywhere. The contract test now pins that invariant instead of its old proxy. It banned `srcset=` outright because at the time one URL and one format were the same policy; it asserts no <picture>/<source>/JPEG and that every candidate is one AVIF with a width descriptor. ADDITIVE BY CONSTRUCTION, which is most of the work here. An /i/ URL names its bytes, so re-encoding a tier that did not need to change remints its hash, rewrites every page referencing it, and orphans the a-dict and p-dict snapshots built against it. reencode-thumbnails.sh takes TIERS=xs to write the new tier alone, and the 158 photos were backfilled from canonical sources that way: 0 existing tiers lost, 0 changed, 474 -> 632 files in www/i. Two bugs that blocked that and are worth fixing regardless. hash-thumbnails.sh discovered stems from .jpg only, so a run writing one AVIF and no JPG silently hashed zero photos. And its per-stem write ASSIGNED the entry where the file's own header calls it a merge, so an additive run would have dropped a, j and s while their files sat on disk in www/i -- damage that reads as a map that forgot them rather than as anything missing. Also: width/height go 600x600 to 184x184. They were describing a file that has not been served since the grid moved to the 400px tier, and with srcset the intrinsic size depends on which candidate wins while the tile is always 184. The x tier is REQUIRED in check-photo-pipeline.mjs rather than optional. The serving path degrades fine without it, which is exactly why nothing would ever notice it missing. Its digest-count assertion went red on this change because it hardcoded 3 tiers; it reads TIER_KEYS.length now.
Hovering a photo fetched /images/meta/<stem>.json and the bars could not draw
until it landed: measured on production at 135ms then 117ms, once per photo. The
worker already knows which twelve it drew, so it packs their four channels into
data-hist on each tile (64 bins x 4, one byte each, base64, 344 chars).
Controlled in a browser with /images/meta/* aborted outright: bars drew on 6 of
6 hovers. Same six hovers cost 6 requests before and 3 after, and the 3 that
remain are for TEXT racing the shared EXIF index, never for bars.
IT HAD TO BE IN THE MARKUP. Warming from tooltip.js was the obvious cheaper
build and cannot work: index.html loads that module ON THE FIRST HOVER, on
purpose ("visitors who never hover transfer no tooltip JavaScript"), so anything
it warms is by construction too late for the hover that caused the load. I wrote
the warm first and measured 0 warmed requests before any hover, which is what
sent me looking at the loader.
WHERE THE WORKER GETS THEM is the rest of the design. The fragment draws a random
twelve per request, so all 158 must be reachable. Bundling them costs 22.9 KiB
gzip, 83% of the bundle's remaining headroom to its advisory. Reading twelve
files per request costs twelve subrequests against a 50 cap on this plan
(gotcha 36). So it is ONE file, memoised per isolate exactly like alt.json and
hashes.json already are: one read per isolate, free on every warm one.
This does not contradict build-exif-index.mjs's reasoning for keeping histograms
out of exif.json. That is about a file every visitor downloads whole, where all
158 would take it from 2.6 KiB to 24 KiB for bars most people never see. Only
the twelve a visitor can actually hover ship here, measured at +2055 br on the
document.
TWO REAL BUGS THE CONTRACT TEST CAUGHT, both introduced by the 200px tier in the
previous commit and both on the AVIF decode repair:
- its stem regex spelled `(?:-400)?`, so a failing 200px tile matched nothing
and the repair silently did not run. That tier is what DPR-1 visitors get, ie
most of them.
- it assigned image.src while srcset was still set. srcset WINS, so the browser
re-picks the AVIF candidate it just failed to decode. The handler runs, the
attribute changes, and the image stays broken.
Both now have assertions naming them. The test that caught this was pinning
`(?:-400)?` as "the small tier is the only suffix we mint", which stopped being
true one commit earlier.
Also takes the dead `const img = slot.querySelector("img")` in tooltip.js, whose
note said to remove it on the next real edit to that file since it is
content-hashed and would otherwise mint a new /a/ URL for nothing. This is that
edit.
histograms.json is generated by build-histogram-index.mjs, wired into
extract-photo-metadata.sh so `pnpm run photos` keeps it current, and
check-photo-pipeline.mjs rebuilds it through the generator's OWN packing function
and fails on drift. Controlled three ways: a corrupted entry, a missing entry,
and the clean tree.
/rn/art asked Cloudflare Image Resizing for quality 82 while the photo pipeline gives avifenc -q 63. That showed up as 0.323 bytes per pixel against the photo tier's 0.066, a 4.9x gap, on art displayed at 120x120. The number alone proves nothing, because the two encoders are different, so the sweep is the justification. Measured against the REAL Cloudflare AVIF encoder (the on-zone /rn/art JPEG variant as source, via /cdn-cgi/image), on four covers from the live playlist, ssimulacra2 at the 120px size the art is displayed at: q82 88.67 89.85 88.67 88.82 16013 7348 15148 13331 bytes q70 86.43 87.74 85.33 87.30 12765 5947 12289 11263 q63 83.73 86.53 83.83 85.12 11136 5293 10929 10313 About 27% off. The floor worth comparing against is what this site already ships rather than a number from a table: the 200px photo tier every DPR-1 visitor now receives measures 78-80 at ITS display size, so q63 art sits above the grid it sits beside. EVALUATE AT DISPLAY SIZE. The same sweep judged at the encoded 240px reads about 5 points harsher and puts q63 at 67.9, which looks like a real degradation and is measuring pixels nobody is shown. The art has been a 2x asset for a 120px box since artUrls() dropped the 120w candidate on purpose. ART_VERSION goes to 2, which is not optional: /rn/art responses are immutable for a year, so a quality change reaches nobody without a new path. Its own comment has said "bump it to re-tune width or quality" the whole time. Old URLs keep serving their q82 bytes for the rest of their year, by design.
data-hist was 68% of the 12-tile fragment (2014 of 2948 br). Base64 packs three bytes into four characters, which destroys the byte alignment brotli's context modelling exploits, and histogram bins are SMOOTH: neighbours are close, so neighbouring characters are close and the compressor can use that. base64 344 chars/tile 1964 B br one char per bin 256 chars/tile 1263 B br -36% 5-bit packed+b64 216 chars/tile 1240 B br 704 br off every homepage document. The 5-bit variant wins by 23 bytes and costs a bit-packing loop at both ends, which is a bad trade for a value the tooltip decodes on a hover. ASCII 63..126 is 64 contiguous characters holding NONE of & < > " (34, 38, 60, 62, all below the range), so escAttr is a no-op here by construction rather than by luck. Asserted over every committed histogram rather than a fixture, because the day it breaks is the day a bin goes out of range. It does hold a backtick, which is one of the characters that forces minify-html to keep the quotes. Measured on the staged document: 7 of 12 tiles quoted, 5 unquoted, all 12 intact at 256 characters. Both are spec-legal and dataset reads them the same; noted because an attribute whose quoting varies per value looks like a bug the first time you diff the output. 64 levels against a 0-100 source costs at most 1 unit of round-trip error, verified against all 158 originals, and the SVG is 32 units tall so one level is half a pixel. The decode goes from atob plus a loop to charCodeAt minus 63, and refuses a truncated or out-of-range value outright rather than drawing a curve with a hole in it. Also corrects the photo pool's header, which called thumb_avif "the <picture> primary", thumb_jpg "the universal <img src> fallback" and thumb_small "the 400px mobile AVIF". All three stopped being true, in three separate changes, without the comment moving.
images/meta/ was 158 committed files that are a PURE PROJECTION of two other committed files. Verified 158/158, text half and histogram half both exact: meta/<stem>.json is exif.json[stem] plus the unpacked histograms.json[stem]. Same argument the Markdown twins won. A pure function of committed bytes should not be a second committed copy that can fall behind, and generating it in build.mjs means there is no step anyone can forget. 158 files and 151 KiB of data leave the tree; the URL contract does not move, and routes:check is what proves that, since it asserts /images/meta/<stem>.json answers 200 against the BUILT tree. Confirmed with the source directory removed entirely. CAUSALITY NOW RUNS ONE WAY. The pipeline still writes images/meta/ locally, build-exif-index.mjs and build-histogram-index.mjs still read it, and the two indexes they emit are what gets committed. build.mjs derives the served per-photo files back out of those indexes, so a stale one is no longer a state this repository can be in. www/images/meta joins www/scripts in STAGE_SKIP for exactly that reason: copying whatever is on the local disk and then writing over it would make the built tree depend on pipeline leftovers. THE DRIFT CHECKS ARE TIERED RATHER THAN DELETED. Where images/meta/ exists — a workstation that just ran the pipeline, which is the only place these indexes can go stale — check-photo-pipeline.mjs rebuilds both from it and fails on any disagreement. Where it does not, it says so and checks coverage instead. Controlled both ways: absent skips with a note and exits 0, present-and-drifted fails by name and exits 1. Nothing is lost by that tiering. The drift it guarded was two committed copies disagreeing, and there is one copy now. The per-stem histogram shape assertion moves off meta/ and onto histograms.json, so it still runs in CI where the directory is gone. One honest cost: regenerated files carry histogram values quantised to the index's 64 levels rather than the original 0-100, so they differ from the retired committed ones by at most 1. That is 0.32px in the 32-unit SVG they feed, and the real source of truth is the photograph, which `zenc histogram` can re-bake.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
aadhar-sh | 42777a7 | Aug 17 2026, 12:59 PM |
Contributor
Wire-size diff vs merge base
Brotli q11, the quality the edge serves. Every number here is deterministic: Client assetsTotal Brotli: 74.97 KiB → 75.15 KiB (+0.17 KiB, +0.2%) across 21 files, 1 changed.
PagesTotal Brotli: 500.45 KiB → 502.05 KiB (+1.60 KiB, +0.3%) across 48 files, 1 changed.
Worker bundle228.47 KiB → 229.72 KiB gzip (+1.25 KiB, +0.5%) Largest module deltas (raw bytes in bundle):
Dictionary deltas (
|
oddharsh
added a commit
that referenced
this pull request
Aug 17, 2026
The rebase onto main brought #439/#440/#441 (the 200px tier, packed histograms, the fingerprint index and one fewer copy of the photo data), and that code was written against www/. Four integration fixes it needed: - build.mjs's meta-twin derivation wrote to ${OUT}/www/images/meta and read exif.json + histograms.json from the same place, so it generated 0 files. Its own floor caught it, which is the tripwire doing exactly its job. - the homepage bake lost its `histograms` load in a conflict resolution and failed with a bare ReferenceError. Restored from main. - contract-tests imported ../www/scripts/build-histogram-index.mjs and read www/images/histograms.json. - tooltip.js cited www/scripts/build-histogram-index.mjs in a comment. That last one was caught by the served-path contract test added earlier on this branch, which is the first time it has found something it was not written for. Also un-resurrected 158 files. #441 DELETED www/images/meta and gitignored it: the per-photo twins are derived from exif.json + histograms.json now, and a pure function of committed bytes should not be a second committed copy. The bulk conflict resolution added them back at their moved path, which would have undone that silently. The .gitignore rule and build.mjs's STAGE_SKIP both survive, repointed to public/images/meta. 307 contract tests (main's 12 coverage tests came across), typecheck, lint, check-wrangler, pages:check, tools:check and perf-budget all clean. Bundle 669.37 KiB, up from 666.62 because main's photo work is now in it.
oddharsh
added a commit
that referenced
this pull request
Aug 17, 2026
Closes the drift the previous commit diagnosed. `zenc histogram --check` now reports "histograms match on disk for 158 photos"; it reported 158 of 158 DRIFTING before. This is the one deliberate rebake that commit said was needed. The committed set came from an unrecorded build, so no pin could reproduce it retroactively — the gap only closes by re-baking once and committing the result. Done under rust-toolchain.toml (rustc 1.93.0, override confirmed active) and a --locked build, so this set IS reproducible from the repo from here on. WHAT MOVED: 12,635 of 40,448 bins, 31.2%, worst delta 37 levels. Same numbers the investigation measured, which is the point — this is that difference being accepted rather than a new one. 62% of it is one level, the decoder noise the format was always going to carry; the tail is the peak normalisation amplifying it, and that is unchanged by this commit. BLAST RADIUS, measured against a pre-rebake build manifest: 164 staged files. 158 are the derived per-photo twins, which are gitignored and rebuilt from this index. The rest are images/histograms.json, index.html (the homepage bakes histograms into the grid since #441) and its 3 page deltas. NO /a/ asset changed, so no content-hashed URL was re-minted and no a-dict entry orphaned. One committed file: public/images/histograms.json. The peak-normalisation fragility is NOT addressed here and is the open item: a stable denominator would make the bake robust to the decoder differences histogram.rs already documents as unavoidable. That changes the format and every histogram once more, so it stays a separate decision. photos:check reports 158 photos, 632 hashed tiers, index in bijection. 307 contract tests, lint, typecheck, tools:check, pages:check, perf-budget and the 136-route oracle all clean.
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
… from it Eight sites across six photo and asset scripts find their output with `$SCRIPT_DIR/..`. That was the served tree when these lived in www/scripts/; it has been tools/ since #452 and #453, so extract-photo-metadata.sh was writing tools/images/metadata.json, hash-thumbnails.sh was hashing into tools/i/, and add-car-photo.sh was aiming at tools/cars. add-photos.sh passed `--root "$PROJECT_DIR/www"`, and www/ stopped existing on 2026-08-18. A rename sweep greps for the old string, and there is no old string here, only a relative expression whose meaning is a function of where the file sits. Two things then kept it silent: `zenc histogram` prints "cannot read" and exits 0, and both callers pipe it to `tail -1`, so the bake skips without failing. And the two scripts resolving through $PROJECT_DIR were correctly updated by the split, which left the pipeline looking half-migrated rather than broken. No photo has been added since the move, so nobody ran into any of it. The same rename left build.mjs's taste exemption anchored on /^www\/(garage| lwe)\//, matching nothing, so the demo pages it exists to exempt lost the exemption and the build printed 14 warnings on every run since 2026-08-18. A warn block nobody can clear is how a warn block gets ignored. Re-anchored on the SERVED_SOURCES roots. Controls both ways: an injected @font-face url() on a garage page still hard-fails, and a 99px radius there is silent again. lens-seed.mjs still spawned execFileSync("pnpm", ["exec", "wrangler", ...]), which refuses outright on a bun tree. The contract test that bans package manager spawns carried lens-seed.mjs as its one RECORDED exception, justified as driving a package script rather than using a manager to reach another binary. That described no code in the file. So the single genuine offender was the single exempted file and the suite reported green. The spawn goes through wranglerCommand() now and the allowlist is gone; a control confirms the guard catches what it used to exempt. Also dead or wrong, removed or corrected: - explorerChrome(), exported and called by nothing anywhere, including its own file - an isCallable doc comment orphaned fifteen lines above its function - build.mjs told you to run node public/scripts/build-histogram-index.mjs, which is not a path - check-wrangler.mjs told you to run `pnpm install`. Its transitive-copy scan reads node_modules/.pnpm and so reports 0 forever on a bun tree; the logic is unchanged and the blind spot is now named at the code - MAINTENANCE.md documented `zenc histogram --root www` and two .ts modules as .js CLAUDE.md counts re-derived from the tree: 36 to 37 documents, 46 to 49 test files, 117 to 147 relative specifiers, 474 to 632 hashed tiers (the 200px tier from #441 was never written down), 2645/48 to 2921/50 link refs. Gotcha 16 named contract-tests.mjs under `node --test`; both runners were measured, and the rule holds while both of its symptoms changed, so the documented error string now finds nothing and the blast radius is one file rather than the run. Gotcha 39 records the $SCRIPT_DIR/.. class so the next rename has something to check against. 353 tests, typecheck, lint, build, pages, photos and the 142-route oracle all green.
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
… from it (#525) Eight sites across six photo and asset scripts find their output with `$SCRIPT_DIR/..`. That was the served tree when these lived in www/scripts/; it has been tools/ since #452 and #453, so extract-photo-metadata.sh was writing tools/images/metadata.json, hash-thumbnails.sh was hashing into tools/i/, and add-car-photo.sh was aiming at tools/cars. add-photos.sh passed `--root "$PROJECT_DIR/www"`, and www/ stopped existing on 2026-08-18. A rename sweep greps for the old string, and there is no old string here, only a relative expression whose meaning is a function of where the file sits. Two things then kept it silent: `zenc histogram` prints "cannot read" and exits 0, and both callers pipe it to `tail -1`, so the bake skips without failing. And the two scripts resolving through $PROJECT_DIR were correctly updated by the split, which left the pipeline looking half-migrated rather than broken. No photo has been added since the move, so nobody ran into any of it. The same rename left build.mjs's taste exemption anchored on /^www\/(garage| lwe)\//, matching nothing, so the demo pages it exists to exempt lost the exemption and the build printed 14 warnings on every run since 2026-08-18. A warn block nobody can clear is how a warn block gets ignored. Re-anchored on the SERVED_SOURCES roots. Controls both ways: an injected @font-face url() on a garage page still hard-fails, and a 99px radius there is silent again. lens-seed.mjs still spawned execFileSync("pnpm", ["exec", "wrangler", ...]), which refuses outright on a bun tree. The contract test that bans package manager spawns carried lens-seed.mjs as its one RECORDED exception, justified as driving a package script rather than using a manager to reach another binary. That described no code in the file. So the single genuine offender was the single exempted file and the suite reported green. The spawn goes through wranglerCommand() now and the allowlist is gone; a control confirms the guard catches what it used to exempt. Also dead or wrong, removed or corrected: - explorerChrome(), exported and called by nothing anywhere, including its own file - an isCallable doc comment orphaned fifteen lines above its function - build.mjs told you to run node public/scripts/build-histogram-index.mjs, which is not a path - check-wrangler.mjs told you to run `pnpm install`. Its transitive-copy scan reads node_modules/.pnpm and so reports 0 forever on a bun tree; the logic is unchanged and the blind spot is now named at the code - MAINTENANCE.md documented `zenc histogram --root www` and two .ts modules as .js CLAUDE.md counts re-derived from the tree: 36 to 37 documents, 46 to 49 test files, 117 to 147 relative specifiers, 474 to 632 hashed tiers (the 200px tier from #441 was never written down), 2645/48 to 2921/50 link refs. Gotcha 16 named contract-tests.mjs under `node --test`; both runners were measured, and the rule holds while both of its symptoms changed, so the documented error string now finds nothing and the blast radius is one file rather than the run. Gotcha 39 records the $SCRIPT_DIR/.. class so the next rename has something to check against. 353 tests, typecheck, lint, build, pages, photos and the 142-route oracle all green. Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
…erved #394 re-encoded 316 JPEG thumbnails on 2026-08-14, minting a new content hash for each, and re-baked no histograms. images/histograms.json has described thumbnails nobody is served ever since. All 158 photos move here: 31.2% of the 40,448 bins, mean 0.95 of 100, but 65 photos carry at least one bin past 15 and the worst is 59, so this is a visible bar rather than decoder noise. The decoder was the obvious suspect and is innocent. image 0.25.10 and zune-jpeg 0.5.15 are pinned identically at every commit involved, and Cargo.lock has not moved since the pipeline was relocated. What changed is the INPUT, which is the one thing a version pin cannot hold still: the j-tier hash for L1000069_3 went 0e092e5f -> 2b30aedc in #394, and the bars stayed. WHY NOTHING CAUGHT IT, which is the more useful half. photos:check compares histograms.json against images/meta/. Since #441 made meta/ build OUTPUT that build.mjs derives FROM histograms.json, those two agree no matter what the JPEGs contain. It is gotcha 24's shape: two values from one source, so the check proves that copying works. zenc histogram --check does read the pixels, and pointing it at a built tree is guaranteed to report drift whether or not any exists. The index packs each bin into ONE character over 64 levels while zenc computes 0..100, so a build-derived meta/ has been through a lossy 101 -> 64 -> 101 round trip. Measured: the derived tree reproduces roundTrip(committed) on 100.00% of 40,448 bins, so the packing is faithful and that drift was an artifact of where it was pointed. The real signal was underneath it and needed the pre-#441 committed meta to see. THE FIX AND THE GUARD. The data: materialise meta/, re-run zenc against the current j-tier, roll it back through build-histogram-index. The result satisfies zenc's own check ("histograms match on disk for 158 photos") and round-trips into the built meta/ at 256 of 256 bins for a spot-checked stem. The guard: hash-thumbnails.sh snapshots each stem's j hash and warns when one moves, naming the re-bake. That script is the choke point where new bytes become served bytes. add-photos.sh re-bakes the whole library every run and was never at risk; the gap is a standalone reencode-thumbnails.sh, whose own header tells you to re-run hash-thumbnails.sh and stops there. Controlled both ways: a no-op run stays silent, and a stem whose JPEG bytes change prints the warning naming it. The control mutated real files and the tree was restored to 632 tracked / 632 on disk afterwards. 353 tests, typecheck, lint, build, photos:check and the 142-route oracle green.
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
…erved (#535) #394 re-encoded 316 JPEG thumbnails on 2026-08-14, minting a new content hash for each, and re-baked no histograms. images/histograms.json has described thumbnails nobody is served ever since. All 158 photos move here: 31.2% of the 40,448 bins, mean 0.95 of 100, but 65 photos carry at least one bin past 15 and the worst is 59, so this is a visible bar rather than decoder noise. The decoder was the obvious suspect and is innocent. image 0.25.10 and zune-jpeg 0.5.15 are pinned identically at every commit involved, and Cargo.lock has not moved since the pipeline was relocated. What changed is the INPUT, which is the one thing a version pin cannot hold still: the j-tier hash for L1000069_3 went 0e092e5f -> 2b30aedc in #394, and the bars stayed. WHY NOTHING CAUGHT IT, which is the more useful half. photos:check compares histograms.json against images/meta/. Since #441 made meta/ build OUTPUT that build.mjs derives FROM histograms.json, those two agree no matter what the JPEGs contain. It is gotcha 24's shape: two values from one source, so the check proves that copying works. zenc histogram --check does read the pixels, and pointing it at a built tree is guaranteed to report drift whether or not any exists. The index packs each bin into ONE character over 64 levels while zenc computes 0..100, so a build-derived meta/ has been through a lossy 101 -> 64 -> 101 round trip. Measured: the derived tree reproduces roundTrip(committed) on 100.00% of 40,448 bins, so the packing is faithful and that drift was an artifact of where it was pointed. The real signal was underneath it and needed the pre-#441 committed meta to see. THE FIX AND THE GUARD. The data: materialise meta/, re-run zenc against the current j-tier, roll it back through build-histogram-index. The result satisfies zenc's own check ("histograms match on disk for 158 photos") and round-trips into the built meta/ at 256 of 256 bins for a spot-checked stem. The guard: hash-thumbnails.sh snapshots each stem's j hash and warns when one moves, naming the re-bake. That script is the choke point where new bytes become served bytes. add-photos.sh re-bakes the whole library every run and was never at risk; the gap is a standalone reencode-thumbnails.sh, whose own header tells you to re-run hash-thumbnails.sh and stops there. Controlled both ways: a no-op run stays silent, and a stem whose JPEG bytes change prints the warning naming it. The control mutated real files and the tree was restored to 632 tracked / 632 on disk afterwards. 353 tests, typecheck, lint, build, photos:check and the 142-route oracle green. Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
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.
Five commits on the homepage's photo path: what the grid downloads, what a hover
costs, and how many representations of the same facts the repo carries.
Every number here was measured in a real headful browser or against the live
encoder, not inferred.
1. The grid shipped 2.3x more pixels than it can show
The tile is a fixed 184px column and every visitor got the 400px file. Measured
at each DPR:
Images were 81% of the load, so the 1x case is ~40% off the whole page. The 600px
tier already existed, committed at 3,257 KiB, and nothing served it.
Why srcset is not the
<picture>this file twice removed.<picture>selects on TYPE and leaves a loser to be instantiated: a Chromium hover capture
on 2026-08-11 caught 13 JPEG loads for one tile. srcset selects on SIZE and
resolves to one candidate. I ran that control before writing any of it and again
on the finished page: 12 tiles, two full hover passes, DPR 1/2/3, zero image
loads beyond the initial twelve, identical to a single-URL control. All three
candidates are AVIF, so no type selection happens anywhere.
Additive by construction, which was most of the work. An
/i/URL names itsbytes, so re-encoding an unchanged tier remints its hash, rewrites every page
referencing it, and orphans the a-dict and p-dict snapshots built against it.
reencode-thumbnails.shtakesTIERS=xs; the backfill ran from canonicalsources with 0 existing tiers lost, 0 changed, 474 -> 632 files.
Two bugs fixed on the way, both of which would have hashed zero photos or
silently dropped tiers:
hash-thumbnails.shdiscovered stems from.jpgonly,and its per-stem write ASSIGNED the entry where the file's own header calls it a
merge.
2. A hover cost a round trip
Bars fetched
/images/meta/<stem>.jsonand could not draw until it landed:135ms then 117ms on production, once per photo. They ride on the tile now.
Control: with
/images/meta/*aborted outright, bars drew on 6 of 6 hovers.The same six hovers cost 6 requests before and 3 after, and the 3 that remain are
TEXT racing the shared EXIF index, never bars.
It had to be in the markup. Warming from
tooltip.jswas the cheaper build andcannot work:
index.htmlloads that module ON THE FIRST HOVER, deliberately("visitors who never hover transfer no tooltip JavaScript"), so anything it warms
is too late for the hover that caused the load. I wrote the warm first and
measured 0 warmed requests before any hover, which is what sent me to the loader.
Sourcing it is one memoised ASSETS read per isolate, like
alt.jsonalready is.Bundling all 158 costs 22.9 KiB gzip, 83% of the bundle's headroom; reading
twelve files per request is twelve subrequests against a 50 cap (gotcha 36).
Two real bugs this caught in the previous commit, both on the AVIF decode
repair and both silent: its stem regex spelled
(?:-400)?so a failing 200pxtile matched nothing, and it assigned
image.srcwhilesrcsetwas still set,where srcset wins and the browser re-picks the AVIF it just failed to decode.
3. Album art at q63
/rn/artasked Cloudflare for quality 82 while the photo pipeline gives avifenc-q 63: 0.323 bytes per pixel against 0.066. The numbers are not comparable across
encoders, so the sweep is the justification. Measured against the REAL Cloudflare
AVIF encoder on four covers from the live playlist, ssimulacra2 at the 120px size
the art is DISPLAYED at:
About 27% off. The floor worth comparing against is what this site already ships:
the 200px photo tier every DPR-1 visitor now gets measures 78-80 at ITS display
size, so q63 art sits above the grid beside it.
Judge at display size. The same sweep at the encoded 240px reads ~5 points
harsher and puts q63 at 67.9, which looks like real degradation and is measuring
pixels nobody is shown. ART_VERSION goes to 2, which is not optional: those
responses are immutable for a year.
4. Base64 was fighting brotli
data-histwas 68% of the 12-tile fragment. Base64 packs three bytes into fourcharacters, destroying the byte alignment brotli's context modelling exploits,
and histogram bins are smooth.
704 br off every homepage. The 5-bit variant wins by 23 bytes and costs a
bit-packing loop at both ends, which is a bad trade for a value decoded on a
hover.
ASCII 63..126 holds none of
& < > "(34, 38, 60, 62, all below the range), soescaping is a no-op by construction. It DOES hold a backtick, which forces
minify-html to keep the quotes: 7 of 12 tiles quoted, 5 unquoted, all 12 intact
at 256 characters. Worst round-trip error is 1 of 100 against all 158 originals,
and the SVG is 32 units tall, so one level is half a pixel. The decode drops from
atobplus a loop tocharCodeAtminus 63.5. One representation of the photo data was redundant
images/meta/was 158 committed files that are a pure projection of twoother committed files. Verified 158/158, text half and histogram half both exact.
Same argument the Markdown twins won. They are build output now: 158 files and
151 KiB leave the tree, the URL contract does not move, and
routes:checkprovesit by asserting
/images/meta/<stem>.jsonanswers 200 against the BUILT tree —confirmed with the source directory removed entirely.
The drift checks are tiered rather than deleted. Where
images/meta/exists(a workstation that just ran the pipeline, the only place these indexes can go
stale) both are rebuilt from it and any disagreement fails. Where it does not,
the check says so and verifies coverage instead. Controlled both ways: absent
skips and exits 0, present-and-drifted fails by name and exits 1. Nothing is lost,
because the drift it guarded was two committed copies disagreeing and there is
one copy now.
Measured and deliberately not done
hashes.jsonis 100% derivable fromfingerprints.json(632/632 tiers, hash8is the digest prefix). Deriving it means bundling 52 KB instead of 11.5 KB into
the hot path.
render uses 12 of 158. Saves module memory only.
thumb_jpglooked unused by the grid;/photosrenders it, so it stays.data-uploadedships full ISO with milliseconds where the formatter rendersHH:MM. Worth ~20 br, and it is not dead: it is the date shown beforeexif.jsonlands.Verification
Build, 292/292 contract tests, lint (oxlint incl. type-aware), typecheck,
photos:check,pages:check,routes:check(136 routes),perf-budget, andwrangler deploy --dry-runall green.Expect a large diff: 158 new AVIF files for the 200px tier, and 158 deletions for
the retired
meta/tree.