- 1:1 start-aligns. The centred flex item overflowed both sides and the start was unreachable; measured, a 3000px picture hid its leftmost 980px. Auto margins still centre a small picture. - Drag lifecycle: a move with no button ends the drag, so a press released outside the stage never pans on a later hover. Capture is now load-bearing in a test. The threshold is 4px of total movement. - A press on the stage's own scrollbar is never a pan. The arrows clamp to the stage's client box, so they are never under a classic scrollbar. The test runs a browser without --hide-scrollbars and asserts the gutter exists. - Stacked, the arrows' CSS spot is the stage's centre (30vh), set in view.html because base.html lost to the page's later rule. - The stage reveal is `hidden` until bound, and keeps Fit's drop shadow when revealed. A blurred picture composes blur() drop-shadow(). - The mode follows another tab. A failed or unknown size returns the arrows to their CSS spot. - Tests: object-position, vertical centring, the Fit half of aria-pressed, a storage read that throws, a large picture's toggle, Fit forgetting 1:1, single-axis pan. - Declared: the r2b reveal test reads "no blur" (the shadow stays), and the r2_flow 360px-offset row is retired. Mutation tables 137/137 across four. 810 passed.
225 lines
9.1 KiB
TOML
225 lines
9.1 KiB
TOML
# R2 — the review flow: falsifiers the round claims, and the change each forbids.
|
|
#
|
|
# Every row was proved RED under its mutation in the session that wrote it,
|
|
# then committed here so the proof is an artifact rather than scrollback. The
|
|
# browser rows need the Playwright Chromium the browser tests already use.
|
|
#
|
|
# Deliberately ABSENT: single guards inside a defence in depth, each of which
|
|
# stays green when removed alone because another layer still holds — so a row
|
|
# for any one of them would be a vacuous proof, and this table's own first run
|
|
# said so. `.seen`'s O_NOFOLLOW, O_NONBLOCK and S_ISREG (the FIFO/symlink test
|
|
# covers them together); and `flagged_targets`' `error is None`, since
|
|
# hydration already strips the target from a damaged mark.
|
|
#
|
|
# RETIRED (r2c S3, 2026-09-23): the row on the next arrow's 360px rail offset —
|
|
# the arrows now sit at the drawn picture, clamped inside the stage; the test was
|
|
# replaced as declared in r2c's contract, and its successors are in r2c.toml.
|
|
#
|
|
# RETIRED (r2b D1, 2026-09-23): four rows proving the facts-line row controls
|
|
# (visible at rest, compact, on the facts line) — the operator ruled those
|
|
# controls hover-revealed over the preview strip, and their tests were replaced
|
|
# as declared in r2b's contract. Their successors are in r2b.toml.
|
|
#
|
|
# The serialization row is only a falsifier because its test HOLDS the first
|
|
# refresh in the client: localhost alone never lost the race, and the first
|
|
# draft of that test stayed green with serialization deleted.
|
|
|
|
unit = "the Desk, the lightbox, the review, and the in-place client"
|
|
|
|
[[mutation]]
|
|
label = 'C1 ordinals count from 0, not 1'
|
|
file = "booth/items.py"
|
|
test = "tests/test_flow.py::test_a_filtered_tile_keeps_its_number_in_the_whole_set"
|
|
old = '''ordinal=len(items) + 1,'''
|
|
new = '''ordinal=len(items),'''
|
|
|
|
[[mutation]]
|
|
label = 'C2 .seen: a nested-too-deep marker escapes the never-raises read'
|
|
file = "booth/items.py"
|
|
test = "tests/test_flow.py::test_a_deeply_nested_seen_marker_reads_as_nothing_seen"
|
|
old = '''except (UnicodeDecodeError, ValueError, RecursionError):'''
|
|
new = '''except (UnicodeDecodeError, ValueError):'''
|
|
|
|
[[mutation]]
|
|
label = 'C3 a non-finite q is accepted as a q-value'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_a_non_finite_q_is_malformed"
|
|
old = ''' raise ValueError("non-finite q")'''
|
|
new = ''' pass'''
|
|
|
|
[[mutation]]
|
|
label = 'C3 204 on an explicit JSON Accept becomes the 303'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_an_explicit_json_accept_gets_204_and_the_write_still_lands"
|
|
old = ''' return Response(status_code=204)'''
|
|
new = ''' pass'''
|
|
|
|
[[mutation]]
|
|
label = 'C3 back=view lands on the review for a doc too (ring check dropped)'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_back_view_lands_on_the_review_only_for_a_media_item"
|
|
old = ''' if f in ring:'''
|
|
new = ''' if True:'''
|
|
|
|
[[mutation]]
|
|
label = 'C4 the Desk counts orphan flags'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_a_flag_on_a_file_that_is_gone_stays_visible_and_withdrawable"
|
|
old = '''"flags": len(flagged_targets(marks) & {it.rel for it in items}),'''
|
|
new = '''"flags": len(flagged_targets(marks)),'''
|
|
|
|
[[mutation]]
|
|
label = 'C4 landed_at follows symlinks'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_the_content_clock_reads_the_booth_not_what_its_links_point_at"
|
|
old = ''' st = p.lstat()'''
|
|
new = ''' st = p.stat()'''
|
|
|
|
[[mutation]]
|
|
label = 'C4 one unreadable entry reads the whole booth as landed NOW'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_one_unreadable_entry_costs_that_entry_not_the_booth"
|
|
old = '''pin it in "new" forever.
|
|
continue'''
|
|
new = '''pin it in "new" forever.
|
|
return time.time()'''
|
|
|
|
[[mutation]]
|
|
label = 'C4 a non-web bookmark URL becomes a link'
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow.py::test_the_desk_never_makes_a_non_web_url_clickable"
|
|
old = '''{% set web = e.url.lower().startswith(('http://', 'https://')) %}'''
|
|
new = '''{% set web = true %}'''
|
|
|
|
[[mutation]]
|
|
label = 'C4 a non-web bench URL becomes a link'
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow.py::test_the_desk_never_makes_a_non_web_url_clickable"
|
|
old = '''{% set web = b.url.lower().startswith(('http://', 'https://')) %}'''
|
|
new = '''{% set web = true %}'''
|
|
|
|
[[mutation]]
|
|
label = 'C5 audio/video tiles lose their review link'
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_a_sound_only_booth_can_open_the_review"
|
|
old = '''{% if it.kind in ('video', 'audio') %}<a class="rv-link"'''
|
|
new = '''{% if false %}<a class="rv-link"'''
|
|
|
|
[[mutation]]
|
|
label = 'C6 a NUL in ?f escapes as a 500'
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_a_nul_in_the_review_path_is_a_404_not_a_500"
|
|
old = ''' except (OSError, ValueError):
|
|
# ValueError: an embedded NUL.'''
|
|
new = ''' except OSError:
|
|
# ValueError: an embedded NUL.'''
|
|
|
|
[[mutation]]
|
|
label = 'C3 client: no busy guard (a double-click writes twice)'
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_an_unsaved_choice_survives_a_save_elsewhere_and_a_double_click_writes_once"
|
|
old = ''' if (form.__busy) return;
|
|
'''
|
|
new = ''''''
|
|
|
|
[[mutation]]
|
|
label = 'C3 client: an unsent radio is not carried across a swap'
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_an_unsaved_choice_survives_a_save_elsewhere_and_a_double_click_writes_once"
|
|
old = ''' if (el.checked !== el.defaultChecked) t.checked = el.checked;'''
|
|
new = ''''''
|
|
|
|
[[mutation]]
|
|
label = 'C3 client: saves are not serialized'
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_quick_successive_flags_all_show"
|
|
old = ''' queue = queue.then(function () { return run(form, data); })'''
|
|
new = ''' queue = run(form, data)'''
|
|
|
|
[[mutation]]
|
|
label = 'C3 the standalone marks page has no region'
|
|
file = "booth/templates/marks.html"
|
|
test = "tests/test_flow_browser.py::test_the_standalone_marks_page_updates_in_place"
|
|
old = '''<div class="marks-panel" data-region="marks-panel">'''
|
|
new = '''<div class="marks-panel">'''
|
|
|
|
[[mutation]]
|
|
label = "resolver: an entry that cannot be stat'd raises out of booth_items"
|
|
file = "booth/items.py"
|
|
test = "tests/test_items.py::test_a_folder_that_lists_but_cannot_be_searched_costs_its_files_not_the_index"
|
|
old = '''
|
|
if not p.is_file():
|
|
continue
|
|
except OSError:
|
|
continue'''
|
|
new = '''
|
|
if not p.is_file():
|
|
continue
|
|
except FileNotFoundError:
|
|
continue'''
|
|
|
|
[[mutation]]
|
|
label = "the stacked Desk column is a bare 1fr (content sets its minimum)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_desk_never_scrolls_sideways_at_any_width"
|
|
old = '''
|
|
@media (max-width:1000px){.desk{grid-template-columns:minmax(0,1fr)}}'''
|
|
new = '''
|
|
@media (max-width:1000px){.desk{grid-template-columns:1fr}}'''
|
|
|
|
[[mutation]]
|
|
label = "a long unbreakable install path in <code> scrolls the page sideways"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_desk_never_scrolls_sideways_at_any_width"
|
|
old = '''
|
|
padding:1px 6px;border-radius:var(--radius-sm);border:1px solid var(--border-subtle);overflow-wrap:anywhere}'''
|
|
new = '''
|
|
padding:1px 6px;border-radius:var(--radius-sm);border:1px solid var(--border-subtle)}'''
|
|
|
|
[[mutation]]
|
|
label = "the row's text column cannot shrink (the 700-1000px window overflows)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_desk_never_scrolls_sideways_at_any_width"
|
|
old = '''
|
|
.desk-main{flex:1 1 auto;min-width:0}'''
|
|
new = '''
|
|
.desk-main{flex:1 1 auto}'''
|
|
|
|
[[mutation]]
|
|
label = "the wipe dialog shows the agent-made name raw (bidi, newline)"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow_browser.py::test_the_wipe_dialog_shows_what_is_being_wiped_and_never_fails_open"
|
|
old = '''
|
|
if (!confirm(word(shown(form.getAttribute('data-booth') || '')))) ev.preventDefault();'''
|
|
new = '''
|
|
if (!confirm(word(form.getAttribute('data-booth') || ''))) ev.preventDefault();'''
|
|
|
|
[[mutation]]
|
|
label = "an unknown data-confirm word submits with no prompt (fail open)"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow_browser.py::test_the_wipe_dialog_shows_what_is_being_wiped_and_never_fails_open"
|
|
old = '''
|
|
var word = WORDS[form.getAttribute('data-confirm')] || ASK;
|
|
if (!confirm('''
|
|
new = '''
|
|
var word = WORDS[form.getAttribute('data-confirm')];
|
|
if (word && !confirm('''
|
|
|
|
[[mutation]]
|
|
label = "everything else in activity order again (a look moves a booth up)"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_everything_else_is_ordered_by_last_update_not_by_looking"
|
|
old = '''
|
|
rest.sort(key=lambda b: (-b["landed_at"], b["name"]))'''
|
|
new = '''
|
|
pass'''
|
|
|
|
[[mutation]]
|
|
label = "everything else breaks an update tie by name reversed"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_everything_else_breaks_an_update_tie_by_name"
|
|
old = '''
|
|
rest.sort(key=lambda b: (-b["landed_at"], b["name"]))'''
|
|
new = '''
|
|
rest.sort(key=lambda b: (b["landed_at"], b["name"]), reverse=True)'''
|