Files
booth/tests/mutations/antislop.toml
T
vh 72d6c61629 fix(as-S5c): whose key it is, the doc bar, tile sizes, the rail's shadow, reveal names
The last of the anti-slop interaction work (guidelines G6, G7, G14, G15,
G17), plus booth-dev's note from S5b's gate. Every S5b promise holds: no
re-POST, serialized saves, a batch never reloads, focus survives a swap.
- Keys (G6): one rule in base.html's <head>, BoothKeys.theirs(e), called
  first by the grid, the review and compare. A field or a player owns every
  key but Escape (Esc still goes back from a focused player); a control
  owns Space; a focused 1:1 stage that can pan owns the
  arrows and Space (Chromium puts it in the Tab order); Ctrl/Meta/Alt are
  the browser's. The field check lives on as BoothKeys.isEditable. Before:
  an arrow on a focused video left the review, and Enter on any control
  also opened the grid cursor's tile.
- The grid cursor is real focus: the tile it moves to gets tabindex=-1
  (script-set, one tile at a time) and focus, without a scroll; the cursor
  is an item (its data-item), and a doc closed with its ✕ is skipped; focus that
  lands on a tile (S5b's fallback) makes it the cursor; Enter opens the
  review only from the body, the grid or the tile, by its view?f= link;
  n opens a closed doc's fold; Escape clears the cursor
  and releases the tile's focus. The reticle is its focus mark (no second
  ring).
- The doc bar (G7): the controls leave the <summary>. div.doc-bar holds
  details.doc-fold (its summary is the label only) and div.doc-tools beside
  it; the body and notes follow in div.doc-inline, hidden with a closed
  fold by :has(), scripts on or off. A closed doc keeps its tools. Renders
  pixel-identical to today at 1280 and 390, light and dark.
- Tile sizes (G14): a gallery tile's <img> carries width/height, the
  picture as the browser draws it (EXIF 5-8 swap), read from the header
  only (no decode; PNG getexif is skipped unless the header carried it),
  opened O_NOFOLLOW|O_NONBLOCK, cached by the file's identity (ctime
  included, so cp -p over a file is seen), in a separate
  step (items.image_dims over thumbs.drawn_size) so the Desk never pays it.
  Measured before: a link to tile 30 of 40 landed 44px low (3/3); after, on
  its mark. content-visibility:auto, which the report proposed too, is NOT
  added: a swapped-in tile has no remembered size, and a flag far down moved
  the page 2929px (3/3; 0px without it).
- The rail (G15): html:has(.rail){scroll-padding-top} replaces .item's
  scroll-margin-top (the two add), so a control reached by Tab stops below
  the sticky rail too. Measured before: a Tab-focused flag button at 19.6px,
  under the rail's bottom at 47.6px. The scripts-off fallbacks are the old
  rules' numbers (132px, 217px at <=480), now pinned by a test. The height
  script follows the live rail after every in-place save (it watched the
  replaced node, and read 0px after one flag), and the rail's own controls
  cancel the padding (a Tab between stuck group links scrolled 357px).
- Reveal names (G17): no aria-label on any reveal control; the name is the
  words on it, the glyph in an aria-hidden span, the item's name as
  .sr-only text ("reveal a.png" / "hide a.png"). Reveal all drops
  aria-pressed (its words already say the state; r2b rules them) and its
  "on" look reads the .reveal-all class on <html>. No pixel changes.
- booth-dev's note: a refused batch's forms enter `unsent` with the
  refusal's words, and a later save says every standing failure's words
  (each once, in order) instead of "Saved.", and every warning says the
  other standing failures first, so no failure buries another. Test first:
  test_a_batch_refusal_outlives_an_unrelated_save.
- Rows re-anchored to the same failure: r2b "Space on a focused review
  button", r3 "C3 a held modifier" and both "C3 Space on a focused ..."
  (now in BoothKeys), r2c "the stage reveal shows with scripts off", and
  this contract's S3 doc-bar row and five S5b status-line rows.

Folded from the heid contract review (BEINKA, panel 4/4, thread
01M3NZJNX8D3BEYD48M9K3MV3Q): 24 flags, all prose the tests left open; the
contract states the tile/focus/cursor seam with S5b, the helper's union and
scope, the size's source and every path to none, Reveal all's name, the
refusal sentence's lifetime, and the fallback arithmetic (one test added).

Folded from the heid bug-hunt (HRÖSKVA, panel 4/4, thread
01M3P0ZPRSASFSE5K3PR4NTQP6): R1 closed docs and the cursor as an item, R2
the rail's height after a save, R3 no warning buries another, R5 the view?f=
link, R7 ctime in the size cache, R8 Escape from a player, R9 the rail's own
controls, R10 n on a closed doc. Refuted with reasons: R4 (unreachable: refused
picks re-send together), R6 (Chrome takes the same header's size with or
without the attributes; measured), R11 (by design).

From this slice's own falsifier runs: a "one row wide" row that mutated a
flex basis a non-wrapping bar just shrinks (re-aimed at the bar's flex), and
a Reveal-all "on look" read under the clicking pointer, where :hover draws
the same border (the pointer now leaves first; 3/3 proved).

Contract: as_antislop S5c.
Falsifiers: antislop.toml S5c section.
2026-09-29 01:09:15 -07:00

1467 lines
63 KiB
TOML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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"
# re-anchored in S5c (G7): the bar is a div holding the fold and the tools
old = ''' @media (max-width:600px){.doc-bar{flex-wrap:wrap;row-gap:4px}'''
new = ''' @media (max-width:1px){.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}'''
# ---- S5b: the in-place client (focus, the status line, the unsent-draft guard)
[[mutation]]
label = "S5b no focus restore after a swap"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_returns_to_the_pressed_control"
old = ''' focusRestore(rec, placed);
'''
new = ''''''
[[mutation]]
label = "S5b the first same-key match, not the same one"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_picks_the_same_one_of_two"
old = ''' var el = rec.key && rec.n >= 0 && keyed(region, rec.key)[rec.n];'''
new = ''' var el = rec.key && rec.n >= 0 && keyed(region, rec.key)[0];'''
[[mutation]]
label = "S5b no region fallback (focus falls to body)"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_lands_on_the_region_when_the_control_is_gone"
old = ''' region.setAttribute('tabindex', '-1');
region.focus({preventScroll: true});'''
new = ''''''
[[mutation]]
label = "S5b a closed details' summary is not tried"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_returns_to_the_pressed_control"
old = ''' if (sum) { sum.focus({preventScroll: true}); if (document.activeElement === sum) return; }'''
new = ''''''
[[mutation]]
label = "S5b the pressed control is restored, not the focused one"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_elsewhere_is_left_alone"
old = ''' var ae = document.activeElement;'''
new = ''' var ae = document.querySelector('form[aria-busy="true"] button') || document.activeElement;'''
[[mutation]]
label = "S5b restoring focus scrolls the page"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_restore_does_not_scroll"
old = ''' if (el) el.focus({preventScroll: true});'''
new = ''' if (el) el.focus();'''
[[mutation]]
label = "S5b the covered footer stays in the tab order"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_the_covered_letterhead_leaves_the_tab_order"
old = ''' @media (min-width:901px){body.page-stage .topbar,body.page-stage .foot{visibility:hidden}}'''
new = ''' @media (min-width:901px){body.page-stage .topbar{visibility:hidden}}'''
[[mutation]]
label = "S5b the covered letterhead stays in the tab order"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_the_covered_letterhead_leaves_the_tab_order"
old = ''' @media (min-width:901px){body.page-stage .topbar,body.page-stage .foot{visibility:hidden}}'''
new = ''' @media (min-width:901px){body.page-stage .foot{visibility:hidden}}'''
[[mutation]]
label = "S5b the line is hidden again"
file = "booth/templates/_status.html"
test = "tests/test_antislop.py::test_one_status_line_per_page"
old = '''aria-live="polite"></p>'''
new = '''aria-live="polite" hidden></p>'''
[[mutation]]
label = "S5b an empty line keeps its padding"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_an_empty_status_line_takes_no_space"
old = ''' .status:empty{padding:0;margin:0;border:0;box-shadow:none}'''
new = ''' .status:empty{}'''
[[mutation]]
label = "S5b an empty line is display:none (out of the accessibility tree)"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_the_status_line_is_always_displayed"
old = ''' .status:empty{padding:0;margin:0;border:0;box-shadow:none}'''
new = ''' .status:empty{display:none}'''
[[mutation]]
label = "S5b no Saving… at the press"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_save_says_saving_then_saved"
old = ''' say('Saving…');
markBusy();'''
new = ''' markBusy();'''
[[mutation]]
label = "S5b Saved. never clears"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_save_says_saving_then_saved"
old = ''' setTimeout(function () { if (said === n) say(''); }, 2000);'''
new = ''''''
[[mutation]]
label = "S5b the old timer clears a newer message"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_new_save_is_not_cleared_by_the_last_ones_timer"
old = '''if (said === n) say('');'''
new = '''say('');'''
[[mutation]]
label = "S5b a queued save's Saving… is replaced by Saved."
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_queued_save_keeps_saying_saving"
old = ''' if (others) { say('Saving…'); return; }'''
new = ''''''
[[mutation]]
label = "S5b the tone outlives its words"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_failure_then_an_edit_then_a_save"
old = '''if (tone) st.setAttribute('data-tone', tone); else st.removeAttribute('data-tone');'''
new = '''if (tone) st.setAttribute('data-tone', tone);'''
[[mutation]]
label = "S5b a repeat press says nothing"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_repeat_press_says_still_saving"
old = ''' if (pending[flightKey(form)]) say('Still saving…');
'''
new = ''''''
[[mutation]]
label = "S5b a clean press during a batch says nothing"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_batch_speaks_too"
old = '''if (!batch.length) { say('Still saving…'); return; }'''
new = '''if (!batch.length) return;'''
[[mutation]]
label = "S5b a batch that lands says nothing"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_batch_speaks_too"
# re-anchored in S5c (heid bug-hunt R3): the batch's keys are named once, as `mine`
old = '''
else landed(mine);'''
new = ''''''
[[mutation]]
label = "S5b a batch never says Saving…"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_batch_speaks_too"
old = ''' say('Saving…');
var keys = batch.map(flightKey);'''
new = ''' var keys = batch.map(flightKey);'''
[[mutation]]
label = "S5b the form in flight is not busy"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_the_form_in_flight_is_busy"
old = ''' say('Saving…');
markBusy();'''
new = ''' say('Saving…');'''
[[mutation]]
label = "S5b busy never ends on a failure that stays"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_the_form_in_flight_is_busy"
old = ''' var settle = function () { delete pending[key]; markBusy(); };'''
new = ''' var settle = function () { delete pending[key]; };'''
[[mutation]]
label = "S5b a failure is not a warning"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_failed_save_keeps_the_other_drafts"
old = ''' function warn(text) { return say(text, 'warn'); }'''
new = ''' function warn(text) { return say(text); }'''
[[mutation]]
label = "S5b a failure reloads over other drafts"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_failed_save_keeps_the_other_drafts"
old = ''' return !dirty(f) || (flightKey(f) === key && serial(f) === snap);'''
new = ''' return true;'''
[[mutation]]
label = "S5b text typed while the save flew does not block the reload"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_failed_save_keeps_text_typed_while_it_flew"
old = '''(flightKey(f) === key && serial(f) === snap)'''
new = '''(flightKey(f) === key)'''
[[mutation]]
label = "S5b a changed page reloads over other drafts"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_changed_page_keeps_the_other_drafts"
old = ''' if (mayReload(key, snap)) { reload(); return; }'''
new = ''' { reload(); return; }'''
[[mutation]]
label = "S5b the reload is not asked again at the beat"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_draft_typed_during_the_beat_stays"
# re-anchored in S5c (heid bug-hunt R3): the stay message now says the other standing failures first
old = ''' beat = setTimeout(function () { beat = null; if (mayReload(key, snap)) reload(); else warn(also(STAY, [key])); }, 900);'''
new = ''' beat = setTimeout(reload, 900);'''
[[mutation]]
label = "S5b no unload guard"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_leaving_with_a_draft_asks"
old = ''' ev.preventDefault();
ev.returnValue = '';'''
new = ''''''
[[mutation]]
label = "S5b the client's own reload asks"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_its_own_reload_does_not_ask"
old = ''' if (leaving) return;
'''
new = ''''''
[[mutation]]
label = "S5b a clean page asks"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_leaving_a_clean_page_does_not_ask"
old = ''' if ([].some.call(document.querySelectorAll('form[data-inplace]'), dirty)) {'''
new = ''' if (true) {'''
[[mutation]]
label = "S5b a saved draft still asks"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_saved_draft_no_longer_asks"
old = ''' if ([].some.call(document.querySelectorAll('form[data-inplace]'), dirty)) {'''
new = ''' if (true) {'''
[[mutation]]
label = "S5b the embed has no unload guard"
file = "booth/static/embed.js"
test = "tests/test_antislop_s5b_browser.py::test_embed_leaving_with_a_draft_asks"
old = ''' if (forms[i] !== skip && dirty(forms[i])) { ev.preventDefault(); ev.returnValue = ""; return; }'''
new = ''''''
[[mutation]]
label = "S5b the embed's own submit asks"
file = "booth/static/embed.js"
test = "tests/test_antislop_s5b_browser.py::test_embed_submit_does_not_ask"
old = ''' nativeSent = form;
'''
new = ''''''
[[mutation]]
label = "S5b the line sits in the page flow (a save moves the page)"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_save_does_not_move_the_page"
old = ''' .status{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);z-index:70;'''
new = ''' .status{'''
[[mutation]]
label = "S5b the fixed stage covers the line"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_the_status_line_is_visible_on_the_review"
old = ''' .status{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);z-index:70;'''
new = ''' .status{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);z-index:10;'''
# ---- S5b, folded from the heid bug-hunt (HNITTA, thread 01M3MRTNTWEPJHTN4APRR81KH4)
[[mutation]]
label = "S5b a settled save leaves its stale tile busy"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_stale_tile_is_not_left_busy"
old = ''' var settle = function () { delete pending[key]; markBusy(); };'''
new = ''' var settle = function () { delete pending[key]; };'''
[[mutation]]
label = "S5b a queued form's live copy is not re-marked busy when a save settles"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_queued_form_is_busy_on_the_live_page"
old = ''' var settle = function () { delete pending[key]; markBusy(); };'''
new = ''' var settle = function () { delete pending[key]; };'''
[[mutation]]
label = "S5b a press inside the beat does not cancel the reload"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_resubmit_in_the_beat_is_not_reloaded_away"
old = ''' if (beat) { clearTimeout(beat); beat = null; }
'''
new = ''''''
[[mutation]]
label = "S5b an unrelated save buries a failure"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_an_unrelated_save_does_not_bury_a_failure"
# re-anchored in S5c: a standing failure now carries its own words
old = ''' if (failed.length) { warn(stayed(failed)); return; }
'''
new = ''''''
[[mutation]]
label = "S5b a refresh failure after a 204 says it could not save"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_a_save_whose_page_would_not_refresh_says_saved"
# re-anchored in S5c (heid bug-hunt R3)
old = ''' else warn(also('Saved. Could not refresh the page; reload to see it.', [key]));'''
new = ''' else fail(form, key, snap);'''
[[mutation]]
label = "S5b a focused summary has no key"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_focus_on_a_summary_survives_the_next_swap"
old = ''' if (t === 'SUMMARY') {'''
new = ''' if (false) {'''
[[mutation]]
label = "S5b an edit made while saving is called Saved."
file = "booth/templates/base.html"
test = "tests/test_antislop_s5b_browser.py::test_an_edit_made_while_saving_is_not_called_saved"
# re-anchored in S5c (heid bug-hunt R3)
old = ''' if (keys.some(function (k) { return liveForms(k).some(dirty); })) { warn(also(CHANGED, keys)); return; }
'''
new = ''''''
[[mutation]]
label = "S5b a cancelled native submit stays exempt"
file = "booth/static/embed.js"
test = "tests/test_antislop_s5b_browser.py::test_embed_a_cancelled_submit_is_guarded_again"
old = ''' setTimeout(function () { if (ev.defaultPrevented && nativeSent === form) nativeSent = null; }, 0);
'''
new = ''''''
[[mutation]]
label = "S5b the skip covers every leave, not one"
file = "booth/static/embed.js"
test = "tests/test_antislop_s5b_browser.py::test_embed_the_skip_covers_one_leave"
old = ''' var skip = nativeSent;
nativeSent = null;
'''
new = ''' var skip = nativeSent;
'''
[[mutation]]
label = "S5b an in-place form holds a control dirty() cannot read"
file = "booth/templates/_marks.html"
test = "tests/test_antislop.py::test_the_in_place_client_can_read_every_page"
old = ''' <textarea name="text" rows="2" aria-label="a note on this booth, for the session that posted it"'''
new = ''' <select name="x"></select><textarea name="text" rows="2" aria-label="a note on this booth, for the session that posted it"'''
[[mutation]]
label = "S5b a region nests inside another"
file = "booth/templates/_marks.html"
test = "tests/test_antislop.py::test_the_in_place_client_can_read_every_page"
old = '''<form class="mark-add" method="post" action="/b/{{ name_url }}/note" data-inplace>'''
new = '''<form class="mark-add" method="post" action="/b/{{ name_url }}/note" data-inplace data-region="nested">'''
# ---- S5c: whose key it is, the doc bar, tile sizes, the rail's shadow, reveal names --------
[[mutation]]
label = "S5c a player gives up its keys to the page"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_player_keeps_its_keys"
old = ''' if (BoothKeys.isEditable(t) || (k !== 'Escape' && t.closest('video, audio'))) return true;'''
new = ''' if (BoothKeys.isEditable(t)) return true;'''
[[mutation]]
label = "S5c Enter opens the cursor tile from a control or any focused node"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_enter_on_a_control_does_not_open_the_cursor_tile"
old = ''' if (e.target === document.body || e.target === grid || tiles().indexOf(e.target) >= 0) {'''
new = ''' if (true) {'''
[[mutation]]
label = "S5c a focused 1:1 stage loses its arrows to the page"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_focused_pannable_stage_pans_with_the_arrows"
old = ''' if ((k === ' ' || k.indexOf('Arrow') === 0) && t.closest('.vstage.can-pan')) return true;
'''
new = ''''''
[[mutation]]
label = "S5c the grid cursor is a class only"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_the_grid_cursor_is_real_focus"
old = ''' t[at].focus({ preventScroll: true });
'''
new = ''''''
[[mutation]]
label = "S5c every tile the cursor visited stays focusable"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_the_grid_cursor_is_real_focus"
old = ''' if (el !== c) el.removeAttribute('tabindex');
'''
new = ''''''
[[mutation]]
label = "S5c Escape leaves focus on the tile it un-cursors"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_the_grid_cursor_is_real_focus"
old = ''' if (was && document.activeElement === was) was.blur();
at = -1; cur = null; mark(); break;'''
new = ''' at = -1; cur = null; tiles().forEach(function (x) { x.classList.remove('is-cursor'); }); break;'''
[[mutation]]
label = "S5c the focused cursor tile draws a second ring over the reticle"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_the_grid_cursor_is_real_focus"
old = ''' .item.is-cursor:focus-visible{outline:none}'''
new = ''' .item.is-cursor:focus-visible{}'''
[[mutation]]
label = "S5c focus that lands on a tile does not make it the cursor"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_focus_returned_to_a_tile_makes_it_the_cursor"
old = ''' if (i >= 0 && i !== at) { at = i; cur = e.target.getAttribute('data-item'); mark(); }'''
new = ''' if (false) { at = i; cur = e.target.getAttribute('data-item'); mark(); }'''
[[mutation]]
label = "S5c a control is back inside the doc's summary"
file = "booth/templates/booth.html"
test = "tests/test_antislop.py::test_the_doc_summary_holds_no_controls"
old = ''' <span class="doc-name">{{ it.name }}</span>
</summary>'''
new = ''' <span class="doc-name">{{ it.name }}</span>
<a class="doc-act" href="view?f={{ it.url }}">⤢</a>
</summary>'''
[[mutation]]
label = "S5c a closed fold leaves the doc's body showing"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_closed_doc_keeps_its_tools"
old = ''' .item-doc:has(.doc-fold:not([open])) > .doc-inline{display:none}'''
new = ''' .item-doc:has(.doc-fold:not([open])) > .doc-none{display:none}'''
[[mutation]]
label = "S5c the tools inside the fold, which a closed fold hides"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_a_closed_doc_keeps_its_tools"
old = ''' </summary>
</details>
<div class="doc-tools">'''
new = ''' </summary>
<div class="doc-tools">'''
[[mutation]]
label = "S5c the doc's tools wrap under the name on a wide screen"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_the_doc_bar_is_one_row_wide_and_two_on_a_phone"
old = ''' .doc-bar{display:flex;align-items:stretch;'''
new = ''' .doc-bar{display:block;align-items:stretch;'''
[[mutation]]
label = "S5c a closed fold opens again on the next save"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_closed_doc_stays_closed_through_a_save"
old = ''' if (freshDetails[k]) freshDetails[k].open = d.open;'''
new = ''' if (false) freshDetails[k].open = d.open;'''
[[mutation]]
label = "S5c tile pictures carry no size"
file = "booth/templates/booth.html"
test = "tests/test_antislop.py::test_tile_images_carry_their_drawn_size"
old = '''{% if it.dims %} width="{{ it.dims[0] }}" height="{{ it.dims[1] }}"{% endif %}'''
new = ''''''
[[mutation]]
label = "S5c tile pictures carry no size, so a link lands off its tile"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_a_link_to_a_tile_lands_where_it_points"
old = '''{% if it.dims %} width="{{ it.dims[0] }}" height="{{ it.dims[1] }}"{% endif %}'''
new = ''''''
[[mutation]]
label = "S5c the gallery drops the size on its way to the template"
file = "booth/app.py"
test = "tests/test_antislop.py::test_tile_images_carry_their_drawn_size"
old = ''' "dims": image_dims(child, it),'''
new = ''' "dims": None,'''
[[mutation]]
label = "S5c the drawn size ignores the EXIF orientation"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_tile_images_carry_their_drawn_size"
old = ''' return (h, w) if orientation in (5, 6, 7, 8) else (w, h)'''
new = ''' return (w, h)'''
[[mutation]]
label = "S5c reading a size decodes the picture (PNG getexif)"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_reading_a_size_decodes_nothing"
old = ''' orientation = (im.getexif().get(0x0112, 1) if "exif" in im.info else 1)'''
new = ''' orientation = (im.getexif().get(0x0112, 1))'''
[[mutation]]
label = "S5c a planted link is read through"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_a_planted_fifo_or_link_costs_its_size_and_never_hangs"
old = ''' fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)'''
new = ''' fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)'''
[[mutation]]
label = "S5c a planted FIFO hangs the render"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_a_planted_fifo_or_link_costs_its_size_and_never_hangs"
old = ''' fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)'''
new = ''' fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)'''
[[mutation]]
label = "S5c every render reads every header again"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_a_size_is_read_once_per_version_of_the_file"
old = '''@functools.lru_cache(maxsize=SIZE_CACHE)
'''
new = ''''''
[[mutation]]
label = "S5c a replaced file keeps its old size"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_a_size_is_read_once_per_version_of_the_file"
old = ''' return _read_size(str(path), st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns, st.st_ctime_ns)'''
new = ''' return _read_size(str(path), st.st_dev, st.st_ino, 0, 0, 0)'''
[[mutation]]
label = "S5c no scroll padding: a control reached by Tab hides under the rail"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_tabbed_control_does_not_hide_under_the_rail"
old = ''' html:has(.rail){--rail-pad:calc(var(--rail-h, 120px) + 12px);scroll-padding-top:var(--rail-pad)}'''
new = ''' html:has(.rail){--rail-pad:calc(var(--rail-h, 120px) + 12px)}'''
[[mutation]]
label = "S5c the tile keeps its scroll margin too, and a jump lands two rails down"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_jump_lands_just_below_the_rail"
old = ''' border-radius:var(--radius-xl);overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--shadow-sm)}'''
new = ''' border-radius:var(--radius-xl);overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--shadow-sm);
scroll-margin-top:calc(var(--rail-h, 120px) + 12px)}'''
[[mutation]]
label = "S5c with scripts off, a phone falls back to the desktop rail height"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_the_rail_padding_falls_back_with_scripts_off"
old = ''' html:has(.rail){--rail-pad:calc(var(--rail-h, 205px) + 12px)}'''
new = ''' html:has(.rail){--rail-pad:calc(var(--rail-h, 120px) + 12px)}'''
[[mutation]]
label = "S5c every page gets the rail's scroll padding"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_pages_without_a_rail_have_no_scroll_padding"
old = ''' html:has(.rail){--rail-pad:calc(var(--rail-h, 120px) + 12px);scroll-padding-top:var(--rail-pad)}'''
new = ''' html{--rail-pad:calc(var(--rail-h, 120px) + 12px);scroll-padding-top:var(--rail-pad)}'''
[[mutation]]
label = "S5c a tile's reveal carries a fixed label again"
file = "booth/templates/booth.html"
test = "tests/test_antislop.py::test_reveal_glyphs_are_hidden_from_the_name"
old = ''' <button type="button" class="reveal"><span class="rv-glyph" aria-hidden="true">'''
new = ''' <button type="button" class="reveal" aria-label="reveal {{ name }}"><span class="rv-glyph" aria-hidden="true">'''
[[mutation]]
label = "S5c a tile's reveal glyph is read aloud"
file = "booth/templates/booth.html"
test = "tests/test_antislop.py::test_reveal_glyphs_are_hidden_from_the_name"
old = ''' <button type="button" class="reveal"><span class="rv-glyph" aria-hidden="true">'''
new = ''' <button type="button" class="reveal"><span class="rv-glyph">'''
[[mutation]]
label = "S5c a tile's reveal flips its whole text, and its name loses the item"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_reveal_names_follow_their_words"
old = ''' btn.querySelector('.rv-word').textContent = on ? 'hide' : 'reveal';'''
new = ''' btn.textContent = on ? '🙈 hide' : '👁 reveal';'''
[[mutation]]
label = "S5c the review's reveal flips its whole text"
file = "booth/templates/view.html"
test = "tests/test_antislop_s5c_browser.py::test_reveal_names_follow_their_words"
old = ''' rv.querySelector('.rv-word').textContent = on ? 'hide' : 'reveal';'''
new = ''' rv.textContent = on ? '🙈 hide' : '👁 reveal — blur is cosmetic'; return;'''
[[mutation]]
label = "S5c compare's reveal flips its whole text"
file = "booth/templates/compare.html"
test = "tests/test_antislop_s5c_browser.py::test_reveal_names_follow_their_words"
old = ''' btn.querySelector('.rv-word').textContent = on ? 'hide' : 'reveal';'''
new = ''' btn.textContent = on ? '🙈 hide' : '👁 reveal — blur is cosmetic'; return;'''
[[mutation]]
label = "S5c a doc page's reveal flips its whole text"
file = "booth/templates/doc.html"
test = "tests/test_antislop_s5c_browser.py::test_reveal_names_follow_their_words"
old = ''' rv.querySelector('.rv-word').textContent = on ? 'hide' : 'reveal';'''
new = ''' rv.textContent = on ? '🙈 hide' : '👁 reveal — blur is cosmetic'; return;'''
[[mutation]]
label = "S5c Reveal all claims a pressed state its words already say"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_reveal_names_follow_their_words"
old = ''' btn.querySelector('.ra-note').hidden = on;
}'''
new = ''' btn.querySelector('.ra-note').hidden = on;
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
}'''
[[mutation]]
label = "S5c Reveal all loses its on look with the attribute"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_reveal_names_follow_their_words"
old = ''' .reveal-all .reveal-all-btn{color:var(--text-body);border-color:var(--border-strong)}'''
new = ''''''
[[mutation]]
label = "S5c Reveal all's glyph is read aloud"
file = "booth/templates/view.html"
test = "tests/test_antislop.py::test_reveal_glyphs_are_hidden_from_the_name"
old = '''<span class="ra-glyph" aria-hidden="true">👁</span>'''
new = '''<span class="ra-glyph">👁</span>'''
[[mutation]]
label = "S5c a refused batch's forms never enter unsent (booth-dev's note)"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_batch_refusal_outlives_an_unrelated_save"
old = ''' if (!saved.some(function (x) { return x.key === k; })) unsent[k] = told;'''
new = ''''''
[[mutation]]
label = "S5c only the last standing failure is said again"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_two_standing_failures_are_both_said_once_each"
old = ''' return words.join(' ');'''
new = ''' return words[words.length - 1];'''
[[mutation]]
label = "S5c a refusal shared by two forms is said twice"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_two_standing_failures_are_both_said_once_each"
old = ''' keys.forEach(function (k) { if (words.indexOf(unsent[k]) < 0) words.push(unsent[k]); });'''
new = ''' keys.forEach(function (k) { words.push(unsent[k]); });'''
# ---- S5c, folded from the heid bug-hunt (HRÖSKVA, panel 4/4) --------------------------
[[mutation]]
label = "S5c R1 the cursor lands on a doc closed with its ✕"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_the_cursor_skips_a_closed_doc"
old = '''grid.querySelectorAll('figure.item:not(.is-closed)')'''
new = '''grid.querySelectorAll('figure.item')'''
[[mutation]]
label = "S5c R10 n leaves a closed doc's fold shut over its note field"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_n_opens_the_note_on_a_closed_doc"
old = ''' if (fold) fold.open = true;'''
new = ''''''
[[mutation]]
label = "S5c R5 Enter follows the first link that merely starts with view"
file = "booth/templates/booth.html"
test = "tests/test_antislop_s5c_browser.py::test_enter_reviews_a_file_named_view"
old = ''' click('a[href^="view?f="]');'''
new = ''' click('a[href^="view"]');'''
[[mutation]]
label = "S5c R2 the observer stays on the first rail"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_the_rail_height_follows_the_live_rail_after_a_save"
old = ''' if (ro && rail !== seen) {'''
new = ''' if (ro && !seen) {'''
[[mutation]]
label = "S5c R9 a Tab between stuck rail links scrolls the page up by the rail"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_tab_between_rail_links_does_not_move_the_page"
old = ''' .rail :is(a,button,input,summary){scroll-margin-top:calc(-1 * var(--rail-pad, 0px))}'''
new = ''''''
[[mutation]]
label = "S5c R3 a later failure buries an earlier one"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_later_failure_does_not_bury_an_earlier_one"
old = ''' return before ? before + ' ' + text : text;'''
new = ''' return text;'''
[[mutation]]
label = "S5c R3 a partly refused batch buries an earlier failure"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_two_standing_failures_are_both_said_once_each"
old = ''' else if (told) warn(also(told, mine));'''
new = ''' else if (told) warn(told);'''
[[mutation]]
label = "S5c R8 a focused player swallows Escape"
file = "booth/templates/base.html"
test = "tests/test_antislop_s5c_browser.py::test_a_player_keeps_its_keys"
old = '''(k !== 'Escape' && t.closest('video, audio'))'''
new = '''t.closest('video, audio')'''
[[mutation]]
label = "S5c R7 a file rewritten in place with its mtime restored keeps its old size"
file = "booth/thumbs.py"
test = "tests/test_antislop.py::test_a_file_replaced_in_place_keeps_no_stale_size"
old = '''st.st_mtime_ns, st.st_ctime_ns)'''
new = '''st.st_mtime_ns, 0)'''