fix(as-S5a): every control named, one h1 and a skip link, rings and hit areas
The markup and CSS half of the anti-slop interaction work. The in-place
client is untouched (that is S5b).
- Glyph-only controls carry a name: the withdraw ×s, downloads, open full
page, the viewers' ✕, the board's pin, copy and remove, the bench's
remove, the 1:1 toggle ("1:1, natural pixels"). Film-strip and tray
frames carry the file's name as sr-only text instead of reading "01".
A Desk row's wipe names its booth.
- Fields are named by aria-label, not by their placeholder.
- The inline ask's options are a radiogroup labelled by the prompt; a
single-question fieldset gets an sr-only legend; a titled ask's title
takes bk-ask-<id>-title (it duplicated the question's id).
- One h1 per page (sr-only on the Desk, review and compare), a skip link
to <main id="main">, theme-color for light and dark.
- The review tape is one picture (role=img); its segments leave the tab
order (the film strip holds the same links, named).
- Wipe now uses the Desk's delegated prompt, moved to base.html: it names
the booth and asks the kept-booth question for a kept booth.
- Embed focus rings of its own; rings drawn inside clipping containers;
the withdraw × at least 24px, 44px under a coarse pointer;
touch-action:manipulation; strips contain their overscroll; a long
slug wraps on a phone.
- A truncated why carries its full text in title; a countdown of 48h or
more reads in days.
Two r2_flow.toml rows for the confirm helper now name base.html, where
the helper moved (anchors unchanged; the gate found them drifted).
Contract: as_antislop S5a. Falsifiers: antislop.toml 86/86 proved (S1-S6, S5a);
all 12 tables 366/366 proved on this tree.
This commit is contained in:
@@ -172,3 +172,62 @@ def test_the_hazard_stripe_is_a_pseudo_element(browser, live):
|
||||
assert got["own"] == "none", f"the button paints its own stripe: {got['own'][:80]}"
|
||||
assert "repeating-linear-gradient" in got["pre"] and got["h"] == "3px" and got["content"] != "none", got
|
||||
page.close()
|
||||
|
||||
|
||||
# ---- S5a: hit areas, and Wipe now asks by name -------------------------------------
|
||||
|
||||
def test_withdraw_buttons_are_big_enough_to_hit(browser, live):
|
||||
from booth.marks import write_note
|
||||
base, root = live
|
||||
g = root / "g"
|
||||
g.mkdir()
|
||||
(g / "a.png").write_bytes(_png(64, 48))
|
||||
write_note(g, "a.png", "soft")
|
||||
for ctx_args, floor in (({"viewport": {"width": 1280, "height": 800}}, 24),
|
||||
({"viewport": PHONE, "is_mobile": True, "has_touch": True}, 44)):
|
||||
ctx = browser.new_context(**ctx_args)
|
||||
page = ctx.new_page()
|
||||
page.goto(f"{base}/b/g/marks", wait_until="load") # visible at both widths
|
||||
box = page.locator(".mark-x").first.evaluate("e => e.getBoundingClientRect().height")
|
||||
assert box >= floor, (ctx_args, box)
|
||||
ctx.close()
|
||||
|
||||
|
||||
def test_wipe_now_asks_by_name_in_the_browser(browser, live):
|
||||
base, root = live
|
||||
for name, kept in (("alpha", False), ("beta", True)):
|
||||
(root / name).mkdir()
|
||||
(root / name / "x.txt").write_text("x")
|
||||
if kept:
|
||||
(root / name / ".forever").write_text("")
|
||||
page = browser.new_page(viewport={"width": 1280, "height": 800})
|
||||
page.goto(f"{base}/b/{name}/", wait_until="load")
|
||||
said = []
|
||||
page.on("dialog", lambda d: (said.append(d.message), d.dismiss()))
|
||||
page.locator(".wipe-lg button").click()
|
||||
page.wait_for_timeout(300)
|
||||
assert said and f"“{name}”" in said[0], said
|
||||
assert ("KEPT" in said[0]) == kept, said[0]
|
||||
assert (root / name).exists(), "dismissing the prompt must not wipe"
|
||||
page.close()
|
||||
|
||||
|
||||
def test_touch_and_scroll_behaviour(browser, live):
|
||||
"""No double-tap zoom delay on a control; the film strip does not hand its
|
||||
scroll to the page at its end; a long booth slug wraps on a phone instead of
|
||||
running off the edge (measured on COMPUTED style, not the stylesheet)."""
|
||||
base, root = live
|
||||
g = root / "a-very-long-booth-name-that-runs-off-a-phone-screen-2026-09-28"
|
||||
g.mkdir()
|
||||
(g / "a.png").write_bytes(_png(64, 48))
|
||||
(g / "b.png").write_bytes(_png(64, 48))
|
||||
(g / ".booth.json").write_text('{"handle": "design-dev", "title": "Review"}') # the slug shows beside a title
|
||||
page = browser.new_page(viewport=PHONE)
|
||||
page.goto(f"{base}/b/{g.name}/view?f=a.png", wait_until="load")
|
||||
assert page.locator(".vbar a.vx").evaluate("e => getComputedStyle(e).touchAction") == "manipulation"
|
||||
assert page.locator(".film").evaluate("e => getComputedStyle(e).overscrollBehaviorX") == "contain"
|
||||
page.goto(f"{base}/b/{g.name}/", wait_until="load")
|
||||
slug = page.locator(".h1-slug").evaluate(
|
||||
"e => ({ws: getComputedStyle(e).whiteSpace, r: e.getBoundingClientRect().right, vw: innerWidth})")
|
||||
assert slug["ws"] == "normal" and slug["r"] <= slug["vw"], slug
|
||||
page.close()
|
||||
|
||||
Reference in New Issue
Block a user