fix(review): the heid code-review and bug-hunt panels on r2c, folded (both 4/4 with retries)
- 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.
This commit is contained in:
@@ -11,6 +11,10 @@
|
||||
# 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
|
||||
@@ -139,16 +143,6 @@ test = "tests/test_flow_browser.py::test_the_standalone_marks_page_updates_in_pl
|
||||
old = '''<div class="marks-panel" data-region="marks-panel">'''
|
||||
new = '''<div class="marks-panel">'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C6 the next arrow's rail offset applies at phone width"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_next_arrow_clears_the_rail_only_beside_it"
|
||||
old = ''' .vprev{left:0}.vnext{right:0}
|
||||
@media (min-width:901px){.vnext{right:360px}}
|
||||
'''
|
||||
new = ''' .vprev{left:0}.vnext{right:360px}
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "resolver: an entry that cannot be stat'd raises out of booth_items"
|
||||
file = "booth/items.py"
|
||||
|
||||
+198
-3
@@ -75,10 +75,10 @@ file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_arrows_sit_just_outside_the_picture_and_clamp_to_the_stage"
|
||||
old = '''
|
||||
var p = drawn();
|
||||
if (!p) return;'''
|
||||
if (!p) {'''
|
||||
new = '''
|
||||
var p = null;
|
||||
if (!p) return;'''
|
||||
if (!p) {'''
|
||||
|
||||
[[mutation]]
|
||||
label = "S3 the arrows track the file's natural width, not the drawn picture"
|
||||
@@ -124,7 +124,7 @@ label = "S4 no drag threshold (a jittery click pans)"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_in_one_to_one_a_drag_pans_and_the_picture_cannot_be_dragged_away"
|
||||
old = '''
|
||||
if (Math.abs(dx) < 4 && Math.abs(dy) < 4) return;'''
|
||||
if (dx * dx + dy * dy < 16) return; /* under 4px in all: a click */'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
@@ -152,3 +152,198 @@ old = '''
|
||||
.review-body > .reveal{position:absolute;top:14px;left:14px;z-index:5;'''
|
||||
new = '''
|
||||
.review-body > .reveal{position:absolute;top:14px;left:14px;z-index:-1;'''
|
||||
|
||||
# ---- the heid code-review fold ("VÍGUNDR")
|
||||
|
||||
[[mutation]]
|
||||
label = "1:1 centres a large picture (its start side can never be scrolled to)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_in_one_to_one_every_pixel_of_a_large_picture_is_reachable"
|
||||
old = '''
|
||||
.stage-one .vstage.is-img{overflow:auto;padding:0;justify-content:flex-start;align-items:flex-start}'''
|
||||
new = '''
|
||||
.stage-one .vstage.is-img{overflow:auto;padding:0}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a buttonless hover continues a press released outside the stage"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_a_pan_holds_past_the_stage_edge_and_never_starts_on_a_hover"
|
||||
old = '''
|
||||
if (!(e.buttons & 1)) { endDrag(); return; }'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "no pointer capture (a pan dies at the stage's edge)"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_a_pan_holds_past_the_stage_edge_and_never_starts_on_a_hover"
|
||||
old = '''
|
||||
try { stage.setPointerCapture(drag.id); } catch (x) {}'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "stacked, the arrows' fallback centres on stage AND rail"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_before_placement_the_arrows_never_sit_over_the_rail_on_a_narrow_screen"
|
||||
old = '''
|
||||
@media (max-width:900px){.vnav{top:30vh}}'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "Fit shifts the picture off-centre (object-position), cropping it"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_fit_fills_the_stage_up_or_down"
|
||||
old = '''
|
||||
.vstage.is-img img{width:100%;height:100%;object-fit:contain;'''
|
||||
new = '''
|
||||
.vstage.is-img img{width:100%;height:100%;object-fit:contain;object-position:-100px 50%;'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the arrows are not centred on the stage"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_arrows_sit_just_outside_the_picture_and_clamp_to_the_stage"
|
||||
old = '''
|
||||
a.style.top = (s.top - o.top + s.height / 2) + 'px';'''
|
||||
new = '''
|
||||
a.style.top = (s.top - o.top + s.height / 4) + 'px';'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a booth.fit read that throws raises out of the head script"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_mode_persists_across_prev_next_and_never_flashes"
|
||||
old = '''
|
||||
try {
|
||||
if (localStorage.getItem('booth.fit') === 'one') d.classList.add('stage-one');
|
||||
} catch (e) {}'''
|
||||
new = '''
|
||||
if (localStorage.getItem('booth.fit') === 'one') d.classList.add('stage-one');'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the drag threshold is per axis, not total (a 3,3 diagonal pans nothing)"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_in_one_to_one_a_drag_pans_and_the_picture_cannot_be_dragged_away"
|
||||
old = '''
|
||||
if (dx * dx + dy * dy < 16) return; /* under 4px in all: a click */'''
|
||||
new = '''
|
||||
if (Math.abs(dx) < 4 && Math.abs(dy) < 4) return;'''
|
||||
|
||||
# ---- the heid bug-hunt fold ("ÞREKJUR"). Accepted, not rowed: no `touch-action`
|
||||
# (on touch the stage scrolls natively and the pan yields on pointercancel);
|
||||
# dragstart preventDefault beside draggable=false (defence in depth, one layer
|
||||
# alone holds).
|
||||
|
||||
[[mutation]]
|
||||
label = "the stage reveal shows with scripts off (and does nothing)"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_stage_reveal_never_shows_without_js_and_keeps_the_fit_shadow"
|
||||
old = '''aria-label="reveal {{ file }}" hidden>'''
|
||||
new = '''aria-label="reveal {{ file }}">'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a revealed review picture loses Fit's shadow"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_stage_reveal_never_shows_without_js_and_keeps_the_fit_shadow"
|
||||
old = '''
|
||||
.review .vstage.is-img.is-blurred.revealed img,.reveal-all .review .vstage.is-img.is-blurred img{'''
|
||||
new = '''
|
||||
.review .vstage.is-img.is-blurred.revealed-OFF img,.reveal-all .review .vstage.is-img.is-blurred img{'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a stage mode chosen in another tab does not reach this one"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_a_stage_mode_chosen_in_one_tab_moves_the_others"
|
||||
old = '''
|
||||
if (e.key !== 'booth.fit' && e.key !== null) return;'''
|
||||
new = '''
|
||||
return;'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the drag threshold drops to 3px"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_in_one_to_one_a_drag_pans_and_the_picture_cannot_be_dragged_away"
|
||||
old = '''
|
||||
if (dx * dx + dy * dy < 16) return; /* under 4px in all: a click */'''
|
||||
new = '''
|
||||
if (dx * dx + dy * dy < 9) return; /* under 4px in all: a click */'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a storage write that throws cuts the click short (buttons never update)"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_mode_persists_across_prev_next_and_never_flashes"
|
||||
old = '''
|
||||
try {
|
||||
if (one) localStorage.setItem('booth.fit', 'one'); else localStorage.removeItem('booth.fit');
|
||||
} catch (e) {}'''
|
||||
new = '''
|
||||
if (one) localStorage.setItem('booth.fit', 'one'); else localStorage.removeItem('booth.fit');'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the Fit button's pressed state is never drawn"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_mode_persists_across_prev_next_and_never_flashes"
|
||||
old = '''
|
||||
bFit.setAttribute('aria-pressed', one ? 'false' : 'true');'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
# ---- groa's retry supplement (code review)
|
||||
|
||||
[[mutation]]
|
||||
label = "S1 Fit loses its drop shadow"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_fit_fills_the_stage_up_or_down"
|
||||
old = '''
|
||||
.vstage.is-img img{width:100%;height:100%;object-fit:contain;
|
||||
filter:drop-shadow(0 10px 24px rgb(0 0 0 / .32))}'''
|
||||
new = '''
|
||||
.vstage.is-img img{width:100%;height:100%;object-fit:contain}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "S2 the toggle hides, on load, for a picture larger than the stage"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_toggle_shows_for_every_picture_and_never_without_js"
|
||||
old = '''
|
||||
function settle() { place(); pannable(); }'''
|
||||
new = '''
|
||||
function settle() { place(); pannable(); if (img) document.getElementById('vtoggle').hidden = img.naturalWidth > stage.clientWidth; }'''
|
||||
|
||||
[[mutation]]
|
||||
label = "S2 choosing Fit stores a word instead of forgetting 1:1"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_the_mode_persists_across_prev_next_and_never_flashes"
|
||||
old = '''
|
||||
if (one) localStorage.setItem('booth.fit', 'one'); else localStorage.removeItem('booth.fit');'''
|
||||
new = '''
|
||||
if (one) localStorage.setItem('booth.fit', 'one'); else localStorage.setItem('booth.fit', 'fit');'''
|
||||
|
||||
[[mutation]]
|
||||
label = "S4 pan only when BOTH axes overflow"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_a_picture_that_overflows_one_axis_pans_along_it"
|
||||
old = '''
|
||||
(stage.scrollWidth > stage.clientWidth || stage.scrollHeight > stage.clientHeight);'''
|
||||
new = '''
|
||||
(stage.scrollWidth > stage.clientWidth && stage.scrollHeight > stage.clientHeight);'''
|
||||
|
||||
# ---- groa's retry supplement (bug hunt): classic scrollbars
|
||||
|
||||
[[mutation]]
|
||||
label = "a press on the stage's scrollbar starts a pan"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_a_classic_scrollbar_is_neither_under_an_arrow_nor_a_pan"
|
||||
old = '''
|
||||
if (e.clientX - r.left - stage.clientLeft >= stage.clientWidth ||
|
||||
e.clientY - r.top - stage.clientTop >= stage.clientHeight) return;'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the arrows clamp to the border box (the next one sits under a classic scrollbar)"
|
||||
file = "booth/templates/view.html"
|
||||
test = "tests/test_flow_browser.py::test_a_classic_scrollbar_is_neither_under_an_arrow_nor_a_pan"
|
||||
old = '''
|
||||
var cl = s.left + stage.clientLeft, cr = cl + stage.clientWidth;'''
|
||||
new = '''
|
||||
var cl = s.left, cr = s.right;'''
|
||||
|
||||
Reference in New Issue
Block a user