memory: the cache that aged the thing it cached
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.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
@@ -199,6 +199,7 @@ _As of 2026-09-22:_
|
||||
|
||||
## Recent decisions
|
||||
|
||||
- `[2026-09-23]` ⚠ **The cache that aged the thing it cached** — thumbnails 77.5MB→0.78MB; READ BEFORE PARKING ANYTHING ON A MEASUREMENT (we counted images and the cost was in bytes), and BEFORE PUTTING A SERVER-WRITTEN CACHE INSIDE A BOOTH (excluding its contents does not stop it aging the booth) → `persistent-memory.d/2026-09-23-the-cache-that-aged-the-thing-it-cached.md`
|
||||
- `[2026-09-23]` ⚠ **The probe that nearly dismissed a live injection vector** — the link board rendered `javascript:` hrefs; READ BEFORE TRUSTING A NEGATIVE RESULT FROM AN OBVIOUS PROBE, and before assuming an existing scheme check is the guard you are looking for → `persistent-memory.d/2026-09-23-the-probe-that-nearly-dismissed-a-live-vector.md`
|
||||
- `[2026-09-23]` ✅ **The bug-hunt panel found six defects and five vacuous falsifiers** — READ BEFORE BUILDING ANY FRAGMENT ANCHOR (browsers match raw before decoded, so both sides must be encoded), and before trusting a well-commented diff's guards → `persistent-memory.d/2026-09-23-the-bug-hunt-panel-and-five-vacuous-falsifiers.md`
|
||||
- `[2026-09-22]` ✅ **v1.0.0b1 — the v1 target staged as a beta, and a version that was two copies** — READ BEFORE DERIVING A VERSION FROM `importlib.metadata` HERE; it reports a different artifact, and `booth/__init__.py` turns out to be stdlib-only → `persistent-memory.d/2026-09-22-v1-staged-as-a-beta-and-a-second-copy-of-the-version.md`
|
||||
|
||||
Reference in New Issue
Block a user