fix(as-S5a): fixup from booth-dev's gate — release asks, fail-closed words, ids that cannot collide

From booth-dev's hulda bug-hunt with heid's second voice (BRINGA, thread
01M3MVGQ7QSCCK8WT59TQ4J469):
- The booth page's "kept — release" asks by name, as the Desk's does.
- WORDS has no prototype: data-confirm="__proto__" or "constructor" is an
  unknown word, and asks, instead of throwing before preventDefault.
- The confirm helper moved into <head>: its capture listener exists
  before any form, so a click during load is asked too (the inline
  confirm() it replaced had that property).
- shown() also marks U+2028/U+2029 and the zero-width characters.
- Derived ids take ':' (bk-ask-<id>-<key>:prompt, bk-ask-<id>:title), which
  no id or key can contain; '-prompt' and '-title' collided with valid
  keys. booth-dev's chip test now looks its fragment up by [id=...].
- human_dur says "—" for a value that is not finite, instead of raising.
- The tile's copy of a note drops its id (booth-dev: mark-<id> is the
  panel's article).
- Four guards that asserted source patterns now also hold on computed
  effects: embed rings, rings inside clipping containers, the withdraw ×
  on both axes, and question-level notes fields.

Contract: as_antislop S5a (fixup). Falsifiers: antislop.toml 102/102 with
r2_flow.toml 24/24 proved; the full gate follows.
This commit is contained in:
vh
2026-09-28 13:58:52 -07:00
parent d4f64fd7ec
commit 7143fae6c7
9 changed files with 370 additions and 53 deletions
+118 -1
View File
@@ -188,7 +188,8 @@ def test_withdraw_buttons_are_big_enough_to_hit(browser, live):
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")
box = page.locator(".mark-x").first.evaluate(
"e => { const r = e.getBoundingClientRect(); return Math.min(r.width, r.height); }")
assert box >= floor, (ctx_args, box)
ctx.close()
@@ -231,3 +232,119 @@ def test_touch_and_scroll_behaviour(browser, live):
"e => ({ws: getComputedStyle(e).whiteSpace, r: e.getBoundingClientRect().right, vw: innerWidth})")
assert slug["ws"] == "normal" and slug["r"] <= slug["vw"], slug
page.close()
# ---- S5a fixup: the confirm helper, and the rings on COMPUTED style ------------------------
def _dialog_texts(page):
said = []
page.on("dialog", lambda d: (said.append(d.message), d.dismiss()))
return said
def test_release_on_the_booth_page_asks_in_the_browser(browser, live):
base, root = live
b = root / "kept"
b.mkdir()
(b / "x.txt").write_text("x")
(b / ".forever").write_text("")
page = browser.new_page(viewport={"width": 1280, "height": 800})
page.goto(f"{base}/b/kept/", wait_until="load")
said = _dialog_texts(page)
page.locator(".keep-lg button").click()
page.wait_for_timeout(300)
page.close()
assert said and "“kept”" in said[0] and "Release" in said[0], said
assert (b / ".forever").exists(), "dismissing must not release"
def test_a_prototype_word_still_asks(browser, live):
"""A `data-confirm` naming a property every object inherits is an unknown
word, and an unknown word asks (fail closed)."""
base, root = live
b = root / "g"
b.mkdir()
(b / "x.txt").write_text("x")
page = browser.new_page()
page.goto(f"{base}/b/g/", wait_until="load")
said = _dialog_texts(page)
words = ["__proto__", "constructor", "toString", "hasOwnProperty", "valueOf"]
for w in words:
page.evaluate("""w => { const f = document.createElement('form');
f.method = 'post'; f.action = '/b/g/delete';
f.setAttribute('data-confirm', w); f.setAttribute('data-booth', 'g');
document.body.appendChild(f); f.requestSubmit(); f.remove(); }""", w)
page.wait_for_timeout(100)
page.close()
assert len(said) == len(words), said
assert (b / "x.txt").exists()
def test_the_dialog_shows_hidden_breaks_and_zero_widths_visibly(browser, live):
import urllib.parse
base, root = live
name = "a
b
c​d⁠e"
b = root / name
b.mkdir()
(b / "x.txt").write_text("x")
page = browser.new_page()
page.goto(f"{base}/b/{urllib.parse.quote(name)}/", wait_until="load")
said = _dialog_texts(page)
page.locator(".wipe-lg button").click()
page.wait_for_timeout(300)
page.close()
assert said, "positive control: Wipe now asked"
for ch in "

​⁠":
assert ch not in said[0], (hex(ord(ch)), said[0])
assert said[0].count("�") == 5, said[0]
_RING = """e => { const cs = getComputedStyle(e);
return {fv: e.matches(':focus-visible'), style: cs.outlineStyle, width: cs.outlineWidth,
color: cs.outlineColor, offset: cs.outlineOffset}; }"""
_ALPHA = """c => { const m = c.match(/rgba?\\(([^)]+)\\)/); const p = m ? m[1].split(',') : [];
return p.length > 3 ? parseFloat(p[3]) : 1; }"""
def _keyboard_focus(page, selector):
page.keyboard.press("Shift") # keyboard modality: focus() is then :focus-visible
loc = page.locator(selector).first
loc.focus()
return loc.evaluate(_RING)
def test_rings_inside_clipping_containers_are_drawn_inside(browser, live):
from booth.benches import upsert_bench
base, root = live
g = root / "g"
g.mkdir()
(g / "a.png").write_bytes(_png(64, 48))
upsert_bench(root, "http://10.100.10.50:5173/", "peedlar desk", "peedlar-dev") # a Desk panel with a link
page = browser.new_page(viewport={"width": 1280, "height": 800})
for url, sel in (("/", ".theme button"), ("/", ".desk-panel a"), ("/b/g/", ".item a")):
page.goto(base + url, wait_until="load")
ring = _keyboard_focus(page, sel)
assert ring["fv"] and ring["offset"] == "-2px", (url, sel, ring)
page.close()
def test_the_embed_draws_visible_rings(browser, live):
from booth.marks import declare_pick
base, root = live
b = root / "r"
b.mkdir()
declare_pick(b, "winner", {"prompt": "Which?", "options": ["A", "B"]})
(b / "index.html").write_text(
'<!doctype html><html><head><script src="/_booth/embed.js" defer></script>'
'<style>*:focus{outline:none}</style></head>' # a host that removes rings
'<body><h1>Report</h1><div data-booth-ask="winner"></div></body></html>')
page = browser.new_page()
page.goto(f"{base}/b/r/", wait_until="networkidle")
page.wait_for_selector(".bk-ask-go", timeout=10000)
for sel in (".booth-nav-home", ".bk-ask-go"):
ring = _keyboard_focus(page, sel)
alpha = page.evaluate(_ALPHA, ring["color"])
assert ring["fv"] and ring["style"] == "solid" and ring["width"] == "2px" and alpha == 1, (sel, ring)
page.close()