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
+10 -9
View File
@@ -771,7 +771,13 @@ def test_sentinel_is_not_counted_as_an_item(tmp_path):
assert booth["count"] == 1
def test_index_separates_kept_from_ephemeral(client):
def test_index_marks_kept_on_the_row_instead_of_a_lane(client):
"""R2 C4 (docs/contracts/r2_flow.contract.md, "Assertions that change").
This test used to require a kept LANE rendered before the ephemeral grid.
The Desk removed the lanes — 23 of 24 live booths were kept, so they sorted
nothing — and orders by what needs the operator instead (tested in
tests/test_flow.py). What survives is the fact: a kept booth still says it
is kept, on its own row."""
c, data = client
_touch(data / "scratch" / "a.png")
_touch(data / "links" / "a.png")
@@ -779,14 +785,9 @@ def test_index_separates_kept_from_ephemeral(client):
html = c.get("/").text
# Assert on the lane's markup, not on the word "Kept" — that string also
# appears in the stylesheet comment that is served on every page, so a bare
# substring check passes for the wrong reason.
assert 'class="grid kept-grid"' in html, "kept booths need their own lane"
assert 'class="card card-kept"' in html
# The kept lane is rendered before the ephemeral grid, so the operator sees
# durable boards first rather than hunting for them among the churn.
assert html.index("links") < html.index("scratch")
assert 'data-booth="links" data-kept="1"' in html
assert 'data-booth="scratch" data-kept="0"' in html
assert 'class="grid kept-grid"' not in html, "no lane: kept is a fact, not a grouping"
def test_kept_booth_shows_kept_instead_of_a_countdown(client):