fix(r2): the heid code-review panel (round "Wren", 4/4) — triaged and folded
Code fixes: - The narrow-screen fold was specified and never built (4/4). The tray and notes are now closed <details> in the aside; above 1000px CSS alone (::details-content) shows them and hides the summary. There is no script. Browser-tested at 390 and 1400, JS on and off. - The lightbox gated on parsed board rows, not page identity (3/4). It now uses is_board, the lesson the bench panel already carried. - wants_json returned True at the first good entry, so a malformed later entry was never read (3/4). It now parses every entry first; any error is False. - One flag predicate, flagged_targets. It serves the Desk count, the tray, the filmstrip, the tape and the review button. An unreadable flag entry counts nowhere. - The header's open count and lifetime line, and the no-set marks panel, are now regions (they were stale after an in-place answer). - Inline group headers render only when every group is one contiguous run. Interleaved directories no longer reprint or misfile headers. - A booth held unreadable has no open_since, even with a readable pick beside the damage. - The swap marks an absent region is-stale instead of leaving it looking current. It carries disclosure state (except the sent form's). The failure message is readable for 0.9 s before the reload. Contract amended where the code was right and the text was not: the wants_json and record_seen signatures, landed_at's three refinements, the group position being ring-based, the end of the set offering every other open pick, the Space-key player exception, and the fold mechanism. New tests cover the parse order; a board with media; the header region; the no-set panel; interleaved groups; mixed damage; the flag predicate; the review recording .viewed; the fold at two widths with JS on and off; the status message before the reload; a lost response after a landed write (exactly one note); a stale absent region; stage node identity across a swap; and F with a radio focused. The lost-response and stale tests turn red under their mutations. 724 passed.
This commit is contained in:
+48
-12
@@ -419,21 +419,48 @@ def wants_json(accept: str | None) -> bool:
|
||||
"""
|
||||
if not accept:
|
||||
return False
|
||||
# EVERY entry is parsed before anything is decided: returning True at the
|
||||
# first good JSON entry meant a malformed one after it was never read, so
|
||||
# `application/json, application/json;q=broken` was a 204 and the same pair
|
||||
# reversed a 303 (heid code-review, 3/4). One unparseable entry anywhere
|
||||
# makes the whole header False.
|
||||
wanted = False
|
||||
try:
|
||||
for entry in accept.split(","):
|
||||
mtype, *params = entry.split(";")
|
||||
if mtype.strip().lower() != "application/json":
|
||||
continue
|
||||
q = 1.0
|
||||
for param in params:
|
||||
key, _, value = param.partition("=")
|
||||
if key.strip().lower() == "q":
|
||||
q = float(value.strip())
|
||||
if q > 0:
|
||||
return True
|
||||
if mtype.strip().lower() == "application/json" and q > 0:
|
||||
wanted = True
|
||||
except ValueError:
|
||||
return False
|
||||
return False
|
||||
return wanted
|
||||
|
||||
|
||||
def flagged_targets(marks: Sequence[Mark]) -> set[str]:
|
||||
"""THE flag predicate (R2): the items carrying a READABLE flag mark. The
|
||||
Desk count, the tray, the filmstrip, the tape and the review button all
|
||||
read this, so they cannot disagree about one item. An unreadable flag
|
||||
entry is judgment nobody can see, and counts nowhere."""
|
||||
return {m.target for m in marks
|
||||
if m.shape == "flag" and m.error is None and m.target}
|
||||
|
||||
|
||||
def _contiguous(keys: Sequence[str | None]) -> bool:
|
||||
"""True when each non-None key occupies ONE unbroken run of the sequence."""
|
||||
seen: set[str] = set()
|
||||
prev = object()
|
||||
for k in keys:
|
||||
if k != prev:
|
||||
if k is not None and k in seen:
|
||||
return False
|
||||
if k is not None:
|
||||
seen.add(k)
|
||||
prev = k
|
||||
return True
|
||||
|
||||
|
||||
# The Desk shows this many bookmarks and links to the board for the rest.
|
||||
@@ -615,8 +642,12 @@ def list_booths(data_dir: Path, ttl_seconds: float, now: float | None = None) ->
|
||||
"expires_in": max(0.0, ttl_seconds - (now - mtime)),
|
||||
"mtime": mtime,
|
||||
# ---- R2 C4, the Desk. All from the pass above; no second read.
|
||||
"open_since": min(stamps) if stamps else None,
|
||||
"flags": sum(1 for m in marks if m.shape == "flag"),
|
||||
# A booth held for UNREADABLE marks has no `open_since`, even if
|
||||
# a readable pick sits beside the damage: it sorts after every
|
||||
# dated question, because the damage is what needs fixing.
|
||||
"open_since": (min(stamps) if stamps and hold != HOLD_UNREADABLE
|
||||
else None),
|
||||
"flags": len(flagged_targets(marks)),
|
||||
# Two clocks, named apart (INV-5): `mtime` is activity,
|
||||
# `landed_at` is content. "New since you looked" reads only the
|
||||
# second, so a flag or a view never makes a booth look new.
|
||||
@@ -1142,8 +1173,14 @@ def create_app(
|
||||
# R2 C5: the flag tray — the flagged items in SET order, i.e.
|
||||
# by ordinal, over the full gallery (a filter hides tiles, not
|
||||
# judgments). Order is total with no tie-break: rels are unique.
|
||||
"tray": [it for it in gallery if it["name"] in {
|
||||
m.target for m in marks if m.shape == "flag" and m.error is None}],
|
||||
# Inline group headers only when every group is ONE contiguous
|
||||
# run in the rendered order. Groups come from basenames and the
|
||||
# order from full paths, so they can interleave (d1/aa, d1/bb,
|
||||
# d2/aa); a header then either repeats or files an item under
|
||||
# the wrong group. The rail's jump links do not depend on this.
|
||||
"inline_groups": bool(rail["groups"]) and _contiguous(
|
||||
[it["group"] for it in shown]),
|
||||
"tray": [it for it in gallery if it["name"] in flagged_targets(marks)],
|
||||
"ord_width": len(str(len(gallery))),
|
||||
"uploaded": (booth / UPLOAD_MARKER).exists(),
|
||||
# The same provenance line the index card carries. Deliberate:
|
||||
@@ -1703,7 +1740,7 @@ def create_app(
|
||||
# notes and the flag state travel to full size, which is the size at
|
||||
# which the judgment is actually being made.
|
||||
"marks": item_marks,
|
||||
"flagged": any(m.shape == "flag" for m in item_marks),
|
||||
"flagged": f in flagged_targets(marks),
|
||||
}
|
||||
|
||||
if item is not None and item.kind in REVIEW_KINDS:
|
||||
@@ -1718,8 +1755,7 @@ def create_app(
|
||||
if len(ring) > 1:
|
||||
prev_url = quote(ring[(pos - 1) % len(ring)], safe="/")
|
||||
next_url = quote(ring[(pos + 1) % len(ring)], safe="/")
|
||||
flagged_rels = {m.target for m in marks
|
||||
if m.shape == "flag" and m.error is None}
|
||||
flagged_rels = flagged_targets(marks)
|
||||
# recorded above, before this read: the current item counts as seen
|
||||
seen = read_seen(booth) & set(ring)
|
||||
film = [{"name": r, "url": by_rel[r].url, "ordinal": by_rel[r].ordinal,
|
||||
|
||||
Reference in New Issue
Block a user