feat(r2): C4 the Desk — the index triaged by what needs the operator

- list_booths gains open_since (parsed, never compared as text), flags,
  landed_at (content only; a new, differently named clock, INV-5),
  viewed_at, and a four-image preview that keeps blur.
- The index renders needs you / new since you looked / everything else,
  always in that order. Needs you includes unreadable marks, so a damaged
  judgment file cannot hide. Everything else keeps list_booths' order
  rather than stating a second rule. An empty section renders nothing.
- The side column holds live benches (a damaged registry says so),
  bookmarks from BOOTH_LINKS_BOARD with booth URLs left out (capped at 8),
  and the pickup form.
- test_booth's kept-lane test is rewritten as the contract declared: kept
  is a fact on each row, not a lane.

Two of the new tests were VACUOUS on their first draft, and mutation-
checking caught both. The clocks test used a future t0, so a hand-set
marker outranked every real write. The look-then-judge test followed the
flag's 303, and the resulting GET recorded a fresh look. Both are fixed
and now go red under their mutation.
This commit is contained in:
vh
2026-09-23 08:45:54 -07:00
parent b9750d221a
commit ce27b06f32
6 changed files with 545 additions and 162 deletions
+7 -4
View File
@@ -31,7 +31,7 @@ touches:
- "booth/templates/base.html (layout CSS; the in-place script)"
- "booth/templates/doc.html (NOT restructured — a doc keeps its reading page; named because it was checked)"
- "booth/static/embed.js (NOT TOUCHED — the verbatim path keeps its author's layout; requirement 6)"
- "tests/test_booth.py (TWO assertions change: L785-786, the kept-lane presence pair. L810-811, the absence pair, survive unchanged. See 'Assertions that change')"
- "tests/test_booth.py (THREE assertions change, all in test_index_separates_kept_from_ephemeral: L785-786, the kept-lane presence pair, and L789, kept-before-ephemeral. L810-811, the absence pair, survive unchanged. See 'Assertions that change')"
- "tests/test_flow.py (NEW)"
assumptions:
- "ONE VIEWER. `.seen` records what has been seen at full size, not WHO saw it. ROADMAP parks 'per-viewer state (who has seen what)' on the one-viewer premise; this contract keeps that premise and does not reopen the parked item."
@@ -220,8 +220,10 @@ rule — a second renderer in JavaScript would be the same bug in a new language
have no `open_since` and sort after every booth that has one.
2. **New since you looked** — `not in_needs_you and (viewed_at is None or
landed_at > viewed_at)`. Ordered by `(-landed_at, name)`, newest first.
3. **Everything else** — ordered by `(-mtime, name)`, where `mtime` is today's
`_newest_mtime`: last activity first.
3. **Everything else** — in `list_booths`' own existing order: `(mtime, name)`
descending, where `mtime` is today's `_newest_mtime`. That is last activity
first, with name as the tie-break (`test_the_index_order_has_a_tie_breaker`
pins it). The Desk reuses that rule rather than stating a second one.
- Flagging or viewing a booth moves it up this section. That is intended:
it is activity. It never moves the booth into (2), because (2) reads
`landed_at` (INV-5).
@@ -346,7 +348,7 @@ This applies to image, video and audio items. Docs keep `doc.html`.
| Desk sections | fixed: needs → new → everything |
| needs you | `(open_since, name)` |
| new since you looked | `(-landed_at, name)` |
| everything else | `(-mtime, name)` |
| everything else | `list_booths` order: `(mtime, name)` descending |
| bookmarks | `order_for_display` |
The notes list keeps `(created, id)`.
@@ -387,6 +389,7 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|---|---|---|---|
| test_booth.py L785 | `class="grid kept-grid"` present when a booth is kept | absent; the kept booth appears in its Desk section with the `kept` lifetime line | requirement 8: the lanes sort nothing |
| test_booth.py L786 | `class="card card-kept"` present | replaced by the row carrying `data-kept="1"` | same |
| test_booth.py L789 | the kept booth renders BEFORE the ephemeral one (`html.index("links") < html.index("scratch")`) | replaced by the Desk's stated order (needs → new → everything, each with its own key) | the kept-first order was the lane's; with no lane there is no kept-first rule, and a second hidden ordering would break INV-2 |
| test_booth.py L810-811 | lane absent when nothing is kept | these two SURVIVE unchanged (they assert absence and stay true) | — |
Every other existing assertion is expected to survive, and one of the TDD