feat(r2): C5 the lightbox, and the in-place client

- On a gallery booth the marks panel moves into a sticky verdict aside
  beside the set. The aside comes first in the document, so a narrow screen
  stacks the question above the work; grid areas place it on the right when
  wide. Nothing in an ordered collection moves. Boards are unchanged.
- The flag tray lists flagged items by tile number: the declared change
  from the panel list's (created, id). The standalone marks page keeps the
  list.
- Inline group headers are divs, never figure.item.
- Every mark-dependent element is a data-region: the verdict, each tile,
  the rail's filter counts. There is also a server-rendered status line.
- The in-place script (base.html) POSTs with an explicit JSON Accept, then
  on 204 swaps every region from a fresh GET. Live media and per-viewer view
  state are carried across the swap, so there is no layout jolt and no
  stopped track. It never re-POSTs: on failure it says so and reloads. Tile
  controls re-bind after a swap, and the grid cursor survives it.
- The `n` key opens the tile's closed note disclosure before focusing it.
- test_embed_browser's keyboard-flag test expected a navigation, which is
  the defect R2 removes. It is updated as declared in the contract, and
  tightened: a window marker must survive, proving no reload.

Browser tests: flag in place, with no reload and no scroll jump, and the
tile, tray and rail count all updated; and a failed save that reloads
without re-POSTing. Two mutations turn them red (no carry, no rail region).
700 passed.
This commit is contained in:
vh
2026-09-23 08:57:36 -07:00
parent ce27b06f32
commit 50f88a3e5e
8 changed files with 445 additions and 109 deletions
+13 -4
View File
@@ -592,17 +592,26 @@ def test_the_keyboard_flag_actually_submits(browser, live):
a hidden input does not submit its form. The shortcut never worked while
still swallowing the keystroke.
Asserted end to end: press f, and the flag must come back from the server
on the reloaded page."""
Asserted end to end: press f, and the flag must come back from the server.
R2 C3 (docs/contracts/r2_flow.contract.md, "Assertions that change"): this
used to expect a NAVIGATION — the flag form POSTed and the page reloaded.
That reload is the defect R2 removes. The claim that matters is kept and
tightened: the flag must come back from the SERVER (the swapped tile is
server-rendered), and a marker set on the window before the keypress must
survive, which a reload would wipe."""
base, root = live
_gallery(root)
page = browser.new_page()
page.goto(f"{base}/b/g/", wait_until="networkidle")
page.evaluate("window.__noReload = 1")
page.keyboard.press("ArrowRight") # cursor onto the first tile
with page.expect_navigation(): # the flag form POSTs and redirects back
page.keyboard.press("f")
page.keyboard.press("f")
page.wait_for_selector("figure.item.is-flagged", timeout=10000)
flagged = page.locator("figure.item.is-flagged").count()
survived = page.evaluate("window.__noReload === 1")
page.close()
assert flagged == 1, f"the f key flagged {flagged} items, expected 1"
assert survived, "the flag reloaded the page; in-place judgment must not"