feat(u7): the rail, the filters and the grid keyboard — the ratified three
ROADMAP's U7 row names four components. Three of them -- a sticky rail, filters, and grid keyboard -- are already ratified there and are implemented here. The fourth, replacing directory sections with filename-derived groups, is a scope DEPARTURE the operator has not ruled on and is deliberately not built; test_no_group_rail_is_shipped_yet fails the moment somebody builds it anyway, so it cannot arrive by accident while he is away. Filters are links carrying a query parameter, resolved server-side, so the gallery keeps working with JavaScript off -- U3 already cost the verbatim path its no-JS operation and said so, and the gallery is the surface the operator actually reviews on. An unknown filter falls back to `all` rather than indexing a dict by a value that arrives from an operator-editable URL. `unanswered` means HAS AN OPEN PICK, the U4 hold predicate that already exists. The other reading is a real and different question and stays open on the contract rather than being guessed at. Filtering is a VIEW and never reorders. The grid renders `sorted(rel)` with non-matching items removed, so "the third one" means the same thing with a filter on as with it off, and the zoom ring is untouched by any filter -- a ring that changed with the grid would make `next` depend on how the operator arrived, which is the misfiled-judgment failure invariant 6 exists for. ⚠ The first version of that invariant's test was VACUOUS and the mutation run caught it: it compared each filtered view against the unfiltered RESPONSE, so a reversing mutation reversed both sides and it stayed green under the exact change it forbade. Rewritten against an independent truth -- U1 INV-3 says the order IS sorted(rel) -- and re-verified RED. Written an hour after the entry describing this exact failure class, which is worth recording. 611 -> 623 tests.
This commit is contained in:
+50
-3
@@ -859,7 +859,7 @@ def create_app(
|
||||
return RedirectResponse(url=f"/b/{quote(name, safe='')}/", status_code=307)
|
||||
|
||||
@app.get("/b/{name}/", response_class=HTMLResponse)
|
||||
def booth_view(request: Request, name: str, download: int = 0):
|
||||
def booth_view(request: Request, name: str, download: int = 0, filter: str = "all"):
|
||||
booth = resolve_booth(name)
|
||||
# U4: viewing is activity. ABOVE both early returns — the zip download
|
||||
# and the verbatim-index.html branch are looks at this booth too, and a
|
||||
@@ -902,6 +902,7 @@ def create_app(
|
||||
held_marks, read_err = hold_read(booth) # ONE read; see list_booths
|
||||
hold = hold_reason(held_marks, read_err)
|
||||
marks = held_marks if read_err is None else marks_for(booth)
|
||||
rail, shown = _rail(gallery, marks, filter)
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"booth.html",
|
||||
@@ -912,7 +913,16 @@ def create_app(
|
||||
# The page could not previously tell keep from release, so it
|
||||
# offered neither and you had to go back to the index.
|
||||
"kept": is_kept(booth),
|
||||
"items": gallery,
|
||||
# THE GRID RENDERS `shown`; everything else reads `gallery`.
|
||||
# Filtering is a VIEW: `shown` is `gallery` with non-matching
|
||||
# items removed and NOTHING re-sorted, so "the third one" means
|
||||
# the same thing with a filter on as with it off. Sorting by
|
||||
# anything filter-derived would look right and silently misfile
|
||||
# the operator's judgment — CLAUDE.md invariant 6.
|
||||
"items": shown,
|
||||
"all_items": gallery,
|
||||
"rail": rail,
|
||||
"filter": rail["active"],
|
||||
# A booth carrying links.md is the standing link board: render
|
||||
# its rows as real UI (link, provenance, pin, per-row + bulk
|
||||
# remove) instead of a markdown blob you can only edit by hand.
|
||||
@@ -951,7 +961,9 @@ def create_app(
|
||||
"marks": marks,
|
||||
"marks_open": len(open_marks(marks)),
|
||||
# Per-item marks, keyed by rel, so a tile reads its own judgment
|
||||
# without every tile re-filtering the whole list.
|
||||
# without every tile re-filtering the whole list. Keyed off the
|
||||
# FULL gallery, not the filtered one, so a tile hidden by the
|
||||
# current filter still has its marks if the filter changes.
|
||||
"item_marks": {
|
||||
it["name"]: marks_for_target(marks, it["name"]) for it in gallery
|
||||
},
|
||||
@@ -969,6 +981,41 @@ def create_app(
|
||||
},
|
||||
)
|
||||
|
||||
FILTERS = ("all", "flagged", "annotated", "unanswered")
|
||||
|
||||
def _rail(gallery: list[dict], marks, requested: str) -> tuple[dict, list[dict]]:
|
||||
"""Per-filter counts, and the items the grid should render.
|
||||
|
||||
`requested` ARRIVES FROM A URL, which is operator-editable and
|
||||
link-shared, so an unknown value falls back to `all` rather than
|
||||
indexing a dict by it. A filter nobody can mistype into a 500.
|
||||
|
||||
`unanswered` means HAS AN OPEN PICK — the U4 hold predicate, which
|
||||
already exists and already has a home. The other reading ("has no mark
|
||||
at all") is a genuinely different question and is an open question on
|
||||
the U7 contract, not something to guess at here.
|
||||
"""
|
||||
active = requested if requested in FILTERS else "all"
|
||||
open_ids = {m.id for m in open_marks(marks)}
|
||||
buckets: dict[str, list[dict]] = {f: [] for f in FILTERS}
|
||||
for it in gallery:
|
||||
mine = marks_for_target(marks, it["name"])
|
||||
buckets["all"].append(it)
|
||||
if any(m.shape == "flag" and m.flagged for m in mine):
|
||||
buckets["flagged"].append(it)
|
||||
if any(m.shape == "note" for m in mine):
|
||||
buckets["annotated"].append(it)
|
||||
if any(m.id in open_ids for m in mine):
|
||||
buckets["unanswered"].append(it)
|
||||
rail = {
|
||||
"active": active,
|
||||
# ORDER: the declaration order of FILTERS. Stated because a rail is
|
||||
# an ordered collection and invariant 6 binds to it like any other.
|
||||
"counts": [{"key": f, "n": len(buckets[f])} for f in FILTERS],
|
||||
"total": len(gallery),
|
||||
}
|
||||
return rail, buckets[active]
|
||||
|
||||
def _board_rows(booth: Path) -> list[dict]:
|
||||
"""The link board's rows, or [] for a board that cannot be read.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user