Skip to content

feat(web): restore tap/click/wheel mouse interaction when server strips mouse DECSETs#144

Open
TeigenZhang wants to merge 2 commits into
Ark0N:masterfrom
TeigenZhang:pr/mouse-restore-decset-strip
Open

feat(web): restore tap/click/wheel mouse interaction when server strips mouse DECSETs#144
TeigenZhang wants to merge 2 commits into
Ark0N:masterfrom
TeigenZhang:pr/mouse-restore-decset-strip

Conversation

@TeigenZhang

Copy link
Copy Markdown
Contributor

Summary

Restores mouse interaction with the terminal (tap-to-position cursor, desktop click-to-position, and scroll-wheel forwarding) for sessions whose output the server strips of mouse-tracking DECSETs.

Root cause. To keep the wheel scrolling scrollback, the server strips ?1000/1002/1003/1006 DECSET sequences from claude/codex/gemini output (isAltScreenStripMode, session.ts). Side effect: the browser xterm's mouseTrackingMode is permanently 'none' for those sessions, even though the TUI on the PTY side still has tracking ON and understands SGR reports. Any UI that gates on xterm's live tracking mode — mobile touchend tap, desktop click, wheel forwarding — silently does nothing.

Fix. When a session is one the server strips, hand-encode the SGR report from the pointer position and send it straight to the PTY, bypassing xterm's dormant encoder:

  • Mobile tap — touchend tap encodes an SGR press+release (was already partly handled; consolidated here).
  • Desktop click — a plain left-click encodes SGR press+release. Every click that already means something else is skipped: synthetic/compat clicks after a touch tap, modified clicks (Shift keeps xterm's selection override), double/triple clicks (word/line select), drag-selections, off-grid clicks, and sessions where xterm's own encoder is live.
  • Wheel — Claude Code 2.1.187+ scrolls its own transcript on SGR wheel reports and no longer captures wheel as select-menu navigation (verified against 2.1.202: the /model menu highlight ignores wheel reports). So the wheel is forwarded to the TUI for strip-mode sessions at the buffer bottom, coalesced into one PTY write per ~40ms to avoid a send-keys process storm. Shift+wheel and any scrolled-up viewport stay on xterm's local scrollback.

Scrollback coordinate guard. SGR coordinates are viewport-relative, so a report computed while scrolled up would hit-test the wrong row. Synthetic taps/clicks/wheel are gated on the viewport being at the buffer bottom.

Behavior is unchanged for shell/opencode sessions and when a session genuinely has mouse tracking off.

Changes

  • terminal-ui.js_handleDesktopTerminalClick, _sendSyntheticSgrTap/Wheel, _shouldForwardWheelToApp, _terminalViewportAtBottom, _clientPointToCell; wheel handler forwards to the app for strip-mode sessions
  • test/terminal-touch-tap.test.ts — 12 cases (SGR encoding, grid clamping, click-skip conditions, wheel gating/coalescing, scrollback guard, shell-mode exclusion)

Test plan

  • npm test -- test/terminal-touch-tap.test.ts — 12/12 pass
  • npm run check:frontend-syntax
  • Verified E2E via Playwright against a live instance: desktop click emits SGR press+release; wheel up/down forward SGR 64/65; Shift+wheel stays on local scrollback
  • Verified the TUI side by injecting SGR bytes into a real claude session in an isolated tmux: click expands a collapsed tool-output block; wheel scrolls the transcript; the /model select menu ignores wheel reports

Teigen added 2 commits July 7, 2026 17:52
…-encode SGR when server strips mouse DECSETs

v1.1.7 (3172bef, arrived via the master merge) strips mouse-tracking DECSET
sequences from claude/codex/gemini output so the wheel keeps scrolling
scrollback. Side effect: the browser xterm's mouseTrackingMode is permanently
'none' for those sessions, and the mobile touchend tap branch gates its
synthetic click on exactly that mode — so tap-to-position-cursor silently died.

Fix: when tracking reads 'none' but the session mode is one the server strips
(claude/codex/gemini — the PTY-side TUI still has tracking ON), encode the SGR
press+release report directly from the touch point and send it to the PTY,
bypassing xterm's mouse encoder. No DOM click is dispatched, so xterm's local
selection cannot trigger either.

Tests: 3 new cases in test/terminal-touch-tap.test.ts (SGR encoding, grid
clamping, shell-mode exclusion); verified E2E via Playwright iPhone emulation
against both a stripped-stream instance and the production bundle.
…ssions

Desktop click-to-position-cursor died under the server's mouse-DECSET strip
(same root cause as the mobile touchend tap regression): xterm's native mouse
encoder only emits SGR while mouseTrackingMode is ON, but the server strips the
enabling DECSETs from claude/codex/gemini output. Hand-encode the report for
plain left-clicks (_handleDesktopTerminalClick), skipping every click that
already means something else (synthetic/compat, modified, double/triple,
drag-selection, off-grid, xterm encoder live).

Also widen forwarding to the wheel: Claude Code 2.1.187+ scrolls its own
transcript on SGR wheel reports and no longer captures wheel as select-menu
navigation (verified against 2.1.202), so forward the wheel to the TUI for
strip-mode sessions at the buffer bottom (40ms-coalesced to avoid a tmux
send-keys storm). Shift+wheel and any scrolled-up viewport stay on xterm's
local scrollback. Guard synthetic taps/clicks on viewport-at-bottom so a
scrolled-up report can't hit-test the wrong row.

Tests: 12 cases in test/terminal-touch-tap.test.ts. Verified E2E via Playwright
against the live instance (wheel up/down forward, Shift+wheel local, click).
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.

1 participant