GET /b/{name}/compare with the conjunction 404 (containment AND the review
ring), both sides recorded as seen, view state (side, link) mapped from a
closed set onto every link, side-keyed regions, and back=compare in
_mark_redirect. compare.html: two stages sharing one set of rows, the strip
as picker (the side active now), linked and per-side stepping, X/L/Z/A/B/C
keys under the review's guards, synced pan by fraction with an echo guard,
per-side blur reveals, JS-off parity.
The stage machinery moves out of view.html into _stage_js.html
(BoothMode.bind, BoothStage.attach), shared by the review and compare. The
review gains a Compare control and a C key. At phone width a full top bar
wraps.
Tables: r2c's 15 stage rows re-pointed to _stage_js.html; r2b's phone
top-bar row re-anchored (the wrap made it vacuous alone); new r3.toml. The
contract records the wrap, equal stages and C on the compare page.
561 lines
23 KiB
TOML
561 lines
23 KiB
TOML
# R2b — the Desk row, Reveal all, the theme toggle: every falsifier the
|
|
# contract claims (docs/contracts/r2b_desk_reveal_theme.contract.md), and the
|
|
# change each forbids. Merge 1 is D2 + D2b (the blur half); merge 2 adds D1 + D3.
|
|
|
|
unit = "reveal all, the booth blur toggle (merge 1)"
|
|
|
|
[[mutation]]
|
|
label = "D2 reveal all does not lift the tile's blur"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_reveals_every_blurred_surface_and_survives_the_next_page"
|
|
old = '''
|
|
.reveal-all .item.blurred img,.reveal-all .item.blurred video,'''
|
|
new = '''
|
|
.reveal-all-OFF .item.blurred img,.reveal-all .item.blurred video,'''
|
|
|
|
[[mutation]]
|
|
label = "D2 no pre-paint re-application: the next page of the reel is blurred again"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_reveals_every_blurred_surface_and_survives_the_next_page"
|
|
old = '''
|
|
if (b !== null && sessionStorage.getItem('booth.reveal:' + b) === '1') d.classList.add('reveal-all');'''
|
|
new = '''
|
|
if (false) d.classList.add('reveal-all');'''
|
|
|
|
[[mutation]]
|
|
label = "D2 the reveal is not scoped to the booth (any reveal in the tab reveals every booth)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_on_booth_a_does_not_reveal_booth_b_or_the_desk"
|
|
old = '''
|
|
if (b !== null && sessionStorage.getItem('booth.reveal:' + b) === '1') d.classList.add('reveal-all');'''
|
|
new = '''
|
|
if (b !== null && sessionStorage.length > 0) d.classList.add('reveal-all');'''
|
|
|
|
[[mutation]]
|
|
label = "D2 per-tile reveal buttons do not stand down under reveal all"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_survives_an_in_place_save"
|
|
old = '''
|
|
.reveal-all .item.blurred .reveal,.reveal-all #vreveal{display:none}'''
|
|
new = '''
|
|
.reveal-all-OFF .item.blurred .reveal,.reveal-all #vreveal{display:none}'''
|
|
|
|
[[mutation]]
|
|
label = "D2 blur again wipes each item's own reveal"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_blur_again_restores_each_items_own_reveal"
|
|
old = '''
|
|
var on = d.classList.toggle('reveal-all');
|
|
try {'''
|
|
new = '''
|
|
var on = d.classList.toggle('reveal-all');
|
|
document.querySelectorAll('.item.revealed').forEach(function (i) { i.classList.remove('revealed'); });
|
|
try {'''
|
|
|
|
[[mutation]]
|
|
label = "D2 a storage write that throws swallows the click"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_never_shows_without_js_and_a_storage_failure_still_applies_the_click"
|
|
old = '''
|
|
var on = d.classList.toggle('reveal-all');
|
|
try {'''
|
|
new = '''
|
|
sessionStorage.setItem(key, '1');
|
|
var on = d.classList.toggle('reveal-all');
|
|
try {'''
|
|
|
|
[[mutation]]
|
|
label = "D2 the control shows without JS (server markup not hidden)"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_never_shows_without_js_and_a_storage_failure_still_applies_the_click"
|
|
old = '''
|
|
{% if all_items | selectattr('blurred') | list %}<button type="button" class="reveal-all-btn" data-reveal-all hidden title='''
|
|
new = '''
|
|
{% if all_items | selectattr('blurred') | list %}<button type="button" class="reveal-all-btn" data-reveal-all title='''
|
|
|
|
[[mutation]]
|
|
label = "D2 the control is markup even when nothing is blurred"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_reveal_all_is_in_the_markup_only_when_something_is_blurred_and_always_hidden"
|
|
old = '''
|
|
{% if all_items | selectattr('blurred') | list %}<button'''
|
|
new = '''
|
|
{% if true %}<button'''
|
|
|
|
[[mutation]]
|
|
label = "D2 the review page does not carry data-booth"
|
|
file = "booth/templates/view.html"
|
|
test = "tests/test_flow.py::test_reveal_all_is_in_the_markup_only_when_something_is_blurred_and_always_hidden"
|
|
old = '''
|
|
{% block html_attrs %} data-booth="{{ name }}"{% endblock %}'''
|
|
new = '''
|
|
{% block html_attrs %}{% endblock %}'''
|
|
|
|
[[mutation]]
|
|
label = "D2b the header control's label does not follow the server's fog state"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_the_booth_blur_toggle_works_without_js_and_lands_back_on_the_review"
|
|
old = '''
|
|
<input type="hidden" name="on" value="{{ '0' if booth_blurred else '1' }}">
|
|
<button title="{{ 'un-blur the whole booth'''
|
|
new = '''
|
|
<input type="hidden" name="on" value="1">
|
|
<button title="{{ 'un-blur the whole booth'''
|
|
|
|
[[mutation]]
|
|
label = "D2b the review's control drops `back` (fogging ejects you from the review)"
|
|
file = "booth/templates/view.html"
|
|
test = "tests/test_flow.py::test_the_booth_blur_toggle_works_without_js_and_lands_back_on_the_review"
|
|
old = '''
|
|
<input type="hidden" name="back" value="{{ file }}">'''
|
|
new = '''
|
|
'''
|
|
|
|
[[mutation]]
|
|
label = "D2b the Desk row does not say a booth is fogged"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow.py::test_the_booth_blur_toggle_works_without_js_and_lands_back_on_the_review"
|
|
old = '''
|
|
{% if b.booth_blurred %}<span class="badge badge-blur"'''
|
|
new = '''
|
|
{% if false %}<span class="badge badge-blur"'''
|
|
|
|
[[mutation]]
|
|
label = "D2b an item blurred only by the booth offers a per-item un-blur that does nothing"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_under_a_fogged_booth_each_items_blur_control_tells_the_truth"
|
|
old = '''
|
|
{% if it.blurred and not it.blurred_self %}'''
|
|
new = '''
|
|
{% if false %}'''
|
|
|
|
[[mutation]]
|
|
label = "D2b the per-item control reads the composed blur, not the item's own"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_under_a_fogged_booth_each_items_blur_control_tells_the_truth"
|
|
old = '''
|
|
"blurred_self": it.blurred_self,'''
|
|
new = '''
|
|
"blurred_self": it.blurred,'''
|
|
|
|
# ---- folds: the heid code-review ("BLITZ-2") and bug-hunt ("FENRIR-6") panels on merge 1
|
|
|
|
[[mutation]]
|
|
label = "a board holding files loses the blur controls its labels point at"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_a_board_with_files_gets_the_blur_controls_its_labels_point_at"
|
|
old = '''
|
|
{% if all_items %}
|
|
{# The fog form IS a region'''
|
|
new = '''
|
|
{% if not board %}
|
|
{# The fog form IS a region'''
|
|
|
|
[[mutation]]
|
|
label = "a blurred doc's own page renders clear"
|
|
file = "booth/templates/doc.html"
|
|
test = "tests/test_flow.py::test_a_blurred_docs_own_page_is_blurred_too"
|
|
old = '''
|
|
<div class="docbody{% if blurred %} is-blurred{% endif %}" id="docbody">'''
|
|
new = '''
|
|
<div class="docbody" id="docbody">'''
|
|
|
|
[[mutation]]
|
|
label = "the review offers Reveal all when only a doc (off the ring) is blurred"
|
|
file = "booth/templates/view.html"
|
|
test = "tests/test_flow.py::test_reveal_all_renders_where_it_can_act"
|
|
old = '''
|
|
{% if film | selectattr('blurred') | list %}<button'''
|
|
new = '''
|
|
{% if true %}<button'''
|
|
|
|
[[mutation]]
|
|
label = "the fog form is a GET (changes nothing with scripts off)"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_the_booth_blur_toggle_works_without_js_and_lands_back_on_the_review"
|
|
old = '''<form class="blur-all{% if booth_blurred %} is-on{% endif %}" method="post" action="/b/{{ name_url }}/blurbooth">
|
|
<input type="hidden" name="on" value="{{ '0' if booth_blurred else '1' }}">
|
|
<button title="{{ 'un-blur the whole booth — per-item'''
|
|
new = '''<form class="blur-all{% if booth_blurred %} is-on{% endif %}" method="get" action="/b/{{ name_url }}/blurbooth">
|
|
<input type="hidden" name="on" value="{{ '0' if booth_blurred else '1' }}">
|
|
<button title="{{ 'un-blur the whole booth — per-item'''
|
|
|
|
[[mutation]]
|
|
label = "the swap stops carrying an item's own reveal"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_an_items_own_reveal_survives_an_in_place_save"
|
|
old = '''
|
|
['revealed', 'is-closed'].forEach(function (c) {'''
|
|
new = '''
|
|
['is-closed'].forEach(function (c) {'''
|
|
|
|
[[mutation]]
|
|
label = "a storage READ that throws raises out of the pre-paint script"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_never_shows_without_js_and_a_storage_failure_still_applies_the_click"
|
|
old = '''
|
|
try {
|
|
if (b !== null && sessionStorage.getItem('booth.reveal:' + b) === '1') d.classList.add('reveal-all');
|
|
} catch (e) {}'''
|
|
new = '''
|
|
if (b !== null && sessionStorage.getItem('booth.reveal:' + b) === '1') d.classList.add('reveal-all');'''
|
|
|
|
[[mutation]]
|
|
label = "fogging writes through a planted marker link"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_fogging_never_writes_through_a_planted_marker_link"
|
|
old = '''
|
|
try:
|
|
os.lstat(marker)
|
|
return True
|
|
except FileNotFoundError:
|
|
pass
|
|
try:
|
|
os.close(os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o644))
|
|
except FileExistsError:
|
|
pass # lost a race to another fog: still fogged
|
|
return True'''
|
|
new = '''
|
|
marker.touch(exist_ok=True)
|
|
return True'''
|
|
|
|
[[mutation]]
|
|
label = "the fog landing echoes `back` unchecked"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_the_fog_landing_is_built_from_the_ring_never_echoed"
|
|
old = '''
|
|
if back and back in review_chain(booth_items(booth)):'''
|
|
new = '''
|
|
if back:'''
|
|
|
|
[[mutation]]
|
|
label = "the fog form is outside every region (a swap leaves its label stale)"
|
|
file = "booth/templates/booth.html"
|
|
test = "tests/test_flow.py::test_the_booth_blur_toggle_works_without_js_and_lands_back_on_the_review"
|
|
old = '''
|
|
<span class="region-wrap" data-region="blur-booth"><form class="blur-all'''
|
|
new = '''
|
|
<span class="region-wrap"><form class="blur-all'''
|
|
|
|
[[mutation]]
|
|
label = "Space on a focused review button moves to the next item"
|
|
file = "booth/templates/view.html"
|
|
test = "tests/test_flow_browser.py::test_space_on_a_focused_review_button_presses_it_and_does_not_move_on"
|
|
old = '''e.target !== player && !(e.target.closest && e.target.closest('button, a, summary'))) {'''
|
|
new = '''e.target !== player) {'''
|
|
|
|
[[mutation]]
|
|
label = "the top-bar controls squeeze into multi-line stacks at phone width"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_review_and_doc_top_bars_fit_a_phone"
|
|
# r3 re-anchored this row. r3 added a Compare control to the review's bar and
|
|
# made a full bar WRAP at phone width (a fogged booth already overflowed it by
|
|
# 3px at 390). A wrapping bar never squeezes, so removing the no-wrap rules
|
|
# alone went VACUOUS: the property is now held by both. The defeating change is
|
|
# losing both; r3.toml rows the wrap on its own.
|
|
old = '''
|
|
.blur-all button,.reveal-all-btn{white-space:nowrap}
|
|
@media (max-width:600px){.reveal-all-btn .ra-note{display:none}}
|
|
/* R3: the review's Compare control keeps only its glyph at phone width; its
|
|
title still says what it does (and C does it). */
|
|
@media (max-width:600px){.vcompare-l{display:none}}
|
|
/* ...and at phone width a top bar that cannot hold its controls WRAPS rather
|
|
than scrolling the page sideways. The review's bar was full before R3 (a
|
|
fogged booth overflowed it by 3px at 390); compare's bar holds more. */
|
|
@media (max-width:600px){.vbar{flex-wrap:wrap;row-gap:6px}}'''
|
|
new = '''
|
|
@media (max-width:600px){.vcompare-l{display:none}}'''
|
|
|
|
[[mutation]]
|
|
label = "the Desk strip under another booth's reveal is lifted by a whisker (blur(0px) is not blurred)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_on_booth_a_does_not_reveal_booth_b_or_the_desk"
|
|
old = '''
|
|
.blurred-thumb{filter:blur(16px)}'''
|
|
new = '''
|
|
.blurred-thumb{filter:blur(0px)}'''
|
|
|
|
# ---- the flake: the test browser has no internet (positive control per file)
|
|
|
|
[[mutation]]
|
|
label = "the flow test browser can reach the internet (Google Fonts can stall networkidle)"
|
|
file = "tests/test_flow_browser.py"
|
|
test = "tests/test_flow_browser.py::test_the_test_browser_has_no_internet"
|
|
old = '''
|
|
b = pw.chromium.launch(args=OFFLINE)'''
|
|
new = '''
|
|
b = pw.chromium.launch()'''
|
|
|
|
[[mutation]]
|
|
label = "the embed test browser can reach the internet"
|
|
file = "tests/test_embed_browser.py"
|
|
test = "tests/test_embed_browser.py::test_the_test_browser_has_no_internet"
|
|
old = '''
|
|
b = pw.chromium.launch(args=OFFLINE)'''
|
|
new = '''
|
|
b = pw.chromium.launch()'''
|
|
|
|
# ---- merge 2: D1 the Desk row, D1b dates, D3 the theme toggle
|
|
# Successors to the four r2_flow rows retired for D1. One branch is proved by
|
|
# reading, not here: a true touch LAPTOP (fine pointer + a coarse one) cannot be
|
|
# emulated — Chromium's touch emulation makes the primary pointer coarse.
|
|
|
|
[[mutation]]
|
|
label = "D1 the cluster is visible at rest"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_row_controls_take_no_room_where_a_hover_exists"
|
|
old = '''
|
|
opacity:0;pointer-events:none;transition:opacity var(--dur-1) var(--ease-out)}'''
|
|
new = '''
|
|
opacity:1;pointer-events:none;transition:opacity var(--dur-1) var(--ease-out)}'''
|
|
|
|
[[mutation]]
|
|
label = "D1 hover shows the cluster but leaves it unclickable"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_row_controls_take_no_room_where_a_hover_exists"
|
|
old = '''
|
|
.desk-row:hover .desk-acts,.desk-row:focus-within .desk-acts{opacity:1;pointer-events:auto}'''
|
|
new = '''
|
|
.desk-row:hover .desk-acts,.desk-row:focus-within .desk-acts{opacity:1}'''
|
|
|
|
[[mutation]]
|
|
label = "D1 the cluster stays in flow where a hover exists (it takes room)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_row_controls_take_no_room_where_a_hover_exists"
|
|
old = '''
|
|
.desk-acts{position:absolute;top:17px;left:calc(12px + 210px - 5px);transform:translateX(-100%);flex:none;gap:4px;padding:3px;'''
|
|
new = '''
|
|
.desk-acts{position:static;top:17px;left:calc(12px + 210px - 5px);transform:translateX(-100%);flex:none;gap:4px;padding:3px;'''
|
|
|
|
[[mutation]]
|
|
label = "D1 hover-only everywhere (no controls at all on touch)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_on_touch_the_row_controls_are_visible_in_flow_and_at_least_28px"
|
|
old = '''
|
|
.desk-acts{flex:1 0 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px}'''
|
|
new = '''
|
|
.desk-acts{flex:1 0 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px;opacity:0}'''
|
|
|
|
[[mutation]]
|
|
label = "D1 touch controls fall below the 28px floor"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_on_touch_the_row_controls_are_visible_in_flow_and_at_least_28px"
|
|
old = '''
|
|
min-height:32px;min-width:32px;padding:0 10px;font-family:var(--font-mono);font-size:var(--size-caption);'''
|
|
new = '''
|
|
min-height:18px;min-width:18px;padding:0 10px;font-family:var(--font-mono);font-size:var(--size-caption);'''
|
|
|
|
[[mutation]]
|
|
label = "D1 zip back in the middle"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow_browser.py::test_the_row_controls_run_zip_keep_or_release_then_wipe"
|
|
old = '''
|
|
<a class="dl-link" href="/b/{{ b.name_url }}/?download=1" title="download this booth as a zip">⬇ zip</a>
|
|
{%- if b.kept %}
|
|
<form class="release"'''
|
|
new = '''
|
|
{%- if b.kept %}
|
|
<a class="dl-link" href="/b/{{ b.name_url }}/?download=1" title="download this booth as a zip">⬇ zip</a>
|
|
<form class="release"'''
|
|
|
|
[[mutation]]
|
|
label = "D1 a held booth's pill reads as counting down"
|
|
file = "booth/templates/index.html"
|
|
test = "tests/test_flow.py::test_the_lifetime_pill_class_is_kept_held_or_counting"
|
|
old = '''('life-held' if b.hold in ('open', 'unreadable') else 'life-count')'''
|
|
new = '''('life-held' if b.hold == 'never' else 'life-count')'''
|
|
|
|
[[mutation]]
|
|
label = "D1b an unknown birth time renders a guess"
|
|
file = "booth/templates/_dates.html"
|
|
test = "tests/test_flow.py::test_created_and_updated_are_dated_facts_and_none_says_nothing"
|
|
old = '''{% macro dates(created_at, landed_at, now) -%}'''
|
|
new = '''{% macro dates(created_at, landed_at, now) -%}{%- set created_at = created_at or landed_at -%}'''
|
|
|
|
[[mutation]]
|
|
label = "D1b updated is measured from the wrong clock"
|
|
file = "booth/templates/_dates.html"
|
|
test = "tests/test_flow.py::test_created_and_updated_are_dated_facts_and_none_says_nothing"
|
|
old = '''updated {{ age|ago }}'''
|
|
new = '''updated {{ (now - created_at)|ago }}'''
|
|
|
|
[[mutation]]
|
|
label = "D3 a stored theme is not applied at load (a reload forgets it)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
|
|
old = '''
|
|
if (t === 'light' || t === 'dark') d.setAttribute('data-theme', t);'''
|
|
new = '''
|
|
if (false) d.setAttribute('data-theme', t);'''
|
|
|
|
[[mutation]]
|
|
label = "D3 System snapshots the OS instead of following it live"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
|
|
old = '''
|
|
if (c === 'system') d.removeAttribute('data-theme'); else d.setAttribute('data-theme', c);'''
|
|
new = '''
|
|
if (c === 'system') d.setAttribute('data-theme', matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'); else d.setAttribute('data-theme', c);'''
|
|
|
|
[[mutation]]
|
|
label = "D3 forced light is not in the sheet"
|
|
file = "booth/templates/_svos_tokens.css"
|
|
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
|
|
old = '''
|
|
:root[data-theme="light"] {'''
|
|
new = '''
|
|
:root[data-theme="light-OFF"] {'''
|
|
|
|
[[mutation]]
|
|
label = "D3 the toggle shows without JS (display beats [hidden])"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_theme_toggle_never_shows_without_js_and_a_storage_failure_still_applies"
|
|
old = '''
|
|
.theme[hidden]{display:none}'''
|
|
new = '''
|
|
'''
|
|
|
|
[[mutation]]
|
|
label = "D3 a storage write that throws swallows the choice"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_theme_toggle_never_shows_without_js_and_a_storage_failure_still_applies"
|
|
old = '''
|
|
var c = b.getAttribute('data-theme-choice');
|
|
if (c === 'system')'''
|
|
new = '''
|
|
var c = b.getAttribute('data-theme-choice');
|
|
localStorage.setItem('booth.theme', c);
|
|
if (c === 'system')'''
|
|
|
|
[[mutation]]
|
|
label = "D3 a stylesheet precedes the stored-theme script (a flash)"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow.py::test_a_forced_theme_is_applied_before_first_paint"
|
|
old = '''
|
|
<script>
|
|
(function () {
|
|
var d = document.documentElement, b = d.getAttribute('data-booth');'''
|
|
new = '''
|
|
<style></style>
|
|
<script>
|
|
(function () {
|
|
var d = document.documentElement, b = d.getAttribute('data-booth');'''
|
|
|
|
[[mutation]]
|
|
label = "D3 the ask chrome in a verbatim page ignores the choice"
|
|
file = "booth/static/embed.js"
|
|
test = "tests/test_flow_browser.py::test_a_forced_theme_reaches_the_ask_chrome_inside_a_verbatim_page"
|
|
old = '''
|
|
bkTheme();
|
|
reassociate();'''
|
|
new = '''
|
|
reassociate();'''
|
|
|
|
[[mutation]]
|
|
label = "D3 the ask chrome does not follow a choice made in another tab"
|
|
file = "booth/static/embed.js"
|
|
test = "tests/test_flow_browser.py::test_a_forced_theme_reaches_the_ask_chrome_inside_a_verbatim_page"
|
|
old = '''
|
|
if (e.key === "booth.theme" || e.key === null) bkTheme();'''
|
|
new = '''
|
|
if (false) bkTheme();'''
|
|
|
|
# ---- merge-2 folds: heid bug-hunt ("GORE-7") and code-review ("STAGGER-3")
|
|
# Not expressible as one replacement, so pinned by their tests directly: the
|
|
# row's DOM tab order (read in order by the test), and a declaration dropped
|
|
# from BOTH light copies (the expected set is now written from SVOS, not
|
|
# derived from the copies).
|
|
|
|
[[mutation]]
|
|
label = "a date the calendar cannot hold raises through the Desk"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_a_date_no_calendar_can_hold_renders_nothing_and_never_500s"
|
|
old = '''
|
|
_BAD_DATE = (OverflowError, OSError, ValueError)'''
|
|
new = '''
|
|
_BAD_DATE = ()'''
|
|
|
|
[[mutation]]
|
|
label = "updated is dropped for content older than its booth (one-sided gap)"
|
|
file = "booth/templates/_dates.html"
|
|
test = "tests/test_flow.py::test_updated_shows_whenever_it_differs_from_created_and_a_future_one_says_its_date"
|
|
old = '''(landed_at - created_at)|abs >= 60'''
|
|
new = '''(landed_at - created_at) >= 60'''
|
|
|
|
[[mutation]]
|
|
label = "a content clock ahead of now reads as an age"
|
|
file = "booth/templates/_dates.html"
|
|
test = "tests/test_flow.py::test_updated_shows_whenever_it_differs_from_created_and_a_future_one_says_its_date"
|
|
old = '''{%- if age < -60 %}'''
|
|
new = '''{%- if false %}'''
|
|
|
|
[[mutation]]
|
|
label = "hours run on to 47h (1d ago never appears)"
|
|
file = "booth/app.py"
|
|
test = "tests/test_flow.py::test_an_age_is_said_in_its_largest_whole_unit"
|
|
old = '''
|
|
if s < 86400:
|
|
return f"{s // 3600}h ago"'''
|
|
new = '''
|
|
if s < 2 * 86400:
|
|
return f"{s // 3600}h ago"'''
|
|
|
|
[[mutation]]
|
|
label = "a theme chosen in one tab does not reach the Booth's other open tabs"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_a_theme_chosen_in_one_tab_moves_the_others"
|
|
old = '''
|
|
if (e.key !== 'booth.theme' && e.key !== null) return;
|
|
var t = null;'''
|
|
new = '''
|
|
return;
|
|
var t = null;'''
|
|
|
|
[[mutation]]
|
|
label = "the theme mark reaches the author's own .bk-ask"
|
|
file = "booth/static/embed.js"
|
|
test = "tests/test_flow_browser.py::test_the_theme_marks_only_the_ask_fragments_we_mounted"
|
|
old = '''
|
|
ours.forEach(function (root) {'''
|
|
new = '''
|
|
[document.body].forEach(function (root) {'''
|
|
|
|
[[mutation]]
|
|
label = "keyboard focus no longer reveals the row's controls"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_pill_shows_at_rest_and_focus_reveals_the_controls"
|
|
old = '''
|
|
.desk-row:hover .desk-acts,.desk-row:focus-within .desk-acts{opacity:1;pointer-events:auto}'''
|
|
new = '''
|
|
.desk-row:hover .desk-acts{opacity:1;pointer-events:auto}'''
|
|
|
|
[[mutation]]
|
|
label = "the lifetime pill hides at rest"
|
|
file = "booth/templates/base.html"
|
|
test = "tests/test_flow_browser.py::test_the_pill_shows_at_rest_and_focus_reveals_the_controls"
|
|
old = '''
|
|
.life-count::before{content:"◷"}'''
|
|
new = '''
|
|
.life-count::before{content:"◷"}
|
|
.life{opacity:0}'''
|
|
|
|
[[mutation]]
|
|
label = "high contrast never applies (dark-hc block gone)"
|
|
file = "booth/templates/_svos_tokens.css"
|
|
test = "tests/test_flow_browser.py::test_a_forced_theme_follows_high_contrast"
|
|
old = '''
|
|
/* dark high contrast: whenever dark is in effect (light, below, outranks it) */
|
|
@media (prefers-contrast: more) {'''
|
|
new = '''
|
|
/* dark high contrast: whenever dark is in effect (light, below, outranks it) */
|
|
@media (prefers-contrast: less) {'''
|
|
|
|
[[mutation]]
|
|
label = "Reveal all does not reach a blurred doc's own page"
|
|
file = "booth/templates/doc.html"
|
|
test = "tests/test_flow_browser.py::test_reveal_all_lifts_the_doc_page_it_reaches"
|
|
old = '''
|
|
.reveal-all .docbody.is-blurred .markdown-body,.reveal-all .docbody.is-blurred .textview{filter:none}'''
|
|
new = '''
|
|
.reveal-all-OFF .docbody.is-blurred .markdown-body,.reveal-all .docbody.is-blurred .textview{filter:none}'''
|