Skip to content

fix(terminal): a mouse wheel notch pages the CLI's transcript at once - #699

Merged
Ark0N merged 2 commits into
Ark0N:masterfrom
opticon454:fix/codex-wheel-dead-zone
Oct 24, 2026
Merged

Ark0N merged 2 commits into
Ark0N:masterfrom
opticon454:fix/codex-wheel-dead-zone

Conversation

@opticon454

@opticon454 opticon454 commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

What

A mouse wheel over a Codex pane (and any CLI paged with PageUp and PageDown) now moves the transcript on its first notch, instead of after 5 notches in Chrome or 7 on Firefox's 3-line notches.

Fixes #697. Related: #698 (the wheel moving a Codex menu's selection, #696), which is independent of this one.

Why

When a CLI does not track the mouse (codex-cli 0.162.1 and 0.164.0 do not), _maybePageCliTranscript turns the wheel into whole-page keys. pageKeysForGesture only pages after half a screen of travel, except for a trackpad-sized opening event (under 2 rows), which pre-pays its page and owes the travel back. A mouse notch is 3 to 4 rows, so it only accumulated and the pane showed nothing. Measured from rest in Chromium against Codeman and Codex 0.164.0, 38 rows, 100 px notches 450 ms apart:

Wheel First screen movement before After
Chrome style, 4 rows a notch notch 5 notch 1
Firefox style, 3 rows a notch notch 7 notch 1

A short scroll of one to four notches looked like a dead wheel, while PageUp and PageDown from the keyboard responded at once.

The trade-off this changes

The comment on PAGE_KEY_IMMEDIATE_MAX_ROWS gave the reason notches were left to accumulate: paging at once on a notch made slow notches, each its own gesture past the gap, page a full screen apiece. That is still guarded against:

  • The first notch of a gesture now pre-pays its page like a trackpad flick, and the notches after it repay it. A new opener keeps the same-direction travel left over from the gesture before, so a run of notches pages once per perPage rows of travel, the same count as before, with the first page arriving sooner (a review on this PR found the first cut dropped that leftover and sent 40 pages for 200 notches against 42 on master).
  • A notch's debt lasts PAGE_KEY_NOTCH_DEBT_MS (3 s, against the trackpad's PAGE_KEY_DEBT_MS of 1 s), so notches 1.2 s apart still repay one page instead of paging a screen each. Notches more than 3 s apart are separate gestures and each pages, which is the most that can be said for a pager that can only move a screen at a time.
  • test/terminal-scroll-routing.test.ts's existing case, five notches at 40, 250 and 1200 ms spacing sending exactly one page, still passes unchanged. The trackpad rules, the debt lifetime of 1 s, reversals and pinches are untouched.

How

pageKeysForGesture (terminal-ui.js, shared by the main pane and tiles) pre-pays an opening event of any size below one page (counting the leftover travel of the gesture before), tags the debt of an opener of 2 rows or more (notch: true) and gives it PAGE_KEY_NOTCH_DEBT_MS. The signal is the opener's size, not the device, so a touch contact whose first touchmove is 2 rows or more, or a brisk trackpad flick that opens large, is treated the same way. Nothing else changes.

Overlap with open PRs

This PR changes pageKeysForGesture and its comments in src/web/public/terminal-ui.js, which other open PRs also edit elsewhere in the file. pr-watch lists #510, #608 and #686 as sharing a file. A git merge-tree of this branch with each head shows no conflict in terminal-ui.js for any of them. The conflicts merge-tree does report for #510 and #608 are in files this branch does not touch (session.ts, mux-interface.ts, keyboard-accessory.js), because those two PRs already conflict with current master on their own. It also merges cleanly with #681 (the scroll-after-tab-switch PR that conflicts with the sibling #698) and with #698 itself. I did not try the others, and will rebase as they land.

Tests

  • Three existing tests asserted that the first notch sends nothing and now assert that it pages at once and the following notch repays it: preserves accumulated coarse Codex wheel fallback travel (renamed) in terminal-scroll-routing.test.ts, and the two tile cases in terminal-tile-scroll.test.ts.
  • Review follow-ups: 6 notches of 4 rows on a 10-row tile send [UP, '', UP, UP, UP, UP] (the leftover is carried; it fails without that), and a touch contact opening with 3 rows pages once and repays.
  • Other new cases in terminal-scroll-routing.test.ts: Chrome and Firefox notch sequences (a page on notch 1, repaid, the next gesture opening on the next notch), a debt kept across 1.2 s pauses and dropped after more than PAGE_KEY_NOTCH_DEBT_MS, and a reversal paging back.
  • The four Chromium suites that drive wheel and touch paging (codex-wheel-routing, terminal-touch-paging, terminal-tile-scroll.browser, terminal-touch-swipe-axis) pass; lint, tsc, the frontend syntax check and prettier pass.
  • Checked end to end in Chromium against Codeman built from this branch (before the leftover fix) with Codex 0.164.0: from rest, the first notch moved the transcript for both wheel styles.

@Ark0N

Ark0N commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Thank you @opticon454 for this, and for the clear write-up in #697. This makes the first mouse-wheel notch over a page-key pane (Codex without mouse tracking, opencode) page the transcript at once, while a 3 s notch debt keeps slow notches from each paging a full screen. The logic checks out: your new and rewritten tests fail on master and pass here, and typecheck, lint, format, the frontend syntax check and the four Chromium wheel/touch suites are all green.

A few things to land with it:

  1. docs/architecture-invariants.md:246 still documents the old rule. It says a larger opening event "is a mouse-wheel notch ... and accumulates as before", gives the debt lifetime as one second only, and lists "notch accumulation" among the pageKeysForGesture rules. Please rewrite those sentences for the new behaviour (any opener under one page pre-pays; an opener of 2 rows or more gets PAGE_KEY_NOTCH_DEBT_MS; past 3 s each notch pages). The same phrase is in the comment at src/web/public/terminal-tile.js:1176.

  2. Leftover travel is dropped when a notch opens a new gesture (src/web/public/terminal-ui.js:260). The pre-pay returns pending: lines - dir * perPage and discards the same-direction remainder left after the previous debt was repaid, so travel per page becomes ceil(perPage / notch) * notch. With notches 400 ms apart: 200 Chrome notches on 38 rows send 40 pages against master's 42, and 6 notches on a 10-row tile send 3 against 4. Carrying the remainder fixes it and keeps the first-notch page:

    const carried = pending * dir > 0 ? pending : 0;
    if (size >= PAGE_KEY_MIN_START_ROWS && size < perPage && (carried + lines) * dir < perPage) {
      return { state: { pending: carried + lines - dir * perPage, /* rest unchanged */ } };
    }

    With that, the existing scroll-routing, tile-scroll and touch-tap tests all pass, and the counts match master. A pinning case: 6 notches of -4 on rows = 10, expecting [UP, '', UP, UP, UP, UP].

  3. The notch signal is opener size, not device (src/web/public/terminal-ui.js:101). A touch contact whose first touchmove is 2+ lines now pages at once too, and a brisk trackpad flick with a large first event gets the 3 s debt. Both are fine, but please say so in the comments on PAGE_KEY_IMMEDIATE_MAX_ROWS and PAGE_KEY_NOTCH_DEBT_MS, and add one touch case to the pure-helper tests ([-3, 0, { touchId: 1 }], [-3, 16, { touchId: 1 }] expecting [UP, '']).

  4. Small ones: the test at test/terminal-scroll-routing.test.ts:697 is still called "accumulates a notch-sized opening event" and its comment says "none pages alone"; please rename it and assert [UP, '', '', '', ''] instead of the joined string. The comments at terminal-ui.js:101, :214 and :6064 still describe the trackpad-only pre-pay.

I will apply these on top when merging. If you push them first, I will merge your version instead.

@opticon454

Copy link
Copy Markdown
Contributor Author

Thanks @Ark0N. All four are pushed in 77eefce4, so there is nothing for you to apply.

  1. Docs. docs/architecture-invariants.md now describes the new rule: any opener under one page pre-pays; an opener of 2 rows or more is tagged as a notch and gets PAGE_KEY_NOTCH_DEBT_MS instead of PAGE_KEY_DEBT_MS; after 3 s of quiet each notch pages; the signal is opener size, not device; and a new opener keeps the leftover travel. The "notch accumulation" mention is now "notch debt", here and in the comment at terminal-tile.js:1176.
  2. Leftover travel. Taken as you wrote it (carried = pending * dir > 0 ? pending : 0, and the pre-pay only when (carried + lines) * dir < perPage, so a bigger total falls through to plain travel). Your pinning case is in: 6 notches of -4 on rows = 10 give [UP, '', UP, UP, UP, UP], and the test fails if carried is forced to 0. I also added a 200-notch run on 38 rows asserting the page count stays within one page of travel divided by perPage.
  3. Opener size, not device. PAGE_KEY_IMMEDIATE_MAX_ROWS and PAGE_KEY_NOTCH_DEBT_MS now say so in their comments (the touch and brisk-flick cases get the 3 s debt, and that is fine), and the touch case is in the helper tests: [-3, 0, { touchId: 1 }], [-3, 16, { touchId: 1 }] give [UP, ''].
  4. Small ones. The :697 test is renamed "pages a notch-sized opening event at once, and the notches after it repay it" with [UP, '', '', '', ''] and its comment fixed; the three comments at terminal-ui.js that described the trackpad-only pre-pay now describe any opener under a page.

Typecheck, lint, prettier, the frontend syntax check, the scroll-routing, tile and touch unit tests (200) and the four Chromium wheel and touch suites (9) pass.

Heads-up: this and #698 both edit the long gesture-rules paragraph at docs/architecture-invariants.md:246, so whichever lands second needs a trivial manual merge there; a git merge-tree of the two branches shows that as the only conflict.

@Ark0N

Ark0N commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Thank you @opticon454 for this, and for turning the first round around so quickly. This makes the first mouse-wheel notch over a page-key pane (Codex without mouse tracking, opencode) page the transcript at once, with a 3 s notch debt so slow notches still repay that page instead of each paging a screen.

All four points from the first review are in 77eefce4: the invariant paragraph and the tile comment describe the new rule, the leftover travel is carried (200 notches on 38 rows now send 42 pages, as on master, and the 10-row tile case sends 5 against the first cut's 3), the opener-size note and the touch case are in, and the test name and comments are fixed. Your new and rewritten tests fail on master and pass here, and typecheck, lint, format, the frontend syntax check, the full unit suite and the four Chromium wheel and touch suites are green.

At merge time I will touch two comment fragments in pageKeysForGesture that still give the 1 s debt only: src/web/public/terminal-ui.js:218 ("stretched to PAGE_KEY_DEBT_MS") and :286 ("the one-second debt lifetime"). Nothing for you to do there.

Thanks also for flagging the overlap with #698 on docs/architecture-invariants.md:246; whichever lands second gets a hand merge of that paragraph. Merging this one as it stands.

@Ark0N
Ark0N merged commit 77eefce into Ark0N:master Oct 24, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Oct 24, 2026
pageKeysForGesture comment (terminal-ui.js:218): the gap stretches to PAGE_KEY_NOTCH_DEBT_MS after a notch-sized opener, not only PAGE_KEY_DEBT_MS
pageKeysForGesture comment (terminal-ui.js:286): a repaid debt ends either lifetime, not only the one-second one

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 24, 2026
terminal-ui.js: the _maybePageCliTranscript JSDoc names PAGE_KEY_NOTCH_DEBT_MS as the stretched gap after a notch-sized opener
terminal-scroll-routing.test.ts: the notch-opener test comment uses the real numbers (36 rows, 14 rows owed)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 24, 2026
… CLI's transcript at once

Conflict in docs/architecture-invariants.md resolved by keeping both
sides: the hollow-pane paragraph carries #681's unloaded-scrollback
sentence, #698's Codex-menu rewording and #699's notch rewording (a
word-level three-way merge; the three edits touch disjoint sentences).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Ark0N

Ark0N commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Merged, thanks! This ships in 1.45.0 and closes #697.

The hollow-pane paragraph in architecture-invariants had edits from #681, #698 and this PR, and it merged word for word with no conflicts. At merge I only touched comments: pageKeysForGesture and the _maybePageCliTranscript JSDoc now name PAGE_KEY_NOTCH_DEBT_MS as the stretched gap after a notch-sized opener, a repaid debt ends either lifetime, and the notch-opener test comment uses the real numbers (36 rows, 14 owed).

@opticon454
opticon454 deleted the fix/codex-wheel-dead-zone branch October 24, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex and other page-key CLIs: a mouse wheel does nothing for the first 4 to 6 notches

2 participants