--- contract_version: "0.1-PROPOSED" status: "PROPOSED 2026-09-23 by design-dev. Ruled by the operator the same day in the `flow` mark on booth-flow-concepts (direction a_b; compare MODE to be built in this arc; voice plain; emblem no), relayed via Miranda → booth-dev, verbatim at docs/rulings/. Compare mode is NOT in this contract: it lands after this one as r3, as a view toggle over the same item record." module: "booth.app + booth.items + templates (the review flow)" purpose: "Make the Booth a place where judgment happens rather than a place where files are shown. The operator's bar is 'did anything change when I opened it'. A reskin cannot clear that bar; this contract changes the flow. There are three surfaces and one plumbing change. THE DESK: the index triaged by what needs the operator. THE LIGHTBOX: a booth page with the set on the left and the verdict beside it. THE REVIEW: full size with the judgment on screen, a filmstrip, and seen-tracking. The plumbing is IN-PLACE JUDGMENT: a mark POST that does not reload the page or eject you from full size." 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.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)." - "booth.app._mark_redirect (the 303 landing). Extended with one new `back` value; the existing two landings stay byte-identical." - "booth.benches.read_benches, booth.links.parse_link_entries / order_for_display / booth_target (the Desk's side column)." language: "python + jinja + a little javascript" complexity: "high" estimated_loc: 900 confidence: 0.6 used_by: - "booth.app.index (the Desk)" - "booth.app.booth_view (the lightbox)" - "booth.app.booth_view_file (the review)" - "booth.app.booth_answer / booth_note / booth_flag / booth_unmark (in-place judgment)" touches: - "booth/items.py (Item.ordinal; review_chain; read_seen/SEEN_FILE)" - "booth/app.py (list_booths fields; index sections; booth_view verdict data; booth_view_file review context + record_seen; wants_json + 204; _mark_redirect `back=view`)" - "booth/templates/index.html (REWRITTEN as the Desk)" - "booth/templates/booth.html (restructured: two panes; the marks panel moves into the verdict aside; tiles carry ordinals; inline group headers)" - "booth/templates/view.html (REWRITTEN as the review: stage, rail, filmstrip, tape)" - "booth/templates/_marks.html (renders inside the aside; flag list ordered by ordinal)" - "booth/templates/base.html (layout CSS; the in-place script)" - "booth/templates/doc.html (NOT restructured — a doc keeps its reading page; named because it was checked)" - "booth/static/embed.js (NOT TOUCHED — the verbatim path keeps its author's layout; requirement 6)" - "tests/test_booth.py (TWO assertions change: L785-786, the kept-lane presence pair. L810-811, the absence pair, survive unchanged. See 'Assertions that change')" - "tests/test_flow.py (NEW)" assumptions: - "ONE VIEWER. `.seen` records what has been seen at full size, not WHO saw it. ROADMAP parks 'per-viewer state (who has seen what)' on the one-viewer premise; this contract keeps that premise and does not reopen the parked item." - "EVERY JUDGMENT WORKS WITH JAVASCRIPT OFF. Each control stays a plain