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.
769 lines
33 KiB
TOML
769 lines
33 KiB
TOML
# The anti-slop fix slices: every falsifier the contract claims
|
||
# (docs/contracts/as_antislop.contract.md), and the change each forbids.
|
||
|
||
unit = "anti-slop fix slices (the house clock, and the slices after it)"
|
||
|
||
# ---- S1: the house clock
|
||
|
||
[[mutation]]
|
||
label = "S1 clock renders HH:MM"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_clock_forms"
|
||
old = ''' hhmm = time.strftime("%H%M", time.localtime(epoch))'''
|
||
new = ''' hhmm = time.strftime("%H:%M", time.localtime(epoch))'''
|
||
|
||
[[mutation]]
|
||
label = "S1 clock raises on a stamp it cannot read"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_clock_never_raises"
|
||
old = ''' if epoch is None:
|
||
return value if isinstance(value, str) else ""'''
|
||
new = ''' if epoch is None:
|
||
raise ValueError(value)'''
|
||
|
||
[[mutation]]
|
||
label = "S1 byline passes an address through"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_byline_hides_addresses"
|
||
old = ''' return "" if _is_address(bare) else who'''
|
||
new = ''' return who'''
|
||
|
||
[[mutation]]
|
||
label = "S1 the date tooltips keep HH:MM"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_date_stamp_is_house_form"
|
||
old = ''' return time.strftime("%Y-%m-%d %H%M", time.localtime(epoch))'''
|
||
new = ''' return time.strftime("%Y-%m-%d %H:%M", time.localtime(epoch))'''
|
||
|
||
[[mutation]]
|
||
label = "S1 a pick's answer line shows the raw stamp"
|
||
file = "booth/templates/_marks.html"
|
||
test = "tests/test_antislop.py::test_rendered_marks_use_the_house_clock"
|
||
old = '''{{ a.answer.answered_at|clock }}'''
|
||
new = '''{{ a.answer.answered_at }}'''
|
||
|
||
[[mutation]]
|
||
label = "S1 a memo line shows the raw stamp"
|
||
file = "booth/templates/_marks.html"
|
||
test = "tests/test_antislop.py::test_rendered_marks_use_the_house_clock"
|
||
old = '''{{ a.created|clock }}'''
|
||
new = '''{{ a.created }}'''
|
||
|
||
[[mutation]]
|
||
label = "S1 a memo line shows the poster's address"
|
||
file = "booth/templates/_marks.html"
|
||
test = "tests/test_antislop.py::test_rendered_marks_use_the_house_clock"
|
||
old = '''{% if a.by|byline %} · {{ a.by|byline }}{% endif %}'''
|
||
new = '''{% if a.by %} · {{ a.by }}{% endif %}'''
|
||
|
||
[[mutation]]
|
||
label = "S1 an answer line shows the answerer's address"
|
||
file = "booth/templates/_marks.html"
|
||
test = "tests/test_antislop.py::test_rendered_marks_use_the_house_clock"
|
||
old = '''{% if a.answer.answered_by|byline %} · {{ a.answer.answered_by|byline }}{% endif %}'''
|
||
new = '''{% if a.answer.answered_by %} · {{ a.answer.answered_by }}{% endif %}'''
|
||
|
||
[[mutation]]
|
||
label = "S1 the inline ask's state tag shows the raw stamp"
|
||
file = "booth/templates/_ask_inline.html"
|
||
test = "tests/test_antislop.py::test_embed_fragment_uses_the_house_clock"
|
||
old = '''✓ answered <time datetime="{{ a.answer.answered_at }}">{{ a.answer.answered_at|clock }}</time>'''
|
||
new = '''✓ answered {{ a.answer.answered_at }}'''
|
||
|
||
[[mutation]]
|
||
label = "S1 a board row shows HH:MM"
|
||
file = "booth/templates/booth.html"
|
||
test = "tests/test_antislop.py::test_board_rows_use_the_house_clock"
|
||
old = '''{{ e.when|clock }}'''
|
||
new = '''{{ e.when }}'''
|
||
|
||
# ---- S1, folded from the heid bug-hunt (thread 01M3MGPFKWBX0SJK5HFE0P3AFM)
|
||
|
||
[[mutation]]
|
||
label = "S1 clock raises OverflowError on an int past float range (Q1)"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_clock_never_raises"
|
||
old = ''' try:
|
||
f = float(value)
|
||
except (OverflowError, ValueError):
|
||
return None
|
||
return f if math.isfinite(f) else None'''
|
||
new = ''' return float(value) if math.isfinite(value) else None'''
|
||
|
||
[[mutation]]
|
||
label = "S1 a date-only stamp prints an invented 0000 (Q8)"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_clock_forms"
|
||
old = ''' if isinstance(value, str) and ":" not in value:'''
|
||
new = ''' if False:'''
|
||
|
||
[[mutation]]
|
||
label = "S1 an address with a port prints (Q7)"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_byline_hides_addresses"
|
||
old = ''' if s.count(":") == 1:
|
||
candidates.add(s.split(":", 1)[0])'''
|
||
new = ''''''
|
||
|
||
[[mutation]]
|
||
label = "S1 a zero-width character disguises an address (Q7)"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_byline_hides_addresses"
|
||
old = ''' bare = "".join(ch for ch in who if unicodedata.category(ch) not in ("Cf", "Cc")).strip()'''
|
||
new = ''' bare = who.strip()'''
|
||
|
||
[[mutation]]
|
||
label = "S1 a board row's author is not bylined (Q5)"
|
||
file = "booth/templates/booth.html"
|
||
test = "tests/test_antislop.py::test_board_rows_use_the_house_clock"
|
||
old = '''{% if e.who|byline %}<span class="board-who">{{ e.who|byline }}</span>{% endif %}'''
|
||
new = '''{% if e.who %}<span class="board-who">{{ e.who }}</span>{% endif %}'''
|
||
|
||
# ---- S2: legibility
|
||
|
||
[[mutation]]
|
||
label = "S2 the review arrows' chip lets a light stage through (60%)"
|
||
file = "booth/templates/view.html"
|
||
test = "tests/test_antislop.py::test_review_arrows_hold_over_a_white_stage"
|
||
old = '''background:oklch(0.17 0.01 250 / .82);'''
|
||
new = '''background:oklch(0.17 0.01 250 / .6);'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the filmstrip numbers back at 9.5px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_film_numbers_meet_the_label_floor"
|
||
old = ''' .film-ord{position:absolute;left:3px;top:3px;padding:1px 4px;border-radius:var(--radius-sm);
|
||
font:600 var(--size-micro)/1.2 var(--font-mono);background:oklch(0.17 0.01 250 / .85);'''
|
||
new = ''' .film-ord{position:absolute;left:3px;top:3px;padding:1px 4px;border-radius:var(--radius-sm);
|
||
font:600 9.5px/1.2 var(--font-mono);background:oklch(0.17 0.01 250 / .85);'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the marks' state stamp back at 10.5px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_mark_state_meets_the_label_floor"
|
||
old = ''' .mark-state{font-family:var(--font-mono);font-size:var(--size-micro);'''
|
||
new = ''' .mark-state{font-family:var(--font-mono);font-size:10.5px;'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the Desk's section rules back at the label size"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_hint_lines_meet_the_sentence_floor"
|
||
old = ''' .desk-rule{font-size:var(--size-caption);font-weight:400;'''
|
||
new = ''' .desk-rule{font-weight:400;'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the board note back at 11px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_hint_lines_meet_the_sentence_floor"
|
||
old = ''' .board-note{font-family:var(--font-mono);font-size:var(--size-caption);'''
|
||
new = ''' .board-note{font-family:var(--font-mono);font-size:var(--size-micro);'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the bench note back at 11px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_hint_lines_meet_the_sentence_floor"
|
||
old = ''' .bench-note{font-family:var(--font-mono);font-size:var(--size-caption);'''
|
||
new = ''' .bench-note{font-family:var(--font-mono);font-size:var(--size-micro);'''
|
||
|
||
[[mutation]]
|
||
label = "S2 retired benches faded again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_retired_benches_are_not_faded"
|
||
old = ''' .bench-row.is-retired .bench-link,.bench-row.is-retired .bench-url{color:var(--text-muted)}'''
|
||
new = ''' .bench-row.is-retired{opacity:.5}'''
|
||
|
||
[[mutation]]
|
||
label = "S2 an answered ask's option details faded again"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop.py::test_embed_fades_nothing"
|
||
old = ''' ".bk-ask-det{font-size:.82rem}",'''
|
||
new = ''' ".bk-ask-det{font-size:.82rem;opacity:.68}",'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the recorded line faded again"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop.py::test_embed_fades_nothing"
|
||
old = ''' ".bk-ask-was{margin:.15rem 0 .55rem;font-size:.86rem}",'''
|
||
new = ''' ".bk-ask-was{margin:.15rem 0 .55rem;font-size:.86rem;opacity:.8}",'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the notes placeholder back to the browser's grey"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop.py::test_embed_fades_nothing"
|
||
old = ''' ".bk-ask-notes::placeholder{color:inherit;opacity:.75}",'''
|
||
new = ''''''
|
||
|
||
[[mutation]]
|
||
label = "S2 the inline ask's state tag back at 10.5px"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop.py::test_the_ask_tag_meets_the_label_floor"
|
||
old = """ "font:600 11px/1.3 'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;","""
|
||
new = """ "font:600 10.5px/1.3 'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;","""
|
||
|
||
# ---- S2, folded from the heid bug-hunt guard grid: what the COMPUTED style shows
|
||
|
||
[[mutation]]
|
||
label = "S2 the ask title fades (Q9)"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop_s2_browser.py::test_nothing_fades"
|
||
old = """ "letter-spacing:.12em;text-transform:uppercase}","""
|
||
new = """ "letter-spacing:.12em;text-transform:uppercase;opacity:.62}","""
|
||
|
||
[[mutation]]
|
||
label = "S2 a later rule shrinks the film numbers (cascade, not shorthand)"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_s2_browser.py::test_labels_and_sentences_render_at_their_floors"
|
||
old = """ .film-ord{position:absolute;left:3px;top:3px;padding:1px 4px;border-radius:var(--radius-sm);
|
||
font:600 var(--size-micro)/1.2 var(--font-mono);background:oklch(0.17 0.01 250 / .85);color:oklch(0.91 0.008 216)}"""
|
||
new = """ .film-ord{position:absolute;left:3px;top:3px;padding:1px 4px;border-radius:var(--radius-sm);
|
||
font:600 var(--size-micro)/1.2 var(--font-mono);background:oklch(0.17 0.01 250 / .85);color:oklch(0.91 0.008 216);font-size:1px}"""
|
||
|
||
[[mutation]]
|
||
label = "S2 the details go transparent without any opacity"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop_s2_browser.py::test_nothing_fades"
|
||
old = """ ".bk-ask-det{font-size:.82rem}","""
|
||
new = """ ".bk-ask-det{font-size:.82rem;color:transparent}","""
|
||
|
||
[[mutation]]
|
||
label = "S2 the placeholder vanishes (opacity 0) while keeping color:inherit"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop_s2_browser.py::test_nothing_fades"
|
||
old = """ ".bk-ask-notes::placeholder{color:inherit;opacity:.75}","""
|
||
new = """ ".bk-ask-notes::placeholder{color:inherit;opacity:0}","""
|
||
|
||
[[mutation]]
|
||
label = "S2 retired benches greyed by a filter instead of opacity"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_s2_browser.py::test_nothing_fades"
|
||
old = """ .bench-row.is-retired .bench-link,.bench-row.is-retired .bench-url{color:var(--text-muted)}"""
|
||
new = """ .bench-row.is-retired .bench-link,.bench-row.is-retired .bench-url{color:var(--text-muted)}
|
||
.bench-row.is-retired{filter:grayscale(.6) opacity(.5)}"""
|
||
|
||
[[mutation]]
|
||
label = "S2 the flagged tray's number back at 10px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_s2_browser.py::test_labels_and_sentences_render_at_their_floors"
|
||
old = ''' .tray-ord{position:absolute;left:3px;bottom:3px;padding:1px 4px;border-radius:var(--radius-sm);
|
||
font:600 var(--size-micro)/1.2 var(--font-mono);'''
|
||
new = ''' .tray-ord{position:absolute;left:3px;bottom:3px;padding:1px 4px;border-radius:var(--radius-sm);
|
||
font:600 10px/1.2 var(--font-mono);'''
|
||
|
||
[[mutation]]
|
||
label = "S2 the tile's flagged stamp back at 10.5px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_s2_browser.py::test_labels_and_sentences_render_at_their_floors"
|
||
old = ''' font:600 var(--size-micro)/1.3 var(--font-mono);letter-spacing:var(--tracking-caps);text-transform:uppercase;'''
|
||
new = ''' font:600 10.5px/1.3 var(--font-mono);letter-spacing:var(--tracking-caps);text-transform:uppercase;'''
|
||
|
||
[[mutation]]
|
||
label = "S2 compare's A/B badge back at 9.5px"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_s2_browser.py::test_labels_and_sentences_render_at_their_floors"
|
||
old = ''' font:700 var(--size-micro)/1.2 var(--font-mono);background:var(--accent);color:var(--accent-contrast)}'''
|
||
new = ''' font:700 9.5px/1.2 var(--font-mono);background:var(--accent);color:var(--accent-contrast)}'''
|
||
|
||
# ---- S3: phone layouts (measured in a real browser at 390x844)
|
||
|
||
[[mutation]]
|
||
label = "S3 bench rows squeeze instead of wrapping"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_bench_rows_do_not_overprint_on_a_phone"
|
||
old = ''' .bench-row{flex-wrap:wrap;row-gap:4px}'''
|
||
new = ''' .bench-row{row-gap:4px}'''
|
||
|
||
[[mutation]]
|
||
label = "S3 the board head's phone rule gone (the count wraps)"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_board_head_stays_compact_on_a_phone"
|
||
old = ''' @media (max-width:600px){.board-head{flex-wrap:wrap}.board-title{white-space:nowrap}'''
|
||
new = ''' @media (max-width:1px){.board-head{flex-wrap:wrap}.board-title{white-space:nowrap}'''
|
||
|
||
[[mutation]]
|
||
label = "S3 the doc bar squeezes the name again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_doc_name_keeps_a_readable_line_on_a_phone"
|
||
old = ''' @media (max-width:600px){.doc-inline > .doc-bar{flex-wrap:wrap;row-gap:4px}'''
|
||
new = ''' @media (max-width:1px){.doc-inline > .doc-bar{flex-wrap:wrap;row-gap:4px}'''
|
||
|
||
[[mutation]]
|
||
label = "S3 a file tile's link starts under the number again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_file_tile_number_clears_the_download_link"
|
||
old = ''' .item > .ord ~ .dl{padding-top:44px}'''
|
||
new = ''''''
|
||
|
||
[[mutation]]
|
||
label = "S3 the review keeps its tagline on a phone"
|
||
file = "booth/templates/view.html"
|
||
test = "tests/test_antislop_browser.py::test_review_header_is_one_line_on_a_phone"
|
||
old = '''{% block body_attrs %} class="page-stage"{% endblock %}'''
|
||
new = ''''''
|
||
|
||
[[mutation]]
|
||
label = "S3 the tagline hidden on every page (the Desk's negative control)"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_review_header_is_one_line_on_a_phone"
|
||
old = ''' @media (max-width:600px){body.page-stage .topbar .tagline{display:none}}'''
|
||
new = ''' @media (max-width:600px){body .topbar .tagline{display:none}}'''
|
||
|
||
[[mutation]]
|
||
label = "S3 the benches head stacks its count again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_board_head_stays_compact_on_a_phone"
|
||
old = """ .bench-head{flex-wrap:wrap}.bench-title{white-space:nowrap}.bench-note{flex:1 1 100%}}"""
|
||
new = """ }"""
|
||
|
||
# ---- S4: reading measure
|
||
|
||
[[mutation]]
|
||
label = "S4 prose runs the full width again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_doc_prose_reads_at_a_book_measure"
|
||
old = """ .markdown-body > :is(p,ul,ol,blockquote,dl,h1,h2,h3,h4,h5,h6){max-width:72ch}"""
|
||
new = """"""
|
||
|
||
[[mutation]]
|
||
label = "S4 h3 flat against its body again (1.08em)"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_doc_headings_step_by_size"
|
||
old = """.markdown-body h3{font-size:1.2em;font-weight:600}"""
|
||
new = """.markdown-body h3{font-size:1.08em;font-weight:600}"""
|
||
|
||
# ---- S6: the operator's rulings
|
||
|
||
[[mutation]]
|
||
label = "S6 the old tagline copy"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_the_tagline_is_a_sentence"
|
||
old = """ <span class="tagline">held for review · wipes in {{ ttl_hours }}h unless kept</span>"""
|
||
new = """ <span class="tagline">ephemeral media · auto-wipes {{ ttl_hours }}h · kept boards don't</span>"""
|
||
|
||
[[mutation]]
|
||
label = "S6 the tagline in tracked capitals again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_the_tagline_is_a_sentence"
|
||
old = """ .tagline{font-family:var(--font-mono);font-size:var(--size-caption);font-weight:500;color:var(--text-muted)}"""
|
||
new = """ .tagline{font-family:var(--font-mono);font-size:var(--size-caption);font-weight:500;text-transform:uppercase;color:var(--text-muted)}"""
|
||
|
||
[[mutation]]
|
||
label = "S6 the needs-you side stripe back"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_needs_you_rows_carry_no_side_stripe"
|
||
old = """ .desk-row.is-needs{box-shadow:var(--shadow-sm)}"""
|
||
new = """ .desk-row.is-needs{box-shadow:inset 3px 0 0 var(--warning),var(--shadow-sm)}"""
|
||
|
||
[[mutation]]
|
||
label = "S6 the brand dot glows again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_the_brand_dot_is_matte"
|
||
old = """ .brand .dot{width:8px;height:8px;border-radius:var(--radius-pill);background:var(--accent)}"""
|
||
new = """ .brand .dot{width:8px;height:8px;border-radius:var(--radius-pill);background:var(--accent);box-shadow:var(--glow-armed)}"""
|
||
|
||
[[mutation]]
|
||
label = "S6 Wipe now paints the stripe on its own background again"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_the_hazard_stripe_is_a_pseudo_element"
|
||
old = """ position:relative;overflow:hidden;background:transparent;
|
||
border:1px solid var(--danger);color:var(--danger-text);-webkit-backdrop-filter:none;backdrop-filter:none}"""
|
||
new = """ position:relative;overflow:hidden;background:var(--hazard-stripe) top / 100% 3px no-repeat,transparent;
|
||
border:1px solid var(--danger);color:var(--danger-text);-webkit-backdrop-filter:none;backdrop-filter:none}"""
|
||
|
||
[[mutation]]
|
||
label = "S6 the stripe layer lost (no ::before)"
|
||
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{}}"""
|
||
|
||
# ---- S5a fixup: booth-dev's gate (hulda + heid BRINGA, thread 01M3MVGQ7QSCCK8WT59TQ4J469)
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the booth page's release does not ask"
|
||
file = "booth/templates/booth.html"
|
||
test = "tests/test_antislop.py::test_release_on_the_booth_page_asks_by_name"
|
||
old = '''
|
||
|
||
data-booth="{{ name }}" data-confirm="release">'''
|
||
new = '''>'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the booth page's release does not ask (browser)"
|
||
file = "booth/templates/booth.html"
|
||
test = "tests/test_antislop_browser.py::test_release_on_the_booth_page_asks_in_the_browser"
|
||
old = '''
|
||
|
||
data-booth="{{ name }}" data-confirm="release">'''
|
||
new = '''>'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup WORDS inherits from Object.prototype"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_a_prototype_word_still_asks"
|
||
old = ''' var WORDS = Object.create(null);'''
|
||
new = ''' var WORDS = {};'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the confirm helper leaves <head>"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop.py::test_the_confirm_helper_is_listening_before_the_body_exists"
|
||
old = '''<script>
|
||
/* as S5a: moved here from index.html'''
|
||
new = '''</head>
|
||
<script>
|
||
/* as S5a: moved here from index.html'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup shown() lets line separators and zero-widths through"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_the_dialog_shows_hidden_breaks_and_zero_widths_visibly"
|
||
old = ''' return n.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2060\u2066-\u2069\ufeff]/g, '\ufffd');'''
|
||
new = ''' return n.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, '\ufffd');'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup a question's prompt id collides with a key ending -prompt"
|
||
file = "booth/templates/_ask_inline.html"
|
||
test = "tests/test_antislop.py::test_no_id_repeats_on_any_page"
|
||
old = '''id="bk-ask-{{ a.id }}{% if q.key %}-{{ q.key }}{% endif %}:prompt"'''
|
||
new = '''id="bk-ask-{{ a.id }}{% if q.key %}-{{ q.key }}{% endif %}-prompt"'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the title's id collides with a key named title"
|
||
file = "booth/templates/_ask_inline.html"
|
||
test = "tests/test_antislop.py::test_no_id_repeats_on_any_page"
|
||
old = '''id="bk-ask-{{ a.id }}:title"'''
|
||
new = '''id="bk-ask-{{ a.id }}-title"'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the tile's note repeats the article's id"
|
||
file = "booth/templates/booth.html"
|
||
test = "tests/test_antislop.py::test_no_id_repeats_on_any_page"
|
||
old = ''' <div class="item-note">'''
|
||
new = ''' <div class="item-note" id="mark-{{ m.id }}">'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup human_dur raises on nan"
|
||
file = "booth/app.py"
|
||
test = "tests/test_antislop.py::test_human_dur_never_raises"
|
||
old = ''' if not math.isfinite(seconds): # as S5a fixup: int(nan) raises; say nothing we cannot know
|
||
return "—"
|
||
'''
|
||
new = ''''''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup a question's notes field named only by its placeholder"
|
||
file = "booth/templates/_ask_inline.html"
|
||
test = "tests/test_antislop.py::test_fields_are_named"
|
||
old = '''aria-label="notes on this one" placeholder="notes on this one (optional)">'''
|
||
new = '''placeholder="notes on this one (optional)">'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the marks page's question notes named only by their placeholder"
|
||
file = "booth/templates/_marks.html"
|
||
test = "tests/test_antislop.py::test_fields_are_named"
|
||
old = '''aria-label="notes on this one" placeholder="notes on this one (optional)">'''
|
||
new = '''placeholder="notes on this one (optional)">'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the embed chip's ring is transparent"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop_browser.py::test_the_embed_draws_visible_rings"
|
||
old = '''outline:2px solid #fff;outline-offset:1px;box-shadow:0 0 0 4px #15191d}'''
|
||
new = '''outline:2px solid transparent;outline-offset:1px;box-shadow:0 0 0 4px #15191d}'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the embed submit's ring is transparent"
|
||
file = "booth/static/embed.js"
|
||
test = "tests/test_antislop_browser.py::test_the_embed_draws_visible_rings"
|
||
old = '''".bk-ask-go:focus-visible{outline:2px solid var(--bk-accent);outline-offset:2px}"'''
|
||
new = '''".bk-ask-go:focus-visible{outline:2px solid transparent;outline-offset:2px}"'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup an image tile's ring is drawn outside its clip"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_rings_inside_clipping_containers_are_drawn_inside"
|
||
old = '''.theme button:focus-visible,.vtoggle button:focus-visible,.item a:focus-visible,'''
|
||
new = '''.theme button:focus-visible,.vtoggle button:focus-visible,'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup a Desk panel's ring is drawn outside its clip (computed)"
|
||
file = "booth/templates/base.html"
|
||
test = "tests/test_antislop_browser.py::test_rings_inside_clipping_containers_are_drawn_inside"
|
||
old = ''' .desk-panel a:focus-visible{outline-offset:-2px}'''
|
||
new = ''' .desk-panel a:focus-visible{outline-offset:2px}'''
|
||
|
||
[[mutation]]
|
||
label = "S5a fixup the withdraw × narrower than 24px"
|
||
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{min-height:24px}'''
|