* 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
* 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>
* feat(mobile): add Quick Commands (terminal + agent-prompt presets)
Brings the desktop Terminal Quick Commands feature to mobile: saved
agent-prompt or terminal-command presets that launch a new terminal tab.
Entry point sits in the session tab strip next to the "+" new-terminal
button (with a divider) — quick commands spawn a tab, so they live with
tab creation, mirroring desktop's tab-bar split button.
- Launcher button + Quick Commands bottom sheet (search, This project /
Global groups, run/edit/delete rows, add row).
- Add/Edit sheet mirroring desktop TerminalQuickCommandDialog: Label,
Action toggle (Terminal Command | Agent Prompt), Agent select, Prompt /
Command Text, Advanced (Append Enter, Scope Global/Project), validation
and save-failure feedback.
- Launch reuses handleCreateTerminal (extended with enter + toast copy):
agent prompts launch the agent then deliver the prompt; terminal
commands run the (Enter-appended) command text.
- Expose terminalQuickCommands over the remote/mobile RPC surface
(getClientSettings/updateClientSettings allowlists, RuntimeStore type,
and the strict SettingsUpdate zod schema).
- Mirror the agent-prompt support predicate mobile-side (stdin-after-start
agents are unsupported) with a parity test guarding drift from desktop.
- Mock server: sample quick commands + settings.update handler for QA.
* fix(mobile): harden quick command execution
* fix(mobile): harden quick command persistence and launch
* test(mobile): preserve unexpected quick command errors
* fix(mobile): harden quick command launch performance
* fix(runtime): reject malformed quick command updates
* refactor(mobile): reuse shared quick-command logic instead of mirroring
The mobile quick-commands mirror was built on a false premise — that
runtime-importing src/shared/terminal-quick-commands breaks the RN bundle
/ Vitest. It doesn't: tui-agent-config → orca-cli-command-name is a pure
leaf with no module-load Node APIs (verified via probe + bundle-graph).
- Mobile now reuses the canonical desktop helpers (action/agent/scope/
matchesRepo/support/flatten) directly from src/shared; only genuinely
mobile-specific pieces (agent-branded labels, native row truncation,
the launch plan) stay local.
- Multiline runnable terminal commands now flatten via the shared
flattenTerminalQuickCommand (";"-join) — unity with desktop, so a
command saved on one runs identically on the other.
- Drop the MOBILE_TUI_AGENT_PROMPT_COMMAND_UNSUPPORTED mirror + its parity
test; use the shared supportsTerminalAgentQuickCommand predicate.
- Export the shared MAX_QUICK_COMMAND_* length caps for reuse.
* fix(mobile): protect quick command data boundaries
* fix(mobile): enforce quick command limits
* fix(mobile): make quick command updates atomic
* fix(mobile): keep quick command filters recoverable
* fix(mobile): use filled play icon for quick commands
* Revert "fix(mobile): use filled play icon for quick commands"
This reverts commit 169bf053b0.
* fix(mobile): gate quick commands on host capability