Two lessons from the thumbnail work, the second of which nearly shipped. We parked progressive loading on a count of images and the cost was in bytes. 'Measure the real booth before optimising it' was followed and still gave the wrong answer, because we measured the dimension that was easy to measure rather than the one the user feels. And a cache living inside the thing it describes can age that thing. Excluding every path under the cache dir passed its own test and was still wrong: creating the directory touches the BOOTH's own mtime, which is what _newest_mtime seeds from. The contents were excluded; the existence was the leak. Had it reached the Desk, one index load would have pushed every booth's expiry out and the TTL would never have fired again.
3.0 KiB
The cache that aged the thing it cached
2026-09-23 · booth
Thumbnails: 77.5 MB → 0.78 MB on the biggest gallery, ~100 MB → 1.12 MB on the Desk. The operator found the defect in about a minute of using the live redesign. Two lessons, and the second nearly shipped.
⚠ LESSON 1 — we parked it on a count and the cost was in the bytes
ROADMAP parked progressive loading on "the largest gallery is 66 images; at
that size a lazy grid is almost certainly fine", and the parking-lot row said
"270 <img loading=lazy> may be fine." Both count IMAGES. Neither weighs
BYTES. 66 is a fine count sitting on 77.5 MB of 1024×1024 PNGs rendered at
250px — roughly 16× the pixels that reach the screen.
The rule "measure the real booth before optimising it" was followed and still produced the wrong answer, because we measured the dimension that was easy to measure rather than the one that determines the experience. Before parking anything on a measurement again, ask what the user would actually feel.
⚠ LESSON 2 — a cache INSIDE the thing it describes can age it, and excluding the cache is not enough
.thumbs/ lives inside the booth on purpose, so it is swept with the booth and
can never outlive what it describes. But _newest_mtime — which feeds
is_expired, which feeds rmtree — walks the booth, so the server writing a
cache on a mere view counted as the operator touching the booth.
The first fix passed its own test and was still wrong. Excluding every path
under THUMB_DIR does not help, because creating the directory touches the
BOOTH DIRECTORY's own mtime, and _newest_mtime SEEDS from exactly that. The
cache's contents were excluded; its existence was the leak.
The fix restores the booth's stamp across the mkdir. That cannot hide real
activity: any file an agent adds is counted by its own mtime in the same walk,
so the directory stamp is only a seed.
The blast radius if it had reached the Desk. The Desk pulls a preview thumbnail per booth, so ONE INDEX LOAD would have pushed EVERY booth's expiry out — the TTL would never fire again and nothing would ever sweep. Caught by design-dev from the outside, hours before the strip landed; verified after the fix on the live set: 29 booths, two Desk loads, 22 caches generated, 0 clocks moved.
.viewed counting as activity is different and deliberate — that is a record of
a person looking, which U4 says is activity. A server-written derived cache
is machinery, like the .lock sidecars already excluded. Any future cache
inherits this: exclude it from the clock AND preserve the parent's stamp.
A third, smaller one: the dot-namespace was only skin deep
booth_items and zip_booth both tested p.name.startswith(".") — the FILE's
name — so .thumbs/a.png (name a.png) would have rendered as a gallery item
and shipped inside every zip download. CLAUDE.md invariant 2 promises a dotfile
costs nothing in item counts, galleries or zips; that was true only at the top
level. Both now skip every dot-prefixed path COMPONENT.