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:
vh
2026-09-24 00:20:15 -07:00
parent 7151a45ec2
commit 7c879e6038
6 changed files with 546 additions and 46 deletions
+61 -13
View File
@@ -32,9 +32,15 @@ assumptions:
the image shows at fit size"): the size changes, the promise (one picture at a
readable size, no judgment behind a script) holds.
- The drop shadow follows the picture's own pixels (`drop-shadow`), not the
letterboxed box.
letterboxed box, on every path: blurred (`blur() drop-shadow()`, because
`filter` is one property and a blur rule would replace the shadow),
revealed, and under Reveal all.
- **1:1 shows natural pixels**, centred when smaller than the stage and
scrollable when larger. The upscale softness in Fit is exactly why 1:1 exists
scrollable when larger, with EVERY pixel reachable. The stage aligns to its
START edge in 1:1, and the picture's auto margins centre it when it is
smaller. A centred flex item larger than its scroll box overflows both
sides, and the start side can never be scrolled to (heid code-review,
measured: a 3000px picture hid its leftmost 980px). The upscale softness in Fit is exactly why 1:1 exists
and is always one click away.
## S2 — the toggle is always there for a picture
@@ -54,7 +60,10 @@ assumptions:
stage's CSS keys off it (`.stage-one .vstage`); the server renders the
stage as plain `vstage` (Fit is the default, no class needed).
- A stored value other than `one` reads as Fit. A read that throws reads as
Fit; a write that throws still applies the click. Never raises.
Fit; a write that throws still applies the click, the pressed state
included. Never raises.
- A mode chosen in another tab moves every open review (the `storage` event),
as the theme does.
- The buttons' pressed state is drawn from the `<html>` class, the one
record of the mode on the page (storage is its memory, off the page).
@@ -62,12 +71,14 @@ assumptions:
- **Each arrow sits wholly outside the picture's DRAWN edge, its near edge 8px
from the picture**, vertically centred on the stage. The measure is always
the DRAWN picture, never the file's natural size: in Fit, the `object-fit:
contain` content box (from the natural size and the box); in 1:1, the part of
the picture visible in the stage (its box intersected with the stage's). A
the DRAWN picture, never the file's natural size: the `object-fit: contain`
content box (from the natural size and the box). In 1:1 (scale 1) that IS the
picture's own box, and where it runs past the stage the clamp keeps the arrows
inside. A
video's own box counts the same way; audio keeps the stage-edge arrows.
- **Clamped to the stage**: an arrow never goes past the stage's edge (8px
inset), never over the rail, never off the stage. When there is no room for
- **Clamped to the stage's CLIENT box**: an arrow never goes past the stage's
edge (8px inset), never over the rail, never off the stage, and never under
a classic scrollbar (the client box excludes it). When there is no room for
it outside the drawn picture — the drawn picture spans, or nearly spans, the
stage's width — it sits at the stage edge, over the picture. That is the
ONLY case the arrows sit at the stage edge (the operator: "closer to the edge
@@ -75,10 +86,17 @@ assumptions:
width").
- Re-placed on picture load (or at once when it is already loaded), stage
resize (a `ResizeObserver`, which covers window resizes and the rail
stacking), mode switch and 1:1 scroll.
stacking) and mode switch. (Scrolling a 1:1 picture cannot move its drawn
horizontal edges past the clamp, so it needs no re-placement.)
- **Before the picture's size is known** (JS on, picture still loading), and if
it fails to load, the arrows stay at today's stage-edge CSS; they move once
the drawn box is known. Without JS they stay there.
it fails to load, the arrows stay at their CSS spot; they move once the drawn
box is known. Without JS they stay there. If the size ever becomes unknown
again, a placed arrow returns to that spot rather than keeping a stale one.
- That CSS spot is the STAGE's vertical centre. When stacked (≤900px) that
is 30vh down: the stage is the body's first 60vh, and centring on the
whole body put the arrows over a tall rail (heid code-review). The rule
lives in view.html after `.vnav`, because a base.html rule loses to the
page's own later one.
- The anchors, their classes and their hrefs are unchanged (test_booth,
test_navigation, test_flow pin them).
@@ -89,12 +107,24 @@ assumptions:
`grabbing` while dragging, pointer capture.
- **The picture follows the pointer** (the grab convention): a drag of +dx,
+dy changes the stage's scroll by −dx, −dy.
- A press that moves less than 4px is not a drag: nothing pans.
- A press that moves less than 4px IN TOTAL (Euclidean) is not a drag:
nothing pans. A (3, 3) diagonal is 4.24px, so it pans.
- A press on the stage's own scrollbar is the scrollbar's, never a pan.
- The drag is CAPTURED once it begins, so it keeps panning past the stage's
edge. A press released outside the stage before the drag began never
becomes a pan: a move with no button held ends it.
- **Pan listens on the stage only**, and no control is in the stage's
scrolled content: the arrows never were, and **the stage's own reveal
button moves OUT of the stage to sit over it** (found building this: in
1:1 a panned picture carried the button out of view with it). So a control
is never a pan source and keeps its own click, at any scroll.
- That reveal is JS-only, so it renders `hidden` until the script binds it,
the toggle's pattern (heid bug-hunt: shown with scripts off, it did
nothing). A revealed picture keeps Fit's drop shadow.
- Accepted: no `touch-action`. On touch the stage scrolls natively, and the
pan yields on `pointercancel`; `none` would take native touch scrolling
away. The `dragstart` `preventDefault` sits beside `draggable=false` as a
second layer.
- **The picture cannot be dragged away**: `draggable="false"` on `#vimg` and a
`dragstart` `preventDefault` on the stage.
- Fit, or a 1:1 picture that fits: no pan, no grab cursor.
@@ -130,6 +160,22 @@ assumptions:
edge 8px (±2) from it; a landscape drawn as wide as the stage — arrows inside
the stage at its edges, over the picture, never over the rail; after a window
resize they follow the new drawn box.
- `in_one_to_one_every_pixel_of_a_large_picture_is_reachable`: at scroll
(0, 0) the picture's top-left is the stage's; at the far scroll its
bottom-right is; a small picture is centred.
- `a_pan_holds_past_the_stage_edge_and_never_starts_on_a_hover`: a drag
carried past the stage's edge keeps panning; a press released outside,
then a buttonless hover, pans nothing.
- `before_placement_the_arrows_never_sit_over_the_rail_on_a_narrow_screen`:
JS off at 390px with a rail taller than the stage, the arrows sit within
the stage; a picture that fails to load leaves them unplaced, with no error.
- `the_stage_reveal_never_shows_without_js_and_keeps_the_fit_shadow`.
- `a_stage_mode_chosen_in_one_tab_moves_the_others`.
- `a_classic_scrollbar_is_neither_under_an_arrow_nor_a_pan`: with a forced 15px
classic bar (asserted real first: headless Chromium hides scrollbars), the
next arrow sits inside the client box, and a press dispatched on the bar
pans nothing.
- `a_picture_that_overflows_one_axis_pans_along_it`.
- `in_one_to_one_a_drag_pans_and_the_picture_cannot_be_dragged_away`: a picture
overflowing both axes in 1:1 — a drag of (+80, +60) changes the scroll by
(−80, −60); a 2px press pans nothing; a press on the stage's reveal button
@@ -141,7 +187,9 @@ assumptions:
| test | today | after | why |
|---|---|---|---|
| test_flow_browser `test_the_next_arrow_clears_the_rail_only_beside_it` | the next arrow's computed `right` is 360px wide / 0px narrow | replaced by `the_arrows_sit_just_outside_the_picture_and_clamp_to_the_stage` (never over the rail; at the picture's edge) | the arrows now track the picture, not the stage edge (operator) |
| test_flow `test_only_a_picture_gets_the_fit_toggle_and_blur_stays_honest` | `id="vtoggle"` present for a picture (hidden by inline style); the stage is `class="vstage fit is-blurred"` | the same presence, now with the `hidden` attribute; the stage is `class="vstage is-blurred"` | the toggle is `hidden` until the script shows it; the mode moved to `<html>` (never flash) |
| test_flow_browser `test_reveal_all_reveals_every_blurred_surface_and_survives_the_next_page` (r2b) | the revealed review stage's filter is `none` | it carries no blur (`blur(` absent); Fit's `drop-shadow` stays | the stage keeps its shadow on every path (S1) |
| tests/mutations/r2_flow.toml, the row on the next arrow's 360px offset | proved `.vnext{right:360px}` wide / 0 narrow | retired, with successors in r2c.toml | its test was replaced (row one above) |
| test_flow `test_only_a_picture_gets_the_fit_toggle_and_blur_stays_honest` | `id="vtoggle"` present for a picture (hidden by inline style); the stage is `class="vstage fit is-blurred"` | the same presence, now with the `hidden` attribute; the stage is `class="vstage is-img is-blurred"` | the toggle is `hidden` until the script shows it; the mode moved to `<html>` (never flash); `is-img` scopes the picture-only 1:1 rules |
## Out of scope