Commit Graph
2 Commits
Author SHA1 Message Date
Brennan Benson f23b3308a5 fix(mobile): route external mouse click and drag to the terminal (#11473)
* fix(mobile): route external mouse click and drag to the terminal

The terminal WebView suppresses mousedown/click at capture so xterm's own
mouse handling stays inert (its onData bytes are dropped by the mobile
bridge). That left hardware mouse clicks and drags with no path at all:
touch taps reached mouse-aware TUIs and drove selection, while a Bluetooth
mouse or trackpad click did nothing (#8818; wheel half landed in #11247).

Add a pointer-event router on the terminal surface (pointerType 'mouse',
left button only) that mirrors touch semantics:

- plain click: same pipeline as a touch tap (links/file paths first, then
  tracking-mode press+release reports, else keyboard focus), and a click
  on an active selection dismisses it like touch does
- drag with mouse tracking: press at the anchor, per-cell motion reports
  (drag/any modes), release on pointerup or pointercancel
- drag without tracking: character-anchored selection reusing the touch
  handle-drag plumbing (edge scroll, handles, copy pill)

Widen the RN gesture-input grammar to pass left-drag motion reports
(SGR button 32, default-encoding byte 64) through the existing
validation and rate limiting.

Mock server: echo the subscribe viewport and serialize scrollback so the
session screen leaves the resubscribe loop, serve the session-tabs
subscribe stream, and add a MOCK_TUI=1 mouse-tracking scenario plus a
[SEND] byte log - the rig used to reproduce and verify this fix on an
Android emulator.

Fixes #8818

* fix(mobile): capture the mouse pointer and clear stale gestures on pointerdown

A drag leaving the terminal surface dropped pointermove/pointerup without
pointer capture, stranding the gesture; a pointerup lost outside the
WebView could leave a tracked press latched until the next gesture.

* fix(mobile): end mouse gestures whose pointerup never reached the surface

Capture the mouse pointer on pointerdown so a drag that leaves the surface
keeps delivering pointermove/pointerup; when capture is unavailable and the
release is lost anyway, synthesize the release from the next buttons==0
pointermove or the next pointerdown, so a tracking TUI is never left with
the left button latched down.

* fix(mock-server): clear the terminal stream interval on resubscribe and unsubscribe

* fix(mobile): synthesize the lost-pointerup release at the pointer's current cell

* test(mobile): split terminal mouse click and drag coverage

* test(mobile): satisfy changed-line quality checks

* fix(mobile): cancel stale mock terminal callbacks

* refactor(mobile): extract mouse report cell mapping
2026-07-31 00:54:40 -07:00
hanjoonchoeandBrennan Benson f4e46383df feat(mobile): add session.tabs.list handler to mock server (#9293)
* feat(mobile): add session.tabs.list handler to mock server

The mock WebSocket server had no handler for session.tabs.list, so the
session screen of a paired dev client hung on 'Loading tabs' forever —
the terminal pane, live input, and command input could never be
exercised against the mock. Respond with a single ready terminal tab
wired to the existing term-1 fixture so the whole session surface works
offline.

* fix(mobile): complete the session.tabs.list mock contract

The new mock response omitted four non-optional fields of
RuntimeMobileSessionTabsResult: publicationEpoch and activeGroupId on the
result, and parentTabId and leafId on the terminal tab. Nothing caught it —
the object literal had no type annotation, and MobileSessionTabsStreamHealth
is generic over both result and tab. A shape-incomplete mock yields
untrustworthy repros for exactly the bugs it gets used for (session tabs,
split panes, pane-to-tab attribution).

Fill the fields with host-realistic values: a per-process publisher epoch, a
layout UUID leaf id, and the `${parentTabId}::${leafId}` surface id
mobileTerminalSurfaceId actually emits. Pin the shape with an explicit return
type so a future required field fails typecheck instead of silently drifting.

Move the fixture into its own module: inlining it pushed
mock-server-rpc-handlers.ts to 317 lines against a 300-line max-lines cap,
which broke `pnpm lint` on the parent commit. It registers through the file's
existing delegation chain, after the native-chat scenario so MOCK_NATIVE_CHAT=1
keeps ownership of the method.

Co-authored-by: Hanjoon Choe <hanjoonchoe@gmail.com>

* test(mobile): pin session tabs mock fidelity

Normalize the selector-backed worktree ID like the real runtime and cover the complete terminal surface response so future contract drift fails the mobile suite.

* fix(mobile): share terminal.list worktree resolution with session tabs

Main added `terminalListWorktreeId`, which the rebased session-tabs fixture
duplicated with a different no-selector fallback — `terminal.list` resolved to
the active fake worktree while `session.tabs.list` returned a literal 'mock',
so a session repro saw two different worktree ids for one screen.

* test(mobile): cover the bare session-tabs worktree selector

Answers the review note that only the `id:`-prefixed path was exercised.

* fix(mobile): make the mock publication epoch unique per process

Date.now() can repeat across a sub-millisecond restart, so the epoch did not
actually guarantee the fresh-publisher identity its comment claimed.

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-29 18:01:38 -07:00