J408: D374 content-first swipes, modifier research #444

Open
opened 2026-11-09 06:29:09 +00:00 by kayg · 10 comments
Owner

J408, D374 (A17): one-finger swipes are content-first, canvas moves on purpose.

Branch swipe-content-first-d374, base origin/dev 80a09ad02.

Plan:

  1. Research which modifier+wheel combos browsers hijack, how trackpad / Magic Mouse swipes differ from a wheel in wheel events, and measure in Playwright (Chromium, WebKit, Firefox). Findings table is posted here as a comment. The owner picks the modifier.
  2. Red specs first (production build, 393x852 touch and 1440x900 wheel), then the change: one finger never moves the canvas, two fingers do, a trackpad/Magic Mouse horizontal swipe moves the canvas only when it starts over empty canvas or chrome, and the modifier+wheel path sits behind one constant.

Estimate: research 1 h, specs and code 2 h.

J408, D374 (A17): one-finger swipes are content-first, canvas moves on purpose. Branch `swipe-content-first-d374`, base origin/dev 80a09ad02. Plan: 1. Research which modifier+wheel combos browsers hijack, how trackpad / Magic Mouse swipes differ from a wheel in `wheel` events, and measure in Playwright (Chromium, WebKit, Firefox). Findings table is posted here as a comment. The owner picks the modifier. 2. Red specs first (production build, 393x852 touch and 1440x900 wheel), then the change: one finger never moves the canvas, two fingers do, a trackpad/Magic Mouse horizontal swipe moves the canvas only when it starts over empty canvas or chrome, and the modifier+wheel path sits behind one constant. Estimate: research 1 h, specs and code 2 h.
Author
Owner

Part 1: modifier + wheel research (J408, D374)

Measured = my Playwright probe (probe-wheel.mjs, raw result wheel-chromium.json, in ~/.cache/codex-scratch/j408/evidence/ on the control host). Researched = web sources, not measured by me. Headless Playwright feeds synthetic wheel events to the page, so it cannot show browser chrome actions (page zoom, history navigation). Those rows are research only.

Modifier + wheel macOS Windows Linux Reaches page as cancellable wheel with flag?
none, vertical scroll scroll scroll Yes (measured, Chromium)
Shift Chromium/Safari scroll sideways scrolls sideways; old Firefox defaults mapped it to history same as Windows Yes. Measured in Chromium: shiftKey set, cancelable, preventDefault stops the native sideways scroll. Firefox: see the Alt row (the pref mechanism is the same).
Alt / Option no default found in Chrome, Safari or Firefox Firefox default is history back/forward (mousewheel.with_alt.action); Chrome and Edge: none found same as Windows Yes in Chromium (measured: altKey set, cancelable, native scroll unchanged). Firefox: researched only, I could not run it, so I don't know whether preventDefault beats its history action.
Ctrl trackpad pinch arrives as ctrl+wheel; the OS "scroll with modifier to zoom" setting can take Ctrl first page zoom page zoom Chromium sends it to the page first and zooms only if not cancelled. Firefox on Linux/macOS is reported to skip zoom when any handler exists. Reserved for zoom (D275), so not a candidate.
Meta / Cmd Cmd+scroll: no browser default found; Safari has no wheel zoom Windows key, not delivered usefully Super key, window-manager territory Flag set in Chromium (measured). On Windows/Linux the key is the OS's, so it's not a candidate.
Ctrl+Shift, Ctrl+Alt, Alt+Shift Alt+Shift measured: still scrolls sideways natively because of Shift untested natively untested natively Flags set in Chromium (measured). Two-key chords are awkward to hold and Ctrl is zoom. Not candidates.

Trackpad / Magic Mouse swipe vs mouse wheel (researched)

  • A Mac trackpad or Magic Mouse sends pixel-mode events (deltaMode 0). A two-finger swipe has both deltaX and deltaY, often fractional, at the display rate (~13–15 ms apart), then a momentum tail with the same sign and decaying deltas. The web has no momentum flag, so a tail is told apart by deltas that only fall.
  • A classic wheel on Windows/Linux sends line steps (Firefox deltaMode 1) or fixed pixel steps (Chromium: 100 or 120 per notch, measured as 120 here), and no deltaX. Magic Mouse and trackpad are indistinguishable from each other in the browser, and deltas are only a guess for telling them from a wheel.
  • Collision: on macOS a horizontal two-finger swipe also starts Chrome's and Safari's back/forward swipe when a deltaX event is not claimed. overscroll-behavior-x: none|contain stops it in Chromium and Firefox (the app already sets overscroll-behavior: none on html, body, #app); Safari may ignore the CSS, so a non-passive wheel listener that calls preventDefault is the dependable fix. The canvas router now cancels every gesture it owns, content's swallowed end included.

Recommendation (the owner picks; the constant is CANVAS_WHEEL_MODIFIER in canvas-wheel-modifier.ts)

  • macOS: Option (Alt). Nothing found takes it. Ctrl and Cmd are zoom territory and Shift also works.
  • Windows and Linux: Shift. It already means "wheel goes sideways" everywhere, D159 and D275 already use it, and Alt+wheel is Firefox's history action by default.
  • If one key must serve all three: Shift (it works on all three and changes nothing for today's users). Alt/Option is a good second choice on macOS, but it is Firefox's history key on Windows and Linux.
  • The constant is set to Shift for now, only because that equals today's behaviour.

Not measured

  • Firefox and WebKit: the VM lacks their system libraries (libgtk-4, libwebkitgtk-6.0, libevent, ...). Installing them is a host change, so I left it for the Maintainer. Playwright browsers firefox-1572 and webkit-2388 are downloaded in ~/.cache/ms-playwright on dev-02 and will run once those libraries exist.
  • Real Safari, a real Magic Mouse and a real trackpad (needs the Mac and a person), page zoom, history navigation, text-size and the macOS accessibility zoom.

Sources: Chrome ctrl+wheel can be overridden, Firefox bug 814303 (macOS ctrl+wheel), Firefox bug 143038 (wheel + modifier), Mozilla Connect: Alt+scroll history, MDN WheelEvent, Trackpad vs Magic Mouse scroll, block the back/forward swipe.

## Part 1: modifier + wheel research (J408, D374) **Measured** = my Playwright probe (`probe-wheel.mjs`, raw result `wheel-chromium.json`, in `~/.cache/codex-scratch/j408/evidence/` on the control host). **Researched** = web sources, not measured by me. Headless Playwright feeds synthetic wheel events to the page, so it cannot show browser chrome actions (page zoom, history navigation). Those rows are research only. | Modifier + wheel | macOS | Windows | Linux | Reaches page as cancellable `wheel` with flag? | |---|---|---|---|---| | none, vertical | scroll | scroll | scroll | Yes (measured, Chromium) | | **Shift** | Chromium/Safari scroll sideways | scrolls sideways; old Firefox defaults mapped it to history | same as Windows | Yes. Measured in Chromium: `shiftKey` set, cancelable, `preventDefault` stops the native sideways scroll. Firefox: see the Alt row (the pref mechanism is the same). | | **Alt / Option** | no default found in Chrome, Safari or Firefox | Firefox default is history back/forward (`mousewheel.with_alt.action`); Chrome and Edge: none found | same as Windows | Yes in Chromium (measured: `altKey` set, cancelable, native scroll unchanged). Firefox: researched only, I could not run it, so I don't know whether `preventDefault` beats its history action. | | **Ctrl** | trackpad pinch arrives as ctrl+wheel; the OS "scroll with modifier to zoom" setting can take Ctrl first | page zoom | page zoom | Chromium sends it to the page first and zooms only if not cancelled. Firefox on Linux/macOS is reported to skip zoom when any handler exists. Reserved for zoom (D275), so not a candidate. | | **Meta / Cmd** | Cmd+scroll: no browser default found; Safari has no wheel zoom | Windows key, not delivered usefully | Super key, window-manager territory | Flag set in Chromium (measured). On Windows/Linux the key is the OS's, so it's not a candidate. | | Ctrl+Shift, Ctrl+Alt, Alt+Shift | Alt+Shift measured: still scrolls sideways natively because of Shift | untested natively | untested natively | Flags set in Chromium (measured). Two-key chords are awkward to hold and Ctrl is zoom. Not candidates. | ### Trackpad / Magic Mouse swipe vs mouse wheel (researched) - A Mac trackpad or Magic Mouse sends pixel-mode events (`deltaMode` 0). A two-finger swipe has both `deltaX` and `deltaY`, often fractional, at the display rate (~13–15 ms apart), then a momentum tail with the same sign and decaying deltas. The web has no momentum flag, so a tail is told apart by deltas that only fall. - A classic wheel on Windows/Linux sends line steps (Firefox `deltaMode` 1) or fixed pixel steps (Chromium: 100 or 120 per notch, measured as 120 here), and no `deltaX`. Magic Mouse and trackpad are indistinguishable from each other in the browser, and deltas are only a guess for telling them from a wheel. - Collision: on macOS a horizontal two-finger swipe also starts Chrome's and Safari's back/forward swipe when a `deltaX` event is not claimed. `overscroll-behavior-x: none|contain` stops it in Chromium and Firefox (the app already sets `overscroll-behavior: none` on `html, body, #app`); Safari may ignore the CSS, so a non-passive `wheel` listener that calls `preventDefault` is the dependable fix. The canvas router now cancels every gesture it owns, content's swallowed end included. ### Recommendation (the owner picks; the constant is `CANVAS_WHEEL_MODIFIER` in `canvas-wheel-modifier.ts`) - **macOS: Option (Alt).** Nothing found takes it. Ctrl and Cmd are zoom territory and Shift also works. - **Windows and Linux: Shift.** It already means "wheel goes sideways" everywhere, D159 and D275 already use it, and Alt+wheel is Firefox's history action by default. - If one key must serve all three: **Shift** (it works on all three and changes nothing for today's users). Alt/Option is a good second choice on macOS, but it is Firefox's history key on Windows and Linux. - The constant is set to Shift for now, only because that equals today's behaviour. ### Not measured - Firefox and WebKit: the VM lacks their system libraries (`libgtk-4`, `libwebkitgtk-6.0`, `libevent`, ...). Installing them is a host change, so I left it for the Maintainer. Playwright browsers `firefox-1572` and `webkit-2388` are downloaded in `~/.cache/ms-playwright` on dev-02 and will run once those libraries exist. - Real Safari, a real Magic Mouse and a real trackpad (needs the Mac and a person), page zoom, history navigation, text-size and the macOS accessibility zoom. Sources: [Chrome ctrl+wheel can be overridden](https://groups.google.com/a/chromium.org/g/blink-dev/c/BW4hshtMsmo/m/crQeQLoH7RYJ), [Firefox bug 814303 (macOS ctrl+wheel)](https://bugzilla.mozilla.org/show_bug.cgi?id=814303), [Firefox bug 143038 (wheel + modifier)](https://bugzilla.mozilla.org/show_bug.cgi?id=143038), [Mozilla Connect: Alt+scroll history](https://connect.mozilla.org/t5/discussions/should-scrolling-with-the-alt-key-held-down-perform-history/m-p/132600), [MDN WheelEvent](https://developer.mozilla.org/docs/Web/API/WheelEvent), [Trackpad vs Magic Mouse scroll](https://blog.rectorsquid.com/detecting-trackpad-scroll-vs-magic-mouse-scroll/), [block the back/forward swipe](https://github.com/CieriS/portfolio/issues/36).
Author
Owner

Progress: red specs pushed (7260f0620, branch swipe-content-first-d374). Router change, TASTE rows and the updated wheel-scroll spec are written locally, not yet run. Waiting on a dev-02 build slot (queue about 8 deep); next is the red run, then the touch finding, then green.

Progress: red specs pushed (7260f0620, branch swipe-content-first-d374). Router change, TASTE rows and the updated wheel-scroll spec are written locally, not yet run. Waiting on a dev-02 build slot (queue about 8 deep); next is the red run, then the touch finding, then green.
Author
Owner

Progress (head 271307c2b, branch swipe-content-first-d374 pushed). Research table is in the comment above.
Red on the old router (log ~/j408-red3.log on dev-02, 1440x900): the sideways-swipe-over-docs-page spec and the modifier+wheel spec fail; green on the new router, with the updated wheel-scroll spec (7 passed, ~/j408-green3.log). One-finger touch: the sideways half already passed on the old code (no touch change needed); the 'past the end' half was a spec flaw (page is ~17,000 px), fixed in the last commit. Next: one slot run with the touch spec, the neighbouring canvas/clusters/settings specs and verify quick.

Progress (head 271307c2b, branch swipe-content-first-d374 pushed). Research table is in the comment above. Red on the old router (log ~/j408-red3.log on dev-02, 1440x900): the sideways-swipe-over-docs-page spec and the modifier+wheel spec fail; green on the new router, with the updated wheel-scroll spec (7 passed, ~/j408-green3.log). One-finger touch: the sideways half already passed on the old code (no touch change needed); the 'past the end' half was a spec flaw (page is ~17,000 px), fixed in the last commit. Next: one slot run with the touch spec, the neighbouring canvas/clusters/settings specs and verify quick.
Author
Owner

Finish (J408). Branch swipe-content-first-d374, head 3f71c5936 (pushed, confirmed with git ls-remote), base origin/dev 80a09ad02.

Commits: red specs + modifier constant; docs window id fix; router change; wheel-scroll spec update; TASTE rows; spec fixes; installGestures doc comment; sleep/doc comments.

Results (dev-02, production build, logs in ~ on dev-02):

  • Red on the old router (j408-red3.log): sideways swipe over the docs page moved the canvas, and modifier+wheel did nothing. Both fail. Green on the new router (j408-green3.log): swipe + wheel-scroll specs 7 passed. Final HEAD run (j408-head.spec.log): swipe-content-first 4/4 and wheel-scroll pass (6 passed in that run).
  • One-finger touch needed no code change: the sideways half passed on the old code too. The spec pins it, including a drag past the page end.
  • Wider regression, 125 specs in j408-wide.spec.log: 120 passed, 5 failed. The same 5 fail on unmodified origin/dev (j408-base.spec.log): clusters-swipes (2), clusters-swipes-both (2), clusters.spec.ts:512. Not caused by this change.
  • scripts/verify quick: exit 0 (j408-quick2.log): scope guard, migration names, Nix vendoring, fmt, doc coverage, shape lints, doc comments on diff, fixed test waits, ui literals, tracer typecheck all PASS. verify fast not run (the brief asked for quick).

Open for the owner:

  1. The modifier. The constant is CANVAS_WHEEL_MODIFIER in apps/tracer/src/canvas/camera/canvas-wheel-modifier.ts, set to Shift for now (equals today's behaviour). Recommendation is in the research comment.
  2. A vertical wheel at the end of a page still moves to the neighbouring window (D275's column step). D374's quote could cover that too; I left it because the brief listed only touch and sideways swipes.
  3. On a free canvas, a one-finger drag on bare canvas (no content under it) still pans. The brief says one finger never moves the canvas; I read "bare canvas" as having no content to scroll.
  4. wheel-scroll.spec.ts was already broken on origin/dev (it used window id d-keys, now d-reference/keys). I fixed that in passing.
  5. Firefox and WebKit could not be measured: dev-02 lacks their system libraries (host change, for the Maintainer).
Finish (J408). Branch `swipe-content-first-d374`, head 3f71c5936 (pushed, confirmed with `git ls-remote`), base origin/dev 80a09ad02. Commits: red specs + modifier constant; docs window id fix; router change; wheel-scroll spec update; TASTE rows; spec fixes; installGestures doc comment; sleep/doc comments. Results (dev-02, production build, logs in `~` on dev-02): - Red on the old router (`j408-red3.log`): sideways swipe over the docs page moved the canvas, and modifier+wheel did nothing. Both fail. Green on the new router (`j408-green3.log`): swipe + wheel-scroll specs 7 passed. Final HEAD run (`j408-head.spec.log`): swipe-content-first 4/4 and wheel-scroll pass (6 passed in that run). - One-finger touch needed no code change: the sideways half passed on the old code too. The spec pins it, including a drag past the page end. - Wider regression, 125 specs in `j408-wide.spec.log`: 120 passed, 5 failed. The same 5 fail on unmodified origin/dev (`j408-base.spec.log`): clusters-swipes (2), clusters-swipes-both (2), clusters.spec.ts:512. Not caused by this change. - `scripts/verify quick`: exit 0 (`j408-quick2.log`): scope guard, migration names, Nix vendoring, fmt, doc coverage, shape lints, doc comments on diff, fixed test waits, ui literals, tracer typecheck all PASS. `verify fast` not run (the brief asked for quick). Open for the owner: 1. The modifier. The constant is `CANVAS_WHEEL_MODIFIER` in `apps/tracer/src/canvas/camera/canvas-wheel-modifier.ts`, set to Shift for now (equals today's behaviour). Recommendation is in the research comment. 2. A vertical wheel at the end of a page still moves to the neighbouring window (D275's column step). D374's quote could cover that too; I left it because the brief listed only touch and sideways swipes. 3. On a free canvas, a one-finger drag on bare canvas (no content under it) still pans. The brief says one finger never moves the canvas; I read "bare canvas" as having no content to scroll. 4. `wheel-scroll.spec.ts` was already broken on origin/dev (it used window id `d-keys`, now `d-reference/keys`). I fixed that in passing. 5. Firefox and WebKit could not be measured: dev-02 lacks their system libraries (host change, for the Maintainer).
Author
Owner

Review of swipe-content-first-d374 at 3f71c5936 (base 80a09ad02)

Verdict: REQUEST CHANGES. The touch and sideways-swipe work is sound and I could not break it. Two behaviours still contradict the owner's quote or the content-first rule, and the research table has a wrong claim behind its macOS recommendation. No P1.

Method: production build on dev-02 through build-slot, worktrees j444-head / j444-base (since removed). The author's specs and my own probe spec run on head (runA.log: 10 passed). I did not run verify fast or verify quick. Evidence is in ~/.cache/codex-scratch/j444-review/evidence/ on the control host: runA.log, runB.log, review-444.spec.ts and review-444/*.json|png. The images below are in that folder.

Your six checks

  1. 393×852 touch: PASS.
    • A one-finger drag never moved the canvas. I tried sideways, up and down, starting on the window pill on the snapped canvas, and also the author's drags past the page end. Camera and column state were unchanged and the stop index stayed at 11 (phone-touch.json, snapped-* keys).
    • The author's "two fingers move the canvas" spec passes (runA.log).
    • Not covered by the author's spec, and also fine: a one-finger drag on a window with nothing to scroll. Columns are overflow-x: clip and touch-action: pan-y.
  2. 1440×900 trackpad: PASS.
    • Over a wide scrollable block, a trackpad-shaped sideways swipe scrolled the block to scrollLeft 710 and the canvas did not move.
    • Over the window pill the same swipe moves the canvas (author spec, passes).
    • My estimate for this fixture: about 72% of the viewport is canvas or chrome that a swipe reaches, and 28% is page content that keeps it.
  3. Modifier constant: PASS on the constant, but the Shift default has a cost (P2-1). The modifier lives behind the single CANVAS_WHEEL_MODIFIER in canvas-wheel-modifier.ts, and Shift is the default. Does Shift+wheel break horizontal scrolling inside content? Yes. Shift+wheel over a overflow-x:auto block with 2400 px of content left scrollLeft at 0, while a plain sideways swipe over the same block reached 710 (desktop-probes.json, shiftWheel against trackpadSideways; screenshot desktop-shift-wheel-over-wide-block.png). The canvas took the gesture and did nothing, because this page is the last window. It is not a regression: on base a Shift wheel was always the canvas's. The same applies to a wide pre or .doc .table and to the focused terminal's horizontal scroll.
  4. Open points 2 and 3: see P2-2 and P3-1 below.
  5. The 5 failures: they fail on plain dev too. runB.log ran the clusters-swipes (2), clusters-swipes-both (2) and clusters.spec.ts:512 specs on base 80a09ad02 and on head. Both runs give 5 failed and 2 passed, with identical errors: 3 vs 6 windows, 5 vs ≥8, and index 0 vs 1. The branch neither causes nor fixes them. I tested the merge base, not the newest origin/dev tip.
  6. Research table: see P2-3. The Chromium claim holds; one Firefox claim does not.

Findings

P2-1: Shift as the canvas modifier takes horizontal scroll away from content. routeWheel makes the canvas the owner whenever canvasModifierHeld(e), before it asks whether the content could scroll sideways (gestures.ts, routeWheel). canvasDeltas also remaps the vertical wheel to sideways for Shift. So a mouse user can no longer scroll a wide table or code block with Shift+wheel, and the content-first rule is broken for exactly that user. Options, for the owner:

  • keep Shift and let content that can still scroll sideways take the modifier gesture (scrollsSideways), with the canvas taking it at the content's end; or
  • pick another modifier. Alt/Option is free of this conflict, but see P2-3.

P2-2: a vertical wheel at a page's end still overflows to the neighbouring window. routeWheel: else owner = scrolls(target, e.deltaY) ? "vertical" : "canvas". A fresh gesture at the end therefore becomes a canvas step. The TASTE row still documents it ("a fresh swipe at the end moves on to the neighbouring window"). Against the owner's words, "I don't [want] scrolls [to] overflow to the canvas once there's nothing to scroll in the content. canvas scrolling has to be intentional", this is an overflow and not intentional, so I judge it a violation. A step from over bare canvas, chrome or the modifier is fine.

  • Caveat: D374 A17 is titled "sideways swipe on touch", so scope is ambiguous.
  • My probe did not reproduce the step: the docs page is the last window in its column, so the index stayed at 11 (verticalAtEnd, verticalAtTop). The finding rests on the code and the TASTE text.
  • Fix: swallow the gesture at the content's end, as the sideways case already does, or have the owner confirm D275's handoff stays.

P2-3: the research table is wrong about Firefox on macOS. The table says macOS Alt has "no default found in Chrome, Safari or Firefox" and recommends "macOS: Option". Firefox's modules/libpref/init/all.js sets pref("mousewheel.with_alt.action", 2), and 2 is "Go back or go forward, in your history". The line is not inside a platform #if, so Firefox on a Mac also treats Option+wheel as history. Whether preventDefault beats that action is untested; the author said so for Windows and Linux only. The table's other claims I checked hold:

  • The Chromium thread (blink-dev BW4hshtMsmo) says ctrl+wheel goes to the renderer first and invokes browser zoom only when unhandled. That matches the table.
  • mousewheel.with_meta.action is 1, "Scrolling contents", the same as default. That matches "Cmd: no browser default".
  • mousewheel.with_shift.action is 4, a vertical wheel treated as horizontal scroll. That matches "Shift scrolls sideways".

The Shift recommendation for Windows and Linux is fine on browser-conflict grounds, but the table never weighs P2-1. The per-OS Option recommendation needs correcting before the owner picks.

P3-1 (open point 3): a one-finger drag on bare canvas of a free canvas still pans. Measured: a drag of (-80, -60) on a bare point moved the world from translate3d(-3997px,-3576px) to (-4077px,-3636px) (phone-touch.json, freeBareOneFingerMovedCanvas: true; phone-free-bare-after-drag.png). Nothing overflows here, since no content is involved, and a drag that starts on bare canvas is arguably deliberate. But D374 says "two fingers on touch", and gestures-free.on-demand.ts pans on any non-interactive pointerdown. Ask the owner; do not decide it in the job.

P3-2: a sideways swipe over scrollable content that cannot scroll that way is swallowed with no feedback. For example, a swipe over the docs page dies silently. That is the design, but it is easy to misread as a stuck canvas. A focused terminal always takes sideways swipes (inScrollableContent), so over an active terminal only the pill, the gaps and a modifier reach the canvas.

P3-3: the swipe-routing specs that already fail on dev were not touched. They cover the code this branch changed and cannot guard it. Open a follow-up issue.

P3-4: spec gaps. swipe-content-first.spec.ts has no case for a sideways swipe or a Shift wheel over horizontally scrollable content, nor for one finger on a short window or on a free canvas. My review-444.spec.ts shows how.

What I did not do

  • Firefox and WebKit. dev-02 lacks their system libraries, as the author said.
  • Real Shift+wheel conversion. Playwright's synthetic Shift+wheel arrives as deltaY with shiftKey, and Chromium did not turn it into deltaX. canvasDeltas handles both.
  • Real Magic Mouse, trackpad and Safari.
  • The newest origin/dev tip for item 5.

Reviewed SHA: 3f71c5936. I did not merge, push or deploy.

## Review of `swipe-content-first-d374` at **3f71c5936** (base 80a09ad02) **Verdict: REQUEST CHANGES.** The touch and sideways-swipe work is sound and I could not break it. Two behaviours still contradict the owner's quote or the content-first rule, and the research table has a wrong claim behind its macOS recommendation. No P1. Method: production build on dev-02 through build-slot, worktrees `j444-head` / `j444-base` (since removed). The author's specs and my own probe spec run on head (`runA.log`: 10 passed). I did not run `verify fast` or `verify quick`. Evidence is in `~/.cache/codex-scratch/j444-review/evidence/` on the control host: `runA.log`, `runB.log`, `review-444.spec.ts` and `review-444/*.json|png`. The images below are in that folder. ### Your six checks 1. **393×852 touch: PASS.** - A one-finger drag never moved the canvas. I tried sideways, up and down, starting on the window pill on the snapped canvas, and also the author's drags past the page end. Camera and column state were unchanged and the stop index stayed at 11 (`phone-touch.json`, `snapped-*` keys). - The author's "two fingers move the canvas" spec passes (`runA.log`). - Not covered by the author's spec, and also fine: a one-finger drag on a window with nothing to scroll. Columns are `overflow-x: clip` and `touch-action: pan-y`. 2. **1440×900 trackpad: PASS.** - Over a wide scrollable block, a trackpad-shaped sideways swipe scrolled the block to scrollLeft 710 and the canvas did not move. - Over the window pill the same swipe moves the canvas (author spec, passes). - My estimate for this fixture: about 72% of the viewport is canvas or chrome that a swipe reaches, and 28% is page content that keeps it. 3. **Modifier constant: PASS on the constant, but the Shift default has a cost (P2-1).** The modifier lives behind the single `CANVAS_WHEEL_MODIFIER` in `canvas-wheel-modifier.ts`, and Shift is the default. Does Shift+wheel break horizontal scrolling inside content? **Yes.** Shift+wheel over a `overflow-x:auto` block with 2400 px of content left scrollLeft at 0, while a plain sideways swipe over the same block reached 710 (`desktop-probes.json`, `shiftWheel` against `trackpadSideways`; screenshot `desktop-shift-wheel-over-wide-block.png`). The canvas took the gesture and did nothing, because this page is the last window. It is not a regression: on base a Shift wheel was always the canvas's. The same applies to a wide `pre` or `.doc .table` and to the focused terminal's horizontal scroll. 4. **Open points 2 and 3:** see P2-2 and P3-1 below. 5. **The 5 failures: they fail on plain dev too.** `runB.log` ran the clusters-swipes (2), clusters-swipes-both (2) and clusters.spec.ts:512 specs on base 80a09ad02 and on head. Both runs give 5 failed and 2 passed, with identical errors: 3 vs 6 windows, 5 vs ≥8, and index 0 vs 1. The branch neither causes nor fixes them. I tested the merge base, not the newest origin/dev tip. 6. **Research table:** see P2-3. The Chromium claim holds; one Firefox claim does not. ### Findings **P2-1: Shift as the canvas modifier takes horizontal scroll away from content.** `routeWheel` makes the canvas the owner whenever `canvasModifierHeld(e)`, before it asks whether the content could scroll sideways (`gestures.ts`, `routeWheel`). `canvasDeltas` also remaps the vertical wheel to sideways for Shift. So a mouse user can no longer scroll a wide table or code block with Shift+wheel, and the content-first rule is broken for exactly that user. Options, for the owner: - keep Shift and let content that can still scroll sideways take the modifier gesture (`scrollsSideways`), with the canvas taking it at the content's end; or - pick another modifier. Alt/Option is free of this conflict, but see P2-3. **P2-2: a vertical wheel at a page's end still overflows to the neighbouring window.** `routeWheel`: `else owner = scrolls(target, e.deltaY) ? "vertical" : "canvas"`. A fresh gesture at the end therefore becomes a canvas step. The TASTE row still documents it ("a fresh swipe at the end moves on to the neighbouring window"). Against the owner's words, "I don't [want] scrolls [to] overflow to the canvas once there's nothing to scroll in the content. canvas scrolling has to be intentional", this is an overflow and not intentional, so I judge it a violation. A step from over bare canvas, chrome or the modifier is fine. - Caveat: D374 A17 is titled "sideways swipe on touch", so scope is ambiguous. - My probe did not reproduce the step: the docs page is the last window in its column, so the index stayed at 11 (`verticalAtEnd`, `verticalAtTop`). The finding rests on the code and the TASTE text. - Fix: swallow the gesture at the content's end, as the sideways case already does, or have the owner confirm D275's handoff stays. **P2-3: the research table is wrong about Firefox on macOS.** The table says macOS Alt has "no default found in Chrome, Safari or Firefox" and recommends "macOS: Option". Firefox's `modules/libpref/init/all.js` sets `pref("mousewheel.with_alt.action", 2)`, and 2 is "Go back or go forward, in your history". The line is not inside a platform `#if`, so Firefox on a Mac also treats Option+wheel as history. Whether `preventDefault` beats that action is untested; the author said so for Windows and Linux only. The table's other claims I checked hold: - The Chromium thread (blink-dev BW4hshtMsmo) says ctrl+wheel goes to the renderer first and invokes browser zoom only when unhandled. That matches the table. - `mousewheel.with_meta.action` is 1, "Scrolling contents", the same as default. That matches "Cmd: no browser default". - `mousewheel.with_shift.action` is 4, a vertical wheel treated as horizontal scroll. That matches "Shift scrolls sideways". The Shift recommendation for Windows and Linux is fine on browser-conflict grounds, but the table never weighs P2-1. The per-OS Option recommendation needs correcting before the owner picks. **P3-1 (open point 3): a one-finger drag on bare canvas of a free canvas still pans.** Measured: a drag of (-80, -60) on a bare point moved the world from translate3d(-3997px,-3576px) to (-4077px,-3636px) (`phone-touch.json`, `freeBareOneFingerMovedCanvas: true`; `phone-free-bare-after-drag.png`). Nothing overflows here, since no content is involved, and a drag that starts on bare canvas is arguably deliberate. But D374 says "two fingers on touch", and `gestures-free.on-demand.ts` pans on any non-interactive pointerdown. Ask the owner; do not decide it in the job. **P3-2: a sideways swipe over scrollable content that cannot scroll that way is swallowed with no feedback.** For example, a swipe over the docs page dies silently. That is the design, but it is easy to misread as a stuck canvas. A focused terminal always takes sideways swipes (`inScrollableContent`), so over an active terminal only the pill, the gaps and a modifier reach the canvas. **P3-3: the swipe-routing specs that already fail on dev were not touched.** They cover the code this branch changed and cannot guard it. Open a follow-up issue. **P3-4: spec gaps.** `swipe-content-first.spec.ts` has no case for a sideways swipe or a Shift wheel over horizontally scrollable content, nor for one finger on a short window or on a free canvas. My `review-444.spec.ts` shows how. ### What I did not do - Firefox and WebKit. dev-02 lacks their system libraries, as the author said. - Real Shift+wheel conversion. Playwright's synthetic Shift+wheel arrives as deltaY with `shiftKey`, and Chromium did not turn it into deltaX. `canvasDeltas` handles both. - Real Magic Mouse, trackpad and Safari. - The newest origin/dev tip for item 5. Reviewed SHA: **3f71c5936**. I did not merge, push or deploy.
Author
Owner

Research table, corrected (J408 round 2)

Round 1 said Option/Alt had "no default found" in Firefox on macOS. That was wrong: Firefox's all.js sets mousewheel.with_alt.action = 2 (history back/forward) with no platform guard, so it applies on macOS too (J426's finding, accepted).

Measured this round, Firefox 159 (Playwright build, Linux aarch64, headless) and Chromium, synthetic wheel events: probe-ff.mjs, raw results j408-ff.json and j408-cr2.json in ~/.cache/codex-scratch/j408/evidence/. Firefox ran once I launched it outside the nix dev shell (inside it the system libraries are not on the path). WebKit still cannot run (needs libgtk-4, libwebkitgtk-6.0, ...: a host change, not done). Each row was run with and without preventDefault, for a vertical (0,120) and a sideways (120,0) wheel, on a page that was reached by a second navigation so that "back" is observable.

Modifier + wheel Firefox 159, no preventDefault Firefox 159, with preventDefault Chromium, no preventDefault Chromium, with preventDefault
none scrolls no scroll, event seen scrolls no scroll
Shift, vertical wheel scrolls sideways no scroll, shiftKey seen scrolls sideways no scroll
Shift, sideways wheel nothing scrolls no scroll scrolls no scroll
Alt history back (page navigated away), both axes no navigation; event delivered with altKey, cancelable scrolls normally no scroll
Ctrl no scroll (zoom is not observable headless) event seen with ctrlKey scrolls normally no scroll
Meta scrolls normally event seen with metaKey scrolls normally no scroll

What this settles:

  • preventDefault on a wheel event with altKey does cancel Firefox's history navigation (measured on Linux; the same pref line is unguarded on macOS, but I could not run Firefox on a Mac). Every canvas-owned gesture is cancelled by the router, so Alt/Option needs no Firefox-only fallback key.
  • Alt's other cost is none that I could find in Chromium on any OS. Not measured: Safari, Edge, and OS or window-manager actions on Alt+scroll (a Linux compositor might take it).
  • Shift reaches the page and scrolls content sideways natively in both browsers, which is why taking it for the canvas (round 1's default) cost mouse users their sideways content scroll (J426 P2-1).

Recommendation, re-derived: Option/Alt on every OS, for the reasons above (it has a browser action only in Firefox, and that action is cancellable; Shift conflicts with content's sideways scroll). That is what the owner chose in D377, and the code now uses it.

Still not measured: WebKit/Safari, a real Magic Mouse or trackpad, real macOS Firefox, zoom and text-size shortcuts (headless).

## Research table, corrected (J408 round 2) Round 1 said Option/Alt had "no default found" in Firefox on macOS. That was wrong: Firefox's `all.js` sets `mousewheel.with_alt.action = 2` (history back/forward) with no platform guard, so it applies on macOS too (J426's finding, accepted). **Measured this round**, Firefox 159 (Playwright build, Linux aarch64, headless) and Chromium, synthetic wheel events: `probe-ff.mjs`, raw results `j408-ff.json` and `j408-cr2.json` in `~/.cache/codex-scratch/j408/evidence/`. Firefox ran once I launched it outside the nix dev shell (inside it the system libraries are not on the path). WebKit still cannot run (needs `libgtk-4`, `libwebkitgtk-6.0`, ...: a host change, not done). Each row was run with and without `preventDefault`, for a vertical (0,120) and a sideways (120,0) wheel, on a page that was reached by a second navigation so that "back" is observable. | Modifier + wheel | Firefox 159, no `preventDefault` | Firefox 159, with `preventDefault` | Chromium, no `preventDefault` | Chromium, with `preventDefault` | |---|---|---|---|---| | none | scrolls | no scroll, event seen | scrolls | no scroll | | Shift, vertical wheel | scrolls sideways | no scroll, `shiftKey` seen | scrolls sideways | no scroll | | Shift, sideways wheel | nothing scrolls | no scroll | scrolls | no scroll | | **Alt** | **history back (page navigated away), both axes** | **no navigation; event delivered with `altKey`, cancelable** | scrolls normally | no scroll | | Ctrl | no scroll (zoom is not observable headless) | event seen with `ctrlKey` | scrolls normally | no scroll | | Meta | scrolls normally | event seen with `metaKey` | scrolls normally | no scroll | What this settles: - **`preventDefault` on a wheel event with `altKey` does cancel Firefox's history navigation** (measured on Linux; the same pref line is unguarded on macOS, but I could not run Firefox on a Mac). Every canvas-owned gesture is cancelled by the router, so Alt/Option needs no Firefox-only fallback key. - Alt's other cost is none that I could find in Chromium on any OS. Not measured: Safari, Edge, and OS or window-manager actions on Alt+scroll (a Linux compositor might take it). - **Shift** reaches the page and scrolls content sideways natively in both browsers, which is why taking it for the canvas (round 1's default) cost mouse users their sideways content scroll (J426 P2-1). Recommendation, re-derived: **Option/Alt on every OS**, for the reasons above (it has a browser action only in Firefox, and that action is cancellable; Shift conflicts with content's sideways scroll). That is what the owner chose in D377, and the code now uses it. Still not measured: WebKit/Safari, a real Magic Mouse or trackpad, real macOS Firefox, zoom and text-size shortcuts (headless).
Author
Owner

Round 2 progress (head f62a848ff, pushed). Corrected Firefox table is above. Implemented D377: Alt modifier, Shift scrolls content, scrolling never overflows to the canvas, free-canvas one-finger pan pinned, '⌥ + scroll' / 'Alt + scroll' keyboard-help entry. Run 1 (dev-02): red on the old router for the Alt/Shift specs; green 128 passed, 5 failed (clusters-swipes x4 and clusters.spec:512: all fail on origin/dev too, filed as #474 and #472; my Docs-view attempt on the swipes specs did not fix them and was reverted). The Settings end-of-content spec passed on the old router (it could not tell), so I strengthened it; final red/green/quick run is queued (dev-02 queue ~9 deep).

Round 2 progress (head f62a848ff, pushed). Corrected Firefox table is above. Implemented D377: Alt modifier, Shift scrolls content, scrolling never overflows to the canvas, free-canvas one-finger pan pinned, '⌥ + scroll' / 'Alt + scroll' keyboard-help entry. Run 1 (dev-02): red on the old router for the Alt/Shift specs; green 128 passed, 5 failed (clusters-swipes x4 and clusters.spec:512: all fail on origin/dev too, filed as #474 and #472; my Docs-view attempt on the swipes specs did not fix them and was reverted). The Settings end-of-content spec passed on the old router (it could not tell), so I strengthened it; final red/green/quick run is queued (dev-02 queue ~9 deep).
Author
Owner

Round 2 finish. Branch swipe-content-first-d374, head f62a848ff (pushed; git ls-remote confirms).

  • Red on the old router (j408-r3-red.log): the Settings end-of-content spec fails ('s-appearance-theme did not move while the content reached its end: -4902 vs -762'); earlier run: the Alt and Shift specs fail.
  • Green (j408-r3-green.log): 13 passed. Wider run on the D377 code (j408-r2-green.log): 128 passed, 5 failed, all five also fail on origin/dev (#472 clusters.spec:512, #474 clusters-swipes x4).
  • verify quick: exit 0 (j408-r3-quick.log), every row PASS.
  • Not run: real-PTY real/wheel-routing.spec.ts (flipped to expect no hand-off at a terminal's bottom; needs the real server), verify fast, WebKit.
Round 2 finish. Branch swipe-content-first-d374, head f62a848ff (pushed; git ls-remote confirms). - Red on the old router (j408-r3-red.log): the Settings end-of-content spec fails ('s-appearance-theme did not move while the content reached its end: -4902 vs -762'); earlier run: the Alt and Shift specs fail. - Green (j408-r3-green.log): 13 passed. Wider run on the D377 code (j408-r2-green.log): 128 passed, 5 failed, all five also fail on origin/dev (#472 clusters.spec:512, #474 clusters-swipes x4). - verify quick: exit 0 (j408-r3-quick.log), every row PASS. - Not run: real-PTY real/wheel-routing.spec.ts (flipped to expect no hand-off at a terminal's bottom; needs the real server), verify fast, WebKit.
Author
Owner

Review of swipe-content-first-d374 at f62a848ff (round 2, base 80a09ad02)

Verdict: REQUEST CHANGES. D377 is implemented as written and I could not break the Alt, Shift, end-of-page or touch behaviour. Three things stop an approve: the real-PTY spec has not run (item 3), Firefox still navigates history on Alt+wheel over chrome outside the canvas viewport (P2-1), and verify fast is red on the cargo-deny row (not this branch's fault, see item 6). No P1.

Method: production build on dev-02 through build-slot (worktree j444r2), verify fast on compute01. Evidence is in ~/.cache/codex-scratch/j444-r2-review/evidence/ on the control host: review-444-r2.spec.ts, review-444-kb.spec.ts, probe-ff.mjs, probe-ff-app.mjs, j444r2-ff.json, j444r2-cr.json, and shots/*.png. My probe specs are not part of the branch.

The six checks

1. D377 points, 1440×900 and 393×852: PASS (Chromium).

  • Run: the author's swipe-content-first (11), wheel-scroll (4) and settings-rows-of-windows (28) specs plus my probes, 47 passed (17.8m).
  • I probed on a window with neighbours on both sides (the long multiplexer docs page, stop 3 of 12), because the last window cannot show a step (my round 1 mistake, and the author's specs sit on the last window).
    • Mouse wheel 0,300 over the page: the page scrolled, the canvas did not move.
    • To the end, then 5 fresh vertical wheels 400 ms apart and a trackpad-shaped vertical swipe: stop 3 → 3, canvas state unchanged. Same at the top going up. A vertical scroll stops at a page's end.
    • Alt + vertical wheel over the page: stop 3 → 4, the page scrollTop stayed 0. Alt + sideways wheel: 3 → 4. Alt + a trackpad-shaped burst with momentum: 3 → 4 (exactly one stop, no runaway). Screenshots desktop-before-alt.png and desktop-after-alt-vertical.png.
    • Shift + wheel over the page: canvas unchanged. Over a wide block it scrolls sideways (author spec 42, pass).
    • Alt + wheel over a wide block: the canvas moves and the block stays (author spec 41, pass).
  • Touch at 393×852: one finger scrolled the page (oneFingerVerticalScrolledPage: true), and sideways and past-the-end drags left the canvas and stop index at 11. Two fingers moved 11 → 10 (phone-after-one-finger.png, phone-after-two-fingers.png). One finger on empty free canvas still pans (author spec 36, pass).
  • Not covered: Alt+wheel over a scrollable terminal. The only terminal proof is the real-PTY spec, which has not run (item 3). My probes used the docs mock with no focused terminal, so I have no measurement for it.

2. Firefox Alt+wheel and history: the author's table holds, but the app has a gap (P2-1).

  • I repeated the author's probe-ff.mjs on dev-02 (Firefox 159, outside the nix shell, headless). Raw: j444r2-ff.json.
    • Alt, no preventDefault: history back, both axes (navigated: true).
    • Alt, with preventDefault: no navigation on either axis, event delivered with altKey: true, cancelable.
    • Chromium (j444r2-cr.json): Alt scrolls normally without preventDefault, nothing navigates.
  • I then ran Alt+wheel in Firefox against the real production tracer build (probe-ff-app.mjs):
Pointer over In .cl-viewport? Alt+wheel 0,300 Alt+wheel 300,0
centre (pre.canvas-preview) yes no navigation no navigation
top pill (button.ui-pill.capsule) no navigated back navigated back
right edge (section.terminal-screen) no navigated back navigated back

So preventDefault works where the router sees the event, and not elsewhere. See P2-1.

3. real/wheel-routing.spec.ts: NOT RUN. I queued it on compute01 (release musl binary from my verify fast, playwright.real.config.ts). It has been at "queue position 11" in build-slot since 17:35 CET and has not got a slot (load average ~34). I cannot report green or red. It is the only proof for a terminal's end-of-scrollback and for Alt over a terminal, so approval should wait for it. I will post the result here as soon as it has run.

4. Labels.

  • The keyboard help has the new row: Move the canvas with the wheel, keycaps Alt and scroll on Linux (shots/desktop-keyboard-help.png). The unit test pins ⌥ + scroll (Mac) and Alt + scroll (other). I could not render the Mac variant here.
  • Remaining Shift mentions for the canvas: apps/tracer/src/canvas/mock/windows-proto/canvas.ts:250 (Pan sideways, Shift+Scroll) and the header comment at line 10. It is the gated D162 canvas mockup (main.ts:514, canvasMode) with its own Shift pan handler, so it is not the product canvas. P3-1.
  • The old entry Next window or column — Scroll (shortcuts.ts:61) is still in the help. After D377 a plain scroll over content does not go to the next window, so the row is misleading. P3-2.
  • TASTE and the router docs are consistent with D377.

5. Regression risk: reaching the next window.

  • Keyboard: Ctrl+Shift+] and Ctrl+Shift+[ step one window (stop 11 → 10 → 9 → 10, kb.json). Ctrl+Shift+←/→ jump to the window on the left or right (3 → 10 → 11, per desktop.json; it moves across columns, not one stop). Alt+wheel: one stop per gesture. Both work.
  • Trackpad swipe from bare canvas: on the docs view at stop 3, a 60 px sideways swipe from a 5×5 grid of pointer positions reached another window from 12 of 25 positions (48%): the top row (window chrome) and the right-hand side. From none of the left-hand positions, because they sit over scrollable page content. On the last window only 1 of 25 (4%), because the page fills the screen. That is the design (content first), and a docs-full layout has almost no bare canvas. TASTE ("moves on purpose") and D377 allow it, but it is the cost. P3-3 for the owner.
  • Verdict on ease: reachable without overflow, with Alt+wheel and the keyboard. A Magic Mouse or trackpad user with no free hand on Alt has only the window pill and the gaps.

6. scripts/verify fast on compute01 (through build-slot, nix dev shell, HEAD f62a848ffb): exit 1, cargo-deny only.

| fmt                       | PASS         |
| migration names           | PASS         |
| migration tooling tests   | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| Traefik component manifest | PASS         |
| release component publication | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | FAIL (1)     |
| gate architecture         | PASS         |
| doc coverage ratchet      | PASS         |
| shape lints off (D351)    | PASS         |
| doc comments on diff      | PASS         |
| fixed test waits          | PASS         |
| privileged integration    | SKIP         |
| probe release exclusion   | PASS         |
| nightly fuzz reporter     | PASS         |
| static builds             | PASS         |
fast_exit=1
  • cargo-deny: error[vulnerability]: libsqlite3-sys via C SQLite CVE-2026-119375 … ID: RUSTSEC-2026-0459, advisories FAILED, bans ok, licenses ok, sources ok. The branch changes no Rust and no Cargo.lock, so this is the known dev-wide advisory, not this branch.
  • Static sizes (report only, D243 amended): x86_64 28176912 bytes, delta +5758656 against the stale baseline 22418256; aarch64 25306336 bytes, delta +4853056 against 20453280. The branch is TypeScript, docs and tests only, so these deltas are the baseline's age, not this branch.
  • The tracer browser rows are scripts/verify browser, not fast. I ran the three relevant spec files above instead (47 passed).

Findings

P2-1: Alt+wheel over chrome outside the canvas viewport navigates history in Firefox. The wheel listener lives on .cl-viewport (gestures.ts, three viewport.addEventListener("wheel", …)), so preventDefault never runs for an event over the top pill, the toolbar or the screen edge. Firefox's own Alt+wheel action then goes back (measured: 4 of 4 rows in the table above, both axes, probe-ff-app.mjs). D377 and the author's note say Alt is safe "because the router cancels every wheel gesture the canvas owns", but the router does not own the whole page. Effect: a Firefox user who turns the wheel with Alt held over the pill or the screen's edge loses the page; for a terminal app with live sessions that is a navigation away (a beforeunload prompt may or may not help). Fix: one window-level wheel listener with passive: false that calls preventDefault on e.altKey (the canvas keeps routing inside the viewport), plus a spec on Firefox or at least a unit test. Caveat: headless Firefox 159 on Linux; I did not run Firefox on a Mac, where the pref line is unguarded.

P2-2 (gate): real/wheel-routing.spec.ts was flipped to expect no hand-off and has still not run. The author says it needs the real server; I queued it (item 3) and will post the result. A red result is a finding by the brief, and nothing in this review replaces it.

P3-1: stale Shift label in the gated canvas mockup (windows-proto/canvas.ts:10, :250, "Pan sideways", Shift+Scroll). Only reachable with canvasMode; update the words or note that the mockup keeps its own rule.

P3-2: the help row Next window or column — Scroll is now misleading (shortcuts.ts:61). Say where it works ("Scroll on empty canvas") or remove it now that Move the canvas with the wheel and the keyboard rows exist.

P3-3 (owner awareness): reaching the next window without Alt depends on bare canvas, which a docs or settings layout barely has (48% and 4% of the grid positions in my probe). Alt+wheel and Ctrl+Shift+] work, so I do not call it a violation of D377.

P3-4: spec gap. swipe-content-first.spec.ts asserts "no step at the end of the page" on the last window of the Docs view, where a step would have no target (the author's spec 43, and my round 1 probe). It would pass on the old router too, which is why the author had to strengthen the Settings spec. A middle window (as in my probe) catches it. Move the end-of-page case onto a window with a neighbour.

What I did not do

  • real/wheel-routing.spec.ts: not run, see item 3.
  • Alt+wheel over a scrollable terminal; the Mac label; WebKit/Safari; real Magic Mouse and trackpad hardware (synthetic wheel events only); Firefox on a Mac.
  • The five specs that fail on dev (#472, #474), which I did not rerun.

Reviewed SHA: f62a848ff. I did not merge, push or deploy. Test servers I started on dev-02 are stopped; the worktrees ~/worktrees/j444r2 on dev-02 and compute01 are still there for the real spec.

## Review of `swipe-content-first-d374` at **f62a848ff** (round 2, base 80a09ad02) **Verdict: REQUEST CHANGES.** D377 is implemented as written and I could not break the Alt, Shift, end-of-page or touch behaviour. Three things stop an approve: the real-PTY spec has not run (item 3), Firefox still navigates history on Alt+wheel over chrome outside the canvas viewport (P2-1), and `verify fast` is red on the cargo-deny row (not this branch's fault, see item 6). No P1. Method: production build on dev-02 through build-slot (worktree `j444r2`), `verify fast` on compute01. Evidence is in `~/.cache/codex-scratch/j444-r2-review/evidence/` on the control host: `review-444-r2.spec.ts`, `review-444-kb.spec.ts`, `probe-ff.mjs`, `probe-ff-app.mjs`, `j444r2-ff.json`, `j444r2-cr.json`, and `shots/*.png`. My probe specs are not part of the branch. ### The six checks **1. D377 points, 1440×900 and 393×852: PASS (Chromium).** - Run: the author's `swipe-content-first` (11), `wheel-scroll` (4) and `settings-rows-of-windows` (28) specs plus my probes, `47 passed (17.8m)`. - I probed on a window with neighbours on both sides (the long multiplexer docs page, stop 3 of 12), because the last window cannot show a step (my round 1 mistake, and the author's specs sit on the last window). - Mouse wheel 0,300 over the page: the page scrolled, the canvas did not move. - To the end, then 5 fresh vertical wheels 400 ms apart and a trackpad-shaped vertical swipe: stop 3 → 3, canvas state unchanged. Same at the top going up. **A vertical scroll stops at a page's end.** - Alt + vertical wheel over the page: stop 3 → 4, the page scrollTop stayed 0. Alt + sideways wheel: 3 → 4. Alt + a trackpad-shaped burst with momentum: 3 → 4 (exactly one stop, no runaway). Screenshots `desktop-before-alt.png` and `desktop-after-alt-vertical.png`. - Shift + wheel over the page: canvas unchanged. Over a wide block it scrolls sideways (author spec 42, pass). - Alt + wheel over a wide block: the canvas moves and the block stays (author spec 41, pass). - Touch at 393×852: one finger scrolled the page (`oneFingerVerticalScrolledPage: true`), and sideways and past-the-end drags left the canvas and stop index at 11. Two fingers moved 11 → 10 (`phone-after-one-finger.png`, `phone-after-two-fingers.png`). One finger on empty free canvas still pans (author spec 36, pass). - **Not covered: Alt+wheel over a scrollable terminal.** The only terminal proof is the real-PTY spec, which has not run (item 3). My probes used the docs mock with no focused terminal, so I have no measurement for it. **2. Firefox Alt+wheel and history: the author's table holds, but the app has a gap (P2-1).** - I repeated the author's `probe-ff.mjs` on dev-02 (Firefox 159, outside the nix shell, headless). Raw: `j444r2-ff.json`. - Alt, no `preventDefault`: history back, both axes (`navigated: true`). - Alt, with `preventDefault`: no navigation on either axis, event delivered with `altKey: true`, cancelable. - Chromium (`j444r2-cr.json`): Alt scrolls normally without `preventDefault`, nothing navigates. - I then ran Alt+wheel in Firefox against the real production tracer build (`probe-ff-app.mjs`): | Pointer over | In `.cl-viewport`? | Alt+wheel 0,300 | Alt+wheel 300,0 | |---|---|---|---| | centre (`pre.canvas-preview`) | yes | no navigation | no navigation | | top pill (`button.ui-pill.capsule`) | no | **navigated back** | **navigated back** | | right edge (`section.terminal-screen`) | no | **navigated back** | **navigated back** | So `preventDefault` works where the router sees the event, and not elsewhere. See P2-1. **3. `real/wheel-routing.spec.ts`: NOT RUN.** I queued it on compute01 (release musl binary from my `verify fast`, `playwright.real.config.ts`). It has been at "queue position 11" in build-slot since 17:35 CET and has not got a slot (load average ~34). I cannot report green or red. It is the only proof for a terminal's end-of-scrollback and for Alt over a terminal, so approval should wait for it. I will post the result here as soon as it has run. **4. Labels.** - The keyboard help has the new row: `Move the canvas with the wheel`, keycaps `Alt` and `scroll` on Linux (`shots/desktop-keyboard-help.png`). The unit test pins `⌥ + scroll` (Mac) and `Alt + scroll` (other). I could not render the Mac variant here. - Remaining Shift mentions for the canvas: `apps/tracer/src/canvas/mock/windows-proto/canvas.ts:250` (`Pan sideways`, `Shift+Scroll`) and the header comment at line 10. It is the gated D162 canvas mockup (`main.ts:514`, `canvasMode`) with its own Shift pan handler, so it is not the product canvas. P3-1. - The old entry `Next window or column — Scroll` (`shortcuts.ts:61`) is still in the help. After D377 a plain scroll over content does not go to the next window, so the row is misleading. P3-2. - TASTE and the router docs are consistent with D377. **5. Regression risk: reaching the next window.** - Keyboard: `Ctrl+Shift+]` and `Ctrl+Shift+[` step one window (stop 11 → 10 → 9 → 10, `kb.json`). `Ctrl+Shift+←/→` jump to the window on the left or right (3 → 10 → 11, per `desktop.json`; it moves across columns, not one stop). Alt+wheel: one stop per gesture. Both work. - Trackpad swipe from bare canvas: on the docs view at stop 3, a 60 px sideways swipe from a 5×5 grid of pointer positions reached another window from 12 of 25 positions (48%): the top row (window chrome) and the right-hand side. From none of the left-hand positions, because they sit over scrollable page content. On the last window only 1 of 25 (4%), because the page fills the screen. That is the design (content first), and a docs-full layout has almost no bare canvas. TASTE ("moves on purpose") and D377 allow it, but it is the cost. P3-3 for the owner. - Verdict on ease: reachable without overflow, with Alt+wheel and the keyboard. A Magic Mouse or trackpad user with no free hand on Alt has only the window pill and the gaps. **6. `scripts/verify fast` on compute01 (through build-slot, nix dev shell, HEAD f62a848ffb5a246e347be65011bd5a70cb6aa4f8): exit 1, cargo-deny only.** ``` | fmt | PASS | | migration names | PASS | | migration tooling tests | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | Traefik component manifest | PASS | | release component publication | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | FAIL (1) | | gate architecture | PASS | | doc coverage ratchet | PASS | | shape lints off (D351) | PASS | | doc comments on diff | PASS | | fixed test waits | PASS | | privileged integration | SKIP | | probe release exclusion | PASS | | nightly fuzz reporter | PASS | | static builds | PASS | fast_exit=1 ``` - cargo-deny: `error[vulnerability]: libsqlite3-sys via C SQLite CVE-2026-119375 … ID: RUSTSEC-2026-0459`, `advisories FAILED, bans ok, licenses ok, sources ok`. The branch changes no Rust and no `Cargo.lock`, so this is the known dev-wide advisory, not this branch. - Static sizes (report only, D243 amended): x86_64 28176912 bytes, delta +5758656 against the stale baseline 22418256; aarch64 25306336 bytes, delta +4853056 against 20453280. The branch is TypeScript, docs and tests only, so these deltas are the baseline's age, not this branch. - The `tracer` browser rows are `scripts/verify browser`, not `fast`. I ran the three relevant spec files above instead (47 passed). ### Findings **P2-1: Alt+wheel over chrome outside the canvas viewport navigates history in Firefox.** The wheel listener lives on `.cl-viewport` (`gestures.ts`, three `viewport.addEventListener("wheel", …)`), so `preventDefault` never runs for an event over the top pill, the toolbar or the screen edge. Firefox's own Alt+wheel action then goes back (measured: 4 of 4 rows in the table above, both axes, `probe-ff-app.mjs`). D377 and the author's note say Alt is safe "because the router cancels every wheel gesture the canvas owns", but the router does not own the whole page. Effect: a Firefox user who turns the wheel with Alt held over the pill or the screen's edge loses the page; for a terminal app with live sessions that is a navigation away (a beforeunload prompt may or may not help). Fix: one `window`-level `wheel` listener with `passive: false` that calls `preventDefault` on `e.altKey` (the canvas keeps routing inside the viewport), plus a spec on Firefox or at least a unit test. Caveat: headless Firefox 159 on Linux; I did not run Firefox on a Mac, where the pref line is unguarded. **P2-2 (gate): `real/wheel-routing.spec.ts` was flipped to expect no hand-off and has still not run.** The author says it needs the real server; I queued it (item 3) and will post the result. A red result is a finding by the brief, and nothing in this review replaces it. **P3-1: stale Shift label in the gated canvas mockup** (`windows-proto/canvas.ts:10`, `:250`, "Pan sideways", `Shift+Scroll`). Only reachable with `canvasMode`; update the words or note that the mockup keeps its own rule. **P3-2: the help row `Next window or column — Scroll` is now misleading** (`shortcuts.ts:61`). Say where it works ("Scroll on empty canvas") or remove it now that `Move the canvas with the wheel` and the keyboard rows exist. **P3-3 (owner awareness): reaching the next window without Alt depends on bare canvas, which a docs or settings layout barely has** (48% and 4% of the grid positions in my probe). Alt+wheel and `Ctrl+Shift+]` work, so I do not call it a violation of D377. **P3-4: spec gap.** `swipe-content-first.spec.ts` asserts "no step at the end of the page" on the last window of the Docs view, where a step would have no target (the author's spec 43, and my round 1 probe). It would pass on the old router too, which is why the author had to strengthen the Settings spec. A middle window (as in my probe) catches it. Move the end-of-page case onto a window with a neighbour. ### What I did not do - `real/wheel-routing.spec.ts`: not run, see item 3. - Alt+wheel over a scrollable terminal; the Mac label; WebKit/Safari; real Magic Mouse and trackpad hardware (synthetic wheel events only); Firefox on a Mac. - The five specs that fail on dev (#472, #474), which I did not rerun. Reviewed SHA: **f62a848ff**. I did not merge, push or deploy. Test servers I started on dev-02 are stopped; the worktrees `~/worktrees/j444r2` on dev-02 and compute01 are still there for the real spec.
Author
Owner

Round 3 progress (head 785b57791, pushed). Implemented: window-level Alt guard (Firefox history), chrome-as-bare-canvas for sideways swipes (header, 16 px edge bands, peeking neighbours), help row reworded to 'Swipe on empty canvas', stale mockup Shift+Scroll entry removed, end-of-page spec moved to a middle window. J441's real-PTY run never actually ran: it failed at setup ('spawn .../target/x86_64-unknown-linux-musl/release/infraternal ENOENT', the target had been reaped; log ~/j444r2-real.log on compute01). I am running real/wheel-routing.spec.ts myself on dev-02 with an aarch64 server binary copied from j444's target (the web is this branch's build; the server only serves it). Queued in one slot job with red/green/quick (dev-02 queue pos 5).

Round 3 progress (head 785b57791, pushed). Implemented: window-level Alt guard (Firefox history), chrome-as-bare-canvas for sideways swipes (header, 16 px edge bands, peeking neighbours), help row reworded to 'Swipe on empty canvas', stale mockup Shift+Scroll entry removed, end-of-page spec moved to a middle window. J441's real-PTY run never actually ran: it failed at setup ('spawn .../target/x86_64-unknown-linux-musl/release/infraternal ENOENT', the target had been reaped; log ~/j444r2-real.log on compute01). I am running real/wheel-routing.spec.ts myself on dev-02 with an aarch64 server binary copied from j444's target (the web is this branch's build; the server only serves it). Queued in one slot job with red/green/quick (dev-02 queue pos 5).
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#444
No description provided.