fix(blur): .blurred round-trips any rel, and one writer serves both surfaces

The heid bug-hunt on r2b merge 1 found the /blur route stripping `f` before
writing, so the form for " a.png" blurred its neighbour "a.png". The route was
only half of it: `.blurred` was one stripped rel per line, so no writer could
store a rel with a leading space or a newline, whatever the route did.
Operator-ruled 2026-09-23 ("fix the blur").

- booth/blur.py (new, stdlib-only): read_blurred / set_blurred / BLUR_FILE.
  `.blurred` is now a JSON array in sorted order, the `.seen` shape: opened
  O_NOFOLLOW | O_NONBLOCK with an S_ISREG check and a 1 MiB cap, so a planted
  symlink is refused and a FIFO can no longer hang every Desk render (the old
  read_text() blocked on one). Writes go through mkstemp + os.replace. The
  legacy line format is still READ, so the 6 live line-format files keep their
  blur until their next write upgrades them. Measured before the change: 42
  live rels, none with edge whitespace, so the defect had no live victims.
- The route no longer strips `f`.
- scripts/booth `blur`/`unblur` go through booth.blur.set_blurred instead of
  their own grep/printf line writer. Two writers of one format is how the
  formats drift, and after this change the shell writer would have appended a
  line to a JSON array. Every path is checked before anything is written.
- Item.blurred_self (appended to the record): the item's own blur, resolved in
  booth_items from the same read as `blurred`. It replaces build_gallery's
  second read_blurred, which a write between the two reads could split
  (invariant 3). app.py no longer reads blur state at all, and a test asserts
  it.

Names stay importable from booth.app and booth.items (invariant 4). blur joins
test_stdlib_only. test_cli's per-item-survives test now reads through the reader
rather than asserting the old byte format. The r2b contract and its mutation
row follow blurred_self onto the record. tests/mutations/blur_storage.toml
proves 12 falsifiers by running the change each forbids.

Not in this change, and still ours: the "off"-means-ON idiom drift between
/blur, /blurbooth and /flag (forms only ever send 0/1), and the CLI's
`.blurbooth` touch following a symlink where the service no longer does.
This commit is contained in:
vh
2026-09-23 22:05:18 -07:00
parent cce6a20abe
commit 4cfbce5109
12 changed files with 580 additions and 89 deletions
+30 -16
View File
@@ -19,22 +19,36 @@ loop it turned out to actually be.
_As of 2026-09-23:_
- 🛑 **THE ONE THING BLOCKING: design-dev's blur merge is HELD, awaiting his
ping.** `design-dev/svos-retheme` @ `5ded5ff` (Reveal all + the booth-blur
control) is fetched, merge-tree against `091f4b5` is CLEAN, and his commit
records the contract panel 4/4 folded, 14/14 mutations, 765 passing —
**but the heid code-review and bug-hunt panels were dispatched 17:53 and are
NOT folded.** He said explicitly: *"Hold… I will ping 'merge it' when both
are folded; whoever picks it up after a clear should look for that ping before
merging."* **DO NOT MERGE IT WITHOUT THAT PING.** The operator's "merge
everything" predates this and was not about overriding his gate.
- 🔶 **After that ping: merge, restart, verify 30 booths.** Then his SECOND
merge (Desk row revisions + the dark/light/system theme toggle) follows the
same way.
- ⚠ **THE BROWSER SUITE IS FLAKY UNDER LOAD AND IT IS NOT FIXED.** Three
different tests, one failure each, all passing in isolation. Two real defects
were fixed chasing it and NEITHER is proven to be the cause. **Do not read a
green suite as proof.** Operator ruled design-dev diagnoses it properly.
- ✅ **BOTH r2b MERGES LANDED AND ARE LIVE** (operator-approved 2026-09-23):
`b92b002` (Reveal all + the booth-blur control, design-dev `ca0641f`) and
`cce6a20` (the Desk row, booth dates, the theme toggle, `1558a7f`). Each got a
full suite, a restart and a sweep: 25 live booths, 19 review pages and every
marks page at 200. ⚠ **A peer's "merge it" is not the operator's approval
here.** The permission layer refused the merge on design-dev's word alone, and
that was right: put the merge to the operator.
- 🔶 **NEXT, design-dev's: r2c, the review stage.** Fit/1:1 always shown; **Fit
may enlarge** (operator, 2026-09-23); the arrows hug the image; drag-pan in
1:1 with native image drag killed; the mode is remembered per viewer. Pan
offset across items is parked to r3 (compare). Then **r3, compare mode**:
ours is only the `booth_items` support he asks for.
- ✅ **`.blurred` ROUND-TRIPS ANY REL** (operator: "fix the blur"). A JSON array
via stdlib-only `booth/blur.py`, the one writer for both the service and
`booth blur`. The legacy line format is still read, and a write upgrades it.
The bug design-dev's bug-hunt found (a stripped rel blurring its neighbour)
had no live victims: 6 `.blurred` files, 42 rels, 0 with edge whitespace.
`Item.blurred_self` came along, so blur state has one reader (invariant 3).
**Still open and ours, not done:** the "off"-means-ON idiom drift between
/blur, /blurbooth and /flag (forms only send 0/1), and the CLI's
`.blurbooth` `touch` still follows a symlink where the service no longer does.
- ⚠ **THE BROWSER SUITE WAS FLAKY UNDER LOAD, AND THE CAUSE IS STILL
UNCONFIRMED.** design-dev's suspect: Google Fonts stalling "networkidle". He
reproduced the exact error with a stalled font request (sufficiency only).
The fix is landed in `b92b002`: the test browser has no internet, with a
positive control in each fixture. Since then, **0 reds in 24** untraced runs
against a pre-fix rate of about 1 in 8. That rate is itself 1 red in 8 runs
(95% CI roughly 0.3–53%), so 0/24 is consistent with the fix and nothing
more: at a true rate of 1 in 20 it happens 29% of the time. No trace ever
caught the stalled request. **Do not read a green suite as proof.**
→ `persistent-memory.d/2026-09-23-the-browser-suite-is-flaky-under-load.md`
- ✅ **THE REDESIGN IS LIVE.** R2 (the Desk, the lightbox, the reel) merged and
deployed; release/wipe moved onto the facts line. 30 booths at 200.