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:
@@ -375,3 +375,271 @@ file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_the_hazard_stripe_is_a_pseudo_element"
|
||||
old = """ .wipe-lg button::before,.board-del-sel:not(:disabled)::before{content:"";position:absolute;"""
|
||||
new = """ .wipe-lg button::after,.board-del-sel:not(:disabled)::after{content:"";position:absolute;"""
|
||||
|
||||
# ---- S5a: accessibility plumbing (names, landmarks, focus, hit areas)
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the withdraw-note × unnamed again"
|
||||
file = "booth/templates/_marks.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """<button type="submit" class="mark-x" title="withdraw this note" aria-label="withdraw this note">×</button>"""
|
||||
new = """<button type="submit" class="mark-x" title="withdraw this note">×</button>"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a film frame named only by its number"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """ <span class="sr-only">{{ x.name }}</span>{%- if x.kind == 'image' %}<img loading="lazy" decoding="async" src="{{ x.thumb or x.url }}" alt="">{% else %}<span class="film-kind">"""
|
||||
new = """ {%- if x.kind == 'image' %}<img loading="lazy" decoding="async" src="{{ x.thumb or x.url }}" alt="">{% else %}<span class="film-kind">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a flagged-tray item named only by its number"
|
||||
file = "booth/templates/_marks.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """<span class="sr-only">{{ it.name }}</span>{%- if it.kind"""
|
||||
new = """{%- if it.kind"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the 1:1 button read as digits"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """ aria-label="1:1, natural pixels" title="natural pixels — drag to pan a large picture">"""
|
||||
new = """ title="natural pixels — drag to pan a large picture">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the doc's open-full-page glyph unnamed"
|
||||
file = "booth/templates/booth.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """ title="open full page" aria-label="open {{ it.name }} full page">⤢</a>"""
|
||||
new = """ title="open full page">⤢</a>"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a compare's close glyph unnamed"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """ title="back to the review of A (Esc)" aria-label="back to the review of A">✕</a>"""
|
||||
new = """ title="back to the review of A (Esc)">✕</a>"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the board's pin star unnamed"
|
||||
file = "booth/templates/booth.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """ aria-pressed="{{ 'true' if e.pinned else 'false' }}" aria-label="pin {{ e.desc }}""""
|
||||
new = """ aria-pressed="{{ 'true' if e.pinned else 'false' }}""""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the board's remove × unnamed"
|
||||
file = "booth/templates/booth.html"
|
||||
test = "tests/test_antislop.py::test_every_control_has_a_word_for_a_name"
|
||||
old = """ title="remove this link" aria-label="remove the link {{ e.desc }}""""
|
||||
new = """ title="remove this link""""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a Desk wipe that does not say which booth"
|
||||
file = "booth/templates/index.html"
|
||||
test = "tests/test_antislop.py::test_desk_row_controls_name_their_booth"
|
||||
old = """aria-label="wipe the booth {{ b.name }}""""
|
||||
new = """aria-label="wipe booth""""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the note field named only by its placeholder"
|
||||
file = "booth/templates/_marks.html"
|
||||
test = "tests/test_antislop.py::test_fields_are_named"
|
||||
old = """<textarea name="text" rows="2" aria-label="a note on this booth, for the session that posted it" placeholder"""
|
||||
new = """<textarea name="text" rows="2" placeholder"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the bench URL field unnamed"
|
||||
file = "booth/templates/booth.html"
|
||||
test = "tests/test_antislop.py::test_fields_are_named"
|
||||
old = """<input type="url" name="url" aria-label="https://host:port/" placeholder"""
|
||||
new = """<input type="url" name="url" placeholder"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the embed's notes field unnamed"
|
||||
file = "booth/templates/_ask_inline.html"
|
||||
test = "tests/test_antislop.py::test_fields_are_named"
|
||||
old = """ aria-label="{{ a.notes_label }}" placeholder="{{ a.notes_label }} (optional)">"""
|
||||
new = """ placeholder="{{ a.notes_label }} (optional)">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the inline ask's options not a named group"
|
||||
file = "booth/templates/_ask_inline.html"
|
||||
test = "tests/test_antislop.py::test_radio_groups_are_named_and_ids_are_unique"
|
||||
old = """<div class="bk-ask-opts" role="radiogroup" aria-labelledby="""
|
||||
new = """<div class="bk-ask-opts" aria-labelledby="""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the group names a prompt id that is not there"
|
||||
file = "booth/templates/_ask_inline.html"
|
||||
test = "tests/test_antislop.py::test_radio_groups_are_named_and_ids_are_unique"
|
||||
old = """<p class="bk-ask-prompt" id="bk-ask-{{ a.id }}{% if q.key %}-{{ q.key }}{% endif %}-prompt">"""
|
||||
new = """<p class="bk-ask-prompt">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a titled ask's title reuses the question's id"
|
||||
file = "booth/templates/_ask_inline.html"
|
||||
test = "tests/test_antislop.py::test_radio_groups_are_named_and_ids_are_unique"
|
||||
old = """<p class="bk-ask-title" id="bk-ask-{{ a.id }}-title">"""
|
||||
new = """<p class="bk-ask-title" id="bk-ask-{{ a.id }}">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a single-question fieldset without a legend"
|
||||
file = "booth/templates/_marks.html"
|
||||
test = "tests/test_antislop.py::test_radio_groups_are_named_and_ids_are_unique"
|
||||
old = """{% else %}<legend class="sr-only">{{ q.prompt }}</legend>{% endif %}"""
|
||||
new = """{% endif %}"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the Desk without an h1"
|
||||
file = "booth/templates/index.html"
|
||||
test = "tests/test_antislop.py::test_every_page_has_one_h1_and_a_skip_link"
|
||||
old = """<h1 class="sr-only">Desk</h1>
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the review without an h1"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_antislop.py::test_every_page_has_one_h1_and_a_skip_link"
|
||||
old = """<h1 class="sr-only">{{ file }}</h1>
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a compare without an h1"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_antislop.py::test_every_page_has_one_h1_and_a_skip_link"
|
||||
old = """<h1 class="sr-only">compare {{ sides.a.rel }} and {{ sides.b.rel }}</h1>
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a no skip link"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop.py::test_every_page_has_one_h1_and_a_skip_link"
|
||||
old = """<a class="skip-link" href="#main">Skip to content</a>
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the skip link points at nothing"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop.py::test_every_page_has_one_h1_and_a_skip_link"
|
||||
old = """<main id="main">"""
|
||||
new = """<main>"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a no dark theme-color"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop.py::test_theme_color_for_both_schemes"
|
||||
old = """<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#15191d">
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the tape is a list of links again"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_antislop.py::test_the_tape_is_one_picture"
|
||||
old = """<div class="tape" data-region="tape" role="img" aria-label="""
|
||||
new = """<div class="tape" data-region="tape" aria-label="""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the tape's segments back in the tab order"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_antislop.py::test_the_tape_is_one_picture"
|
||||
old = """ title="{{ num(x.ordinal) }} {{ x.name }}" tabindex="-1" aria-hidden="true"></a>"""
|
||||
new = """ title="{{ num(x.ordinal) }} {{ x.name }}"></a>"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a Wipe now back on an inline anonymous prompt"
|
||||
file = "booth/templates/booth.html"
|
||||
test = "tests/test_antislop.py::test_wipe_now_asks_by_name"
|
||||
old = """ data-booth="{{ name }}" data-confirm="{{ 'wipe-kept' if kept else 'wipe' }}">"""
|
||||
new = """ onsubmit="return confirm('Wipe this booth now?')">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a kept booth's Wipe now asks the ordinary question"
|
||||
file = "booth/templates/booth.html"
|
||||
test = "tests/test_antislop_browser.py::test_wipe_now_asks_by_name_in_the_browser"
|
||||
old = """data-confirm="{{ 'wipe-kept' if kept else 'wipe' }}""""
|
||||
new = """data-confirm="wipe""""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the shared prompt does not reach a booth page"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_wipe_now_asks_by_name_in_the_browser"
|
||||
old = """ var form = ev.target.closest ? ev.target.closest('form[data-confirm]') : null;"""
|
||||
new = """ var form = ev.target.closest ? ev.target.closest('.desk form[data-confirm]') : null;"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a hours never roll up to days"
|
||||
file = "booth/app.py"
|
||||
test = "tests/test_antislop.py::test_human_dur_rolls_up_to_days"
|
||||
old = """ if h >= 48:"""
|
||||
new = """ if h >= 4800:"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the embed's submit button has no ring of its own"
|
||||
file = "booth/static/embed.js"
|
||||
test = "tests/test_antislop.py::test_embed_chrome_draws_its_own_focus_rings"
|
||||
old = """ ".bk-ask-go:focus-visible{outline:2px solid var(--bk-accent);outline-offset:2px}",
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a an embed option has no ring"
|
||||
file = "booth/static/embed.js"
|
||||
test = "tests/test_antislop.py::test_embed_chrome_draws_its_own_focus_rings"
|
||||
old = """ ".bk-ask-opt:has(input:focus-visible){outline:2px solid var(--bk-accent);outline-offset:1px}",
|
||||
"""
|
||||
new = """"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a rings drawn outside a clipping container"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop.py::test_focus_rings_are_drawn_inside_clipping_containers"
|
||||
old = """ .desk-panel a:focus-visible{outline-offset:-2px}"""
|
||||
new = """ .desk-panel a:focus-visible{outline-offset:2px}"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a truncated why without its full text"
|
||||
file = "booth/templates/_provenance.html"
|
||||
test = "tests/test_antislop.py::test_a_truncated_why_carries_its_full_text"
|
||||
old = """<span class="prov-why" title="{{ m.why }}">"""
|
||||
new = """<span class="prov-why">"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the withdraw × at its old size on a desktop"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_withdraw_buttons_are_big_enough_to_hit"
|
||||
old = """ .mark-x{min-width:24px;min-height:24px}"""
|
||||
new = """ .mark-x{}"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the withdraw × not enlarged under a finger"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_withdraw_buttons_are_big_enough_to_hit"
|
||||
old = """ @media (pointer:coarse){.mark-x,.flagtoggle button"""
|
||||
new = """ @media (pointer:coarse){.flagtoggle button"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a double-tap zoom delay back on controls"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_touch_and_scroll_behaviour"
|
||||
old = """ a,button,summary,label,input,select,textarea{touch-action:manipulation}"""
|
||||
new = """ summary,label,input,select,textarea{touch-action:manipulation}"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a the film strip hands its scroll to the page"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_touch_and_scroll_behaviour"
|
||||
old = """ .film,.vcap,.doc-body,.cmp-strip{overscroll-behavior:contain}"""
|
||||
new = """ .vcap,.doc-body,.cmp-strip{overscroll-behavior:contain}"""
|
||||
|
||||
[[mutation]]
|
||||
label = "S5a a long slug runs off a phone"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_antislop_browser.py::test_touch_and_scroll_behaviour"
|
||||
old = """ @media (max-width:600px){.h1-slug{white-space:normal;overflow-wrap:anywhere}}"""
|
||||
new = """ @media (max-width:600px){.h1-slug{}}"""
|
||||
|
||||
Reference in New Issue
Block a user