fix(desk): "everything else" is last UPDATED first, not last activity
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).
This commit is contained in:
+1
-1
@@ -105,7 +105,7 @@ Where it already binds, and what the rule is in each case:
|
||||
| the Desk's sections | fixed: needs you → new since you looked → everything else (R2) |
|
||||
| within *needs you* | `(open_since, name)` |
|
||||
| within *new since you looked* | `(-landed_at, name)` |
|
||||
| within *everything else* | `list_booths` order: `(mtime, name)` descending |
|
||||
| within *everything else* | `(-landed_at, name)`: last updated first, the date each row shows. Was `list_booths`' activity order, which counted a look (operator, 2026-09-23) |
|
||||
| the Desk's bookmarks column | `order_for_display` — pinned first, then newest |
|
||||
| caption sidecar resolution | sorted scan, so two media files sharing a stem resolve the same way every time (a real non-determinism U1 removed) |
|
||||
| marks in a booth | `(created, id)` — time, with the id as tie-break so two marks written in the same second cannot swap |
|
||||
|
||||
+7
-2
@@ -1159,8 +1159,12 @@ def create_app(
|
||||
booth that has one; name breaks ties.
|
||||
new — content landed since the booth was last looked at, or never
|
||||
looked at. Newest content first; name breaks ties.
|
||||
rest — everything else, in `list_booths`' own order (last activity
|
||||
first, name as the tie-break). No second rule is stated.
|
||||
rest — everything else, last UPDATED first: `(-landed_at, name)`,
|
||||
the date the row shows. Not `list_booths`' activity order:
|
||||
that counts a look, so opening a booth moved it up and a
|
||||
script fetching every booth reshuffled the section into
|
||||
reverse name order (operator, 2026-09-23: "last activity can
|
||||
just be last time the booth was updated").
|
||||
The kept/ephemeral lanes are gone: 23 of 24 live booths were kept, so
|
||||
the lanes sorted nothing. Kept status still shows on every row.
|
||||
"""
|
||||
@@ -1176,6 +1180,7 @@ def create_app(
|
||||
in_new = {b["name"] for b in new}
|
||||
rest = [b for b in everything
|
||||
if b["name"] not in in_needs and b["name"] not in in_new]
|
||||
rest.sort(key=lambda b: (-b["landed_at"], b["name"]))
|
||||
benches, benches_error = read_benches(data_dir)
|
||||
board = data_dir / links_board
|
||||
bookmarks = [row for row in _board_rows(board)
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
{% endif %}
|
||||
{% if rest %}
|
||||
<section class="desk-sec" data-section="rest">
|
||||
<h2 class="desk-head">Everything else <span class="desk-rule">last activity first</span></h2>
|
||||
<h2 class="desk-head">Everything else <span class="desk-rule">last updated first</span></h2>
|
||||
{% for b in rest %}{{ row(b, 'rest') }}{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
@@ -6,7 +6,7 @@ purpose: "Make the Booth a place where judgment happens rather than a place wher
|
||||
depends_on:
|
||||
- "booth.items.booth_items + Item (INV-1: the one resolver). Item gains `ordinal`, derived there and nowhere else."
|
||||
- "booth.items.image_chain (the zoom ring). SUPERSEDED for the review route by `review_chain`; image_chain stays importable and unchanged for its existing callers and tests."
|
||||
- "booth.app._newest_mtime (THE definition of activity — booth-dev, 2026-09-23). The Desk's 'last activity' reuses it verbatim. The Desk's 'landed since you looked' is a DIFFERENT question and gets a DIFFERENTLY NAMED helper; see INV-5."
|
||||
- "booth.app._newest_mtime (THE definition of activity — booth-dev, 2026-09-23). It feeds lifetime; the Desk no longer sorts by it (amended 2026-09-23, §3). The Desk's 'landed since you looked' is a DIFFERENT question and gets a DIFFERENTLY NAMED helper; see INV-5."
|
||||
- "booth.app.record_view / VIEW_MARKER (`.viewed`, U4). The Desk reads its mtime to answer 'new since you looked'."
|
||||
- "booth.app.hold_read / hold_reason / open_marks (INV-2 of U2: the one openness predicate). 'Needs you' is `open_marks(...)` non-empty, or `hold_reason(...) == \"unreadable\"` (C4); nothing else."
|
||||
- "booth.marks.as_dict, set_flag, write_note, answer_pick, delete_mark (the write API, UNCHANGED)."
|
||||
@@ -292,13 +292,18 @@ rule — a second renderer in JavaScript would be the same bug in a new language
|
||||
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** — 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).
|
||||
3. **Everything else** — last UPDATED first: `(-landed_at, name)`, the date
|
||||
the row shows as "updated". **Amended 2026-09-23 by the operator** ("last
|
||||
activity can just be last time the booth was updated, not necessarily
|
||||
operator's last activity"). This section used to be `list_booths`' order,
|
||||
`(mtime, name)` descending over `_newest_mtime`, and that clock counts a
|
||||
look: opening a booth moved it up, and a script that fetched every booth
|
||||
(a post-deploy check) collapsed the whole section into reverse name order.
|
||||
- Flagging, viewing or blurring a booth no longer moves it. Only content
|
||||
does, which is also what moves a booth into (2).
|
||||
- `list_booths` keeps its own `(mtime, name)` order for its other readers,
|
||||
and `_newest_mtime` still feeds lifetime (INV-5). Only the Desk's
|
||||
section stopped reading it.
|
||||
|
||||
The side column holds:
|
||||
|
||||
@@ -469,7 +474,7 @@ path) is a 404, as any other unknown rel is — never a 500.
|
||||
| Desk sections | fixed: needs → new → everything |
|
||||
| needs you | `(open_since, name)` |
|
||||
| new since you looked | `(-landed_at, name)` |
|
||||
| everything else | `list_booths` order: `(mtime, name)` descending |
|
||||
| everything else | `(-landed_at, name)`: last updated first (amended 2026-09-23) |
|
||||
| bookmarks | `order_for_display` |
|
||||
|
||||
The notes list keeps `(created, id)`.
|
||||
@@ -490,7 +495,8 @@ path) is a 404, as any other unknown rel is — never a 500.
|
||||
is specified in C3 and is the one declared exception.
|
||||
- **INV-5 — two named clocks.**
|
||||
- `mtime` / `_newest_mtime`: activity. It includes dotfiles and excludes
|
||||
locks, and it feeds lifetime and 'everything else'.
|
||||
locks, and it feeds lifetime. (It fed 'everything else' until 2026-09-23;
|
||||
see §3.)
|
||||
- `landed_at`: content only (non-dot entries), and it feeds 'new since you
|
||||
looked'.
|
||||
- Never the one where the other is meant: a mark or a view is not new
|
||||
|
||||
@@ -210,3 +210,21 @@ old = '''
|
||||
new = '''
|
||||
var word = WORDS[form.getAttribute('data-confirm')];
|
||||
if (word && !confirm('''
|
||||
|
||||
[[mutation]]
|
||||
label = "everything else in activity order again (a look moves a booth up)"
|
||||
file = "booth/app.py"
|
||||
test = "tests/test_flow.py::test_everything_else_is_ordered_by_last_update_not_by_looking"
|
||||
old = '''
|
||||
rest.sort(key=lambda b: (-b["landed_at"], b["name"]))'''
|
||||
new = '''
|
||||
pass'''
|
||||
|
||||
[[mutation]]
|
||||
label = "everything else breaks an update tie by name reversed"
|
||||
file = "booth/app.py"
|
||||
test = "tests/test_flow.py::test_everything_else_breaks_an_update_tie_by_name"
|
||||
old = '''
|
||||
rest.sort(key=lambda b: (-b["landed_at"], b["name"]))'''
|
||||
new = '''
|
||||
rest.sort(key=lambda b: (b["landed_at"], b["name"]), reverse=True)'''
|
||||
|
||||
@@ -243,6 +243,36 @@ def _desk(body: str) -> dict[str, list[str]]:
|
||||
return out
|
||||
|
||||
|
||||
def test_everything_else_is_ordered_by_last_update_not_by_looking(tmp_path):
|
||||
"""Operator, 2026-09-23: "last activity can just be last time the booth was
|
||||
updated". The row shows "updated X ago" (`landed_at`), but the section
|
||||
sorted by `_newest_mtime`, which counts a look, so opening a booth moved it
|
||||
up, and a script that fetched every booth reshuffled the whole section into
|
||||
reverse name order. Defeating change: `rest` left in `list_booths` order."""
|
||||
t0 = time.time() - 100_000
|
||||
for name, landed in (("aaa-old", t0), ("mmm-mid", t0 + 250), ("zzz-new", t0 + 500)):
|
||||
b = _booth(tmp_path, name, {"a.png": PNG})
|
||||
_at(b / "a.png", landed)
|
||||
(b / ".viewed").write_bytes(b"")
|
||||
_at(b / ".viewed", t0 + 1000) # every one looked at since it landed
|
||||
_at(b, t0)
|
||||
_at(tmp_path / "aaa-old" / ".viewed", t0 + 5000) # ...and the OLDEST looked at last
|
||||
body = _client(tmp_path).get("/").text
|
||||
assert _desk(body)["rest"] == ["zzz-new", "mmm-mid", "aaa-old"]
|
||||
assert "last updated first" in body
|
||||
|
||||
|
||||
def test_everything_else_breaks_an_update_tie_by_name(tmp_path):
|
||||
"""CLAUDE.md invariant 6: two booths landed by one rsync share an mtime."""
|
||||
t0 = time.time() - 100_000
|
||||
for name in ("bravo", "alpha", "charlie"):
|
||||
b = _booth(tmp_path, name, {"a.png": PNG})
|
||||
_at(b / "a.png", t0)
|
||||
(b / ".viewed").write_bytes(b"")
|
||||
_at(b / ".viewed", t0 + 10)
|
||||
assert _desk(_client(tmp_path).get("/").text)["rest"] == ["alpha", "bravo", "charlie"]
|
||||
|
||||
|
||||
def test_the_desk_triages_needs_you_then_new_then_everything_else(tmp_path):
|
||||
"""The tracer for C4: three sections, always in this order, each booth in
|
||||
exactly one of them."""
|
||||
|
||||
Reference in New Issue
Block a user