8000 Touchscreen swipe scrolls nothing (xterm.js 6.0.0 touch regression; alt-screen agent panes need a wheel-equivalent handler) · Issue #243 · amirlehmam/wmux · GitHub
[go: up one dir, main page]

Skip to content

Touchscreen swipe scrolls nothing (xterm.js 6.0.0 touch regression; alt-screen agent panes need a wheel-equivalent handler) #243

Closed

Description

@Mattheinrichs

What happens

On Windows 11 with a touchscreen, dragging a finger vertically over a terminal pane running opencode does nothing. Expected: the swipe should scroll the transcript the same way the mouse wheel does. The wheel works in the same pane; tap, selection and keyboard are unaffected, so it is specifically pan that is inert.

I have only tested this with opencode so far, not with a plain shell pane or other agents.

Steps to reproduce

  1. Windows 11 touchscreen device, wmux 2.11.2.
  2. In a pane, run opencode and build up output longer than the pane.
  3. Drag a finger up or down over the pane. Nothing moves. A mouse wheel in the same pane scrolls the transcript.

wmux version

2.11.2

Windows version

Windows 11 Enterprise; Lenovo ThinkPad Z16 Gen 2 (touchscreen)

Anything else

Why this happens, from reading wmux and xterm sources:

  1. wmux pins @xterm/xterm 6.0.0 exactly (package.json). 6.0.0 is where touch scrolling regressed upstream (Regression - Touch scrolling not functioning in 6.0.0 xtermjs/xterm.js#5489): 5.5.0 had a native overflow scroller a finger could drag; 6.0 replaced the viewport with the VS Code SmoothScrollableElement (custom overlay scrollbar, wheel only) and shipped no touch replacement. The upstream fix, PR Fix touch scrolling xtermjs/xterm.js#5563 ("Fix touch scrolling"), merged 2025-12-31 with milestone 7.0.0. Ancestry check: 6.0.0 is 257 commits behind it, 6.1.0-beta.304 contains it, and no stable release has it yet.
  2. wmux's renderer adds no touch fallback either: no touchstart/touchmove handling and no pointerType === 'touch' branch on the terminal surface (the only pointer handlers are the pane-drag/hover UI). A pan produces no wheel event and no scrollLines call, so the gesture is inert.
  3. A dependency bump alone would not fix this case. The upstream fix only moves the scrollable position, which drives buffer scrollback (Viewport.handleTouchScroll -> _onRequestScrollLines). The alternate buffer has no scrollback, and opencode's TUI (OpenTUI renderer) switches to it (\x1b[?1049h), so touch would still do nothing in exactly the panes wmux is built around (opencode, Claude Code, Codex). The wheel works in those panes because it goes through xterm's wheel pipeline (src/browser/services/MouseService.ts): mouse protocol with wheel support -> button-64/65 reports to the app; alt screen without a wheel protocol -> synthesized arrow keys (Touch scrolling should send arrow keys like how wheel events are translated xtermjs/xterm.js#1007); otherwise -> scrollback. Touch never enters that pipeline.

A fix that would actually resolve this: translate a vertical touch pan on the terminal surface into a synthetic WheelEvent, so it inherits all three wheel behaviors:

  • pointer events filtered to pointerType === 'touch' (Chromium/Electron delivers pointer events; the Touch Events API may not be enabled in Electron), with touch-action: none on the terminal host so the pointermove stream stays cancelable, listeners registered { passive: false };
  • accumulate fractional cell height (touchmove deltas are typically well under one cell), clamp per event, ignore deltas inside a tap's wobble;
  • dispatch new WheelEvent('wheel', { deltaY, deltaMode: 0, bubbles: true, cancelable: true }) on the terminal element, then preventDefault() the pointermove.

Terminal creation lives in src/renderer/hooks/useTerminal.ts; the touch wiring would sit alongside it. A later @xterm/xterm bump (first stable containing #5563) restores the native path for scrollback, but per (3) it is not the whole fix.

Happy to test a build with this on the touchscreen device.

Activity

  1. amirlehmam commented on Sep 18, 2026

    @amirlehmam
    Owner

    Fixed in 2.13.0. Your analysis was correct on all three points and it shaped the fix — in particular point 3, which is the reason this isn't a dependency bump.

    On point 3, you were right, and it's even more true than you could see from outside. wmux doesn't use xterm's wheel pipeline at all. handleTerminalWheel (src/renderer/hooks/useTerminal.ts) takes the wheel on the capture phase — xterm's own forwarding is unreliable after the WebGL context swap (#41), and an adjacent <webview> compositor otherwise steals un-prevented wheel events (#47) — and it already implements all three outcomes itself: scrollback, SGR wheel reports, and arrow keys for a non-mouse pager on the alt screen.

    So your suggested fix was exactly the right one, and for a slightly better reason than stated: synthesizing a WheelEvent doesn't just inherit the three wheel behaviours, it inherits wmux's three, which already cover the alt-screen agent panes upstream's #5563 would leave inert. A finger now behaves identically to the wheel in an opencode pane.

    What shipped, following your outline:

    • pointer events filtered on pointerType === 'touch', touch-action: none on the terminal container, listeners { passive: false }, preventDefault() on the pointermove — only while actually panning, so a horizontal drag is left alone
    • pointercancel handled too, or the tracker keeps a finger down forever and the feature works exactly once
    • tap slop at 8px so a tap never scrolls; axis lock decided once at slop-crossing and never revisited, or a diagonal drag flickers between scrolling and not; a second finger rejects the gesture until the last one lifts
    • clientX/clientY carried onto the synthetic event, because with mouse tracking on handleTerminalWheel reports the wheel at the pointer's cell — without them every flick would report at the top-left corner

    One departure from your suggestion: no fractional-cell accumulator in the touch path. wheelDeltaToLines already keeps a per-surface fractional accumulator against the user's measured cell height, so the gesture just feeds it pixels. A second accumulator would have been a second answer to one question, free to drift from the first — and it would have stopped tracking font size.

    The recognizer is pure (src/renderer/utils/touch-pan.ts, 18 tests) precisely because the device that reproduces this isn't the one CI runs on. I also verified the end-to-end chain against a real xterm 6.0.0 in real Chromium — a capture-phase listener receiving an event dispatched on its own element was the load-bearing assumption and no unit test proves it. All 9 checks pass: direction, tap-inert, horizontal-inert, coordinates, and the computed touch-action.

    That said, none of that is a touchscreen. You offered to test a build and I'd genuinely like to take you up on it — 2.13.0 is building now and the zip will be on the releases page shortly. What I can't verify here is feel: whether the 8px slop is right for a real finger, and whether momentum/inertia is missed (there is none — a pan stops when the finger stops). Both are easy to tune if they're wrong.

  2. amirlehmam commented on Sep 18, 2026

    @amirlehmam
    Owner

    Shipped in v2.13.0.

    Closing since the fix is out, but the offer stands and the feedback is genuinely wanted — if the pan feels wrong on the Z16 (8px slop too eager or too sticky, or the lack of inertia being missed), reopen or file a follow-up and I'll tune it. The recognizer is isolated in src/renderer/utils/touch-pan.ts, so the constants are a one-line change.

  3. Mattheinrichs commented on Sep 18, 2026

    @Mattheinrichs
    ContributorAuthor

    Follow-up after real-device testing and tuning on a ThinkPad Z16 Gen 2 (Windows 11), v2.13.0.

    Verified: touch pan works in opencode's alt-screen pane, tap-inert, horizontal-inert, second-finger reject, pointercancel recovery, no wheel/mouse regressions. 8px slop feels right on a real finger; no jump at slop crossing.

    Momentum: missed. A pan stopping dead at lift-off is noticeable on a finger, and reading a long agent transcript means repeated swipes. If you add a hand-rolled velocity/deceleration pass, happy to test and tune it here (flick threshold, decay).

    One conflict found while tuning opencode's scroll_speed (tui.json): wheel and touch can't be tuned independently, because both arrive as SGR wheel reports and opencode applies one multiplier per report.

    • scroll_speed: 1 → wheel ~4-6 rows per detent (too fast), touch tracks the finger (right).
    • scroll_speed: 0.25 → wheel 1-2 rows per detent (right), touch far too slow.

    The wheel number is inflated because wmux forwards one SGR report per terminal line, so a ~100px notch (~5-6 lines) becomes 5-6 reports; xterm sends one report per wheel event. Filed separately as #245 (pre-existing, not a 2.13.0 regression). Either a touch-only gain on the synthetic deltaY or one-report-per-wheel-event for trusted input would let both be tuned sanely.

    Core fix is good; no urgency on either item from my side.

  4. Mattheinrichs commented on Sep 19, 2026

    @Mattheinrichs
    ContributorAuthor

    Final retest on v2.13.1 (ThinkPad Z16 Gen 2, Windows 11):

    • Wheel: fixed. scroll_speed 3 gives a consistent 3 rows per detent, no overshoot.
    • Touch: at 3 the pan runs ~3 rows per cell of finger travel, so the screen outruns the finger and stops feeling like it tracks. At 2 it felt great (2 rows/cell) but the wheel was slightly slow for long scrolls.

    That is the last bit of the shared multiplier: wheel wants 3, touch wants ~2 or less, and both are one SGR report per step in the app. If the touch path could apply its own gain (a constant or a setting), I could leave the wheel at 3 and scale the synthetic delta until the pan tracks the finger. Happy to test values here.

    Momentum remains the other open ask; filed as #248.

    For what it's worth, those two patches together have turned the touchscreen from unusable-in-practice into something I now use as the primary input in wmux. Fast turnaround too, both same-day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0