The operator, on the live Desk: "how is this last activity first?" It was not,
usefully. The section sorted by `_newest_mtime`, which counts a look (`.viewed`),
so opening a booth moved it up. Tonight two post-deploy checks fetched every
booth page within half a second, which recorded 22 looks at once and collapsed
the section into reverse name order through the (mtime, name) tie-break.
Meanwhile each row shows "updated X ago", which is `landed_at`, a different
clock from the one the list was sorted by.
Operator ruling: "last activity can just be last time the booth was updated,
not necessarily operator's last activity." The section now sorts by
`(-landed_at, name)`, the date the row shows, labelled "last updated first".
Looking, flagging and blurring no longer move a booth. `list_booths` keeps its
own order for its other readers, and `_newest_mtime` still feeds lifetime.
The r2_flow contract (§3, the ordering table, INV-5) and ROADMAP's ordering row
are amended to match. Two tests and two r2_flow.toml rows cover it (25/25).
Operator rulings, 2026-09-23.
D1, the Desk row:
- Kept vs ephemeral reads at a glance: an always-visible lifetime pill in
the right column (sage ★ kept, amber held, ◷ counting down).
- The facts line is facts only.
- zip / keep|release / wipe are one cluster, with zip out of the middle.
Where a real hover exists it floats over the preview strip (covering
pictures, never information), appears on hover or keyboard focus, and
takes no room. Anywhere else (touch, any coarse pointer) it is the
row's last line, visible, with 32px controls. × hides too (the operator
answered yes).
D1b: "created 12 Sep" (filesystem birth time; nothing when unknown) and
"updated 5d ago" (the content clock), as <time> facts on the row and in
the booth header, from one macro and one clock per page.
D3, the theme toggle: System · Light · Dark in the top bar.
- Stored in localStorage and applied in <head> before any stylesheet.
- System removes data-theme, so the OS query follows the OS live, with
no listener.
- The token sheet is re-vendored at the same SVOS SHA with a scoping-only
transform (155 declarations, the same set, both directions), so forced
themes win over the OS and high contrast follows the theme in effect.
- The ask chrome inside verbatim pages follows the choice through
data-bk-theme on our own fragments, live across tabs. The host page's
<html> is never touched.
Declared test changes:
- two row tests replaced;
- the wipe-dialog test hovers first;
- four r2_flow rows retired, with successors in r2b.toml (45/45).
785 passed.
- Touch: on a coarse pointer every row control is at least 28px square
again (32px), and wipe stands clear of the zip link. The move onto the
facts line had dropped the deliberate 28px floor to ~21px, 4-6px from
zip; with scripts off no confirm fires, so a mis-tap on wipe is the
delete. The zip link no longer breaks between its glyph and its word,
and each separator is glued to the item after it.
- The wipe dialog shows the name as it should be read: control and bidi
formatting characters in an agent-made name show as U+FFFD, so U+202E
or a newline cannot rewrite what the operator approves. An unknown
data-confirm word now prompts generically instead of submitting
unguarded (fail closed).
- No page scrolls sideways: `code` wraps anywhere, so a long unbreakable
install path in the footer or the empty Desk no longer widens every
page. The overflow test now sweeps 390/720/850/1000/1400 with the
heaviest row the Desk draws, and compares scrollWidth with the page's
own clientWidth.
Its first fixture used a hyphenated path, which wrapped by itself; the
test passed with the bug present until the path became one unbreakable
run. r2_flow.toml: 27/27 proved. 749 passed.
Operator, on the live Desk: "release and x take up space whether or not
they're visible." They sat in a side column at opacity 0, which hides a
control and still reserves its box, and hover-only never worked on
touch.
Each control now sits on the facts line beside the state it changes:
release after "kept", keep after a countdown or hold, wipe last. They
are always visible and quiet, and wipe turns danger only under the
pointer or focus. The side column renders only when the row carries a
badge. The row is flex, so an absent column costs no gap. Forms, POST
targets and data-confirm wording are unchanged.
The flex row exposed a latent sizing bug: the stacked Desk column was a
bare 1fr, whose minimum is its content's, so a long nowrap provenance
line scrolled the page sideways at phone width (1029px at 390). It is
now minmax(0,1fr).
Both behaviours have browser tests, mutation-proved (r2_flow.toml:
21/21). Contract C4 amended.
tests/mutations/r2_flow.toml: 18 falsifiers, each proved RED under its
change by scripts/mutation_check.py (18/18). Its first run found three
vacuous proofs, now resolved:
- landed_at's per-entry skip: the symlink-loop fixture stopped raising
once the clock moved to lstat. New fixture: a folder that lists but
cannot be searched.
- the Desk's bench URL guard: the test covered bookmarks only. A
hand-edited registry bench now rides with it.
- flagged_targets' `error is None`: defence in depth (hydration already
strips a damaged mark's target), so no single-guard row; named in the
table header instead.
The rail's flagged filter and the orphan-flag list read flagged_targets
rather than restating it; no reachable behaviour changes.