Improve mobile terminal typing and selection#1607
Conversation
|
| Filename | Overview |
|---|---|
| packages/app/src/components/terminal-emulator.native.tsx | Replaces WebView bridge with NativeTerminalEmulator; swipeGesturesEnabled prop is declared in the interface but not destructured or used — swipe callbacks fire unconditionally from the PanResponder based on velocity. |
| packages/app/src/components/terminal-pane.tsx | Wires the new native keyboard, paste, copy, and selection-change handlers; swipeGesturesEnabled broadened from mobileView==="agent" to isMobile; clipboard availability refreshes on every keyboard-animation frame via the keyboardInset effect. |
| packages/protocol/src/terminal-input-mode.ts | Adds applicationCursorKeys (mode 1) and bracketedPaste (mode 2004) tracking to TerminalInputModeTracker; both are now returned by getState() and emitted by getPreamble(), closing two previously flagged reconnect/encoding bugs. |
| packages/protocol/src/terminal-key-input.ts | Arrow-key encoding now branches between CSI and SS3 sequences based on applicationCursorKeys mode, correctly handling application cursor mode for vim/readline navigation. |
| packages/app/src/terminal/runtime/terminal-paste.ts | Clean two-function module: encodeTerminalPaste wraps text with bracketed-paste markers and neutralises embedded end-markers; pasteTerminalClipboard delegates encoding to the terminal adapter. |
| packages/app/src/terminal/runtime/terminal-virtual-keyboard.ts | Restructured virtual keyboard rows replace Backspace (row 1) and Space (row 2) with a KeyboardToggle and a Paste action; both keys remain defined in TERMINAL_VIRTUAL_KEY_BUTTONS but are no longer reachable from the virtual bar. |
| packages/app/src/terminal/native-renderer/headless-terminal-state.ts | Wraps @xterm/headless to expose write, resize, getViewportState, getBufferWindow, and getInputModeState; reads bracketedPasteMode and applicationCursorKeysMode directly from xterm internals. |
| packages/app/src/terminal/native-renderer/headless-terminal-output-drain.ts | Chunked async drain with configurable scheduler hooks; correctly gates paint callbacks on non-disposed state and resolves idle waiters after queue drains. |
| packages/server/src/server/daemon-e2e/terminal-byte-headless-parity.e2e.test.ts | End-to-end byte-stream parity test that attaches a headless xterm client alongside a real daemon, generates high-volume output, and asserts that live, snapshot, and restore views all agree. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User taps Paste] --> B[handleTerminalPaste]
B --> C[pasteTerminalClipboard]
C --> D[Clipboard.getStringAsync]
D --> E{text.length > 0?}
E -- No --> F[skip]
E -- Yes --> G[emulatorRef.current.paste]
G -- Native path --> H[NativeTerminalEmulator.paste]
H --> I[inputModeTrackerRef.getState.bracketedPaste]
I --> J[encodeTerminalPaste]
J --> K[callbacksRef.onInput]
K --> L[client.sendTerminalInput]
G -- Web path --> M[TerminalEmulator.paste / pasteText]
M --> N[runtimeRef.getInputModeState.bracketedPaste]
N --> O[encodeTerminalPaste]
O --> P[mountCallbacksRef.onInput]
P --> L
L --> Q[PTY receives encoded paste]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[User taps Paste] --> B[handleTerminalPaste]
B --> C[pasteTerminalClipboard]
C --> D[Clipboard.getStringAsync]
D --> E{text.length > 0?}
E -- No --> F[skip]
E -- Yes --> G[emulatorRef.current.paste]
G -- Native path --> H[NativeTerminalEmulator.paste]
H --> I[inputModeTrackerRef.getState.bracketedPaste]
I --> J[encodeTerminalPaste]
J --> K[callbacksRef.onInput]
K --> L[client.sendTerminalInput]
G -- Web path --> M[TerminalEmulator.paste / pasteText]
M --> N[runtimeRef.getInputModeState.bracketedPaste]
N --> O[encodeTerminalPaste]
O --> P[mountCallbacksRef.onInput]
P --> L
L --> Q[PTY receives encoded paste]
Reviews (7): Last reviewed commit: "Restore terminal toolbar focus handling" | Re-trigger Greptile
Linked issue
None.
Type of change
What does this PR do
This improves the mobile terminal so native app users can type, scroll, select, copy, and paste inside terminal sessions more reliably.
It adds a native terminal renderer backed by headless xterm state, wires native text input and terminal key dispatch through the same mode-aware encoding path, and updates the mobile terminal controls so Paste stays in the virtual keyboard while Copy appears only for an active selection. It also adds Maestro flows and a daemon byte-stream parity test to cover the mobile terminal behaviors this path depends on.
How did you verify it
npm run formatnpm run typechecknpm run lintnpx vitest run packages/protocol/src/terminal-input-mode.test.ts packages/protocol/src/terminal-key-input.test.ts --bail=1npx vitest run packages/app/src/terminal/runtime/terminal-emulator-runtime.test.ts packages/app/src/terminal/runtime/terminal-key-dispatch.test.ts packages/app/src/terminal/runtime/terminal-paste.test.ts packages/app/src/terminal/runtime/terminal-virtual-keyboard.test.ts packages/app/src/terminal/native-renderer/headless-terminal-output-drain.test.ts packages/app/src/terminal/native-renderer/headless-terminal-state.test.ts packages/app/src/terminal/native-renderer/terminal-grid-metrics.test.ts packages/app/src/terminal/native-renderer/terminal-grid-projection.test.ts packages/app/src/terminal/native-renderer/terminal-input.native.test.ts packages/app/src/terminal/native-renderer/terminal-renderer.bench.test.ts packages/app/src/terminal/native-renderer/terminal-resize-policy.test.ts packages/app/src/terminal/native-renderer/terminal-row-model.test.ts packages/app/src/terminal/native-renderer/terminal-screen-model.test.ts packages/app/src/terminal/native-renderer/terminal-scrollback.test.ts packages/app/src/terminal/native-renderer/terminal-selection-gesture.test.ts packages/app/src/terminal/native-renderer/terminal-selection.test.ts packages/app/src/terminal/native-renderer/terminal-size-measurement.test.ts --bail=1npx vitest run packages/server/src/server/daemon-e2e/terminal-byte-headless-parity.e2e.test.ts --bail=1Risk surface
This touches native iOS/Android terminal rendering, keyboard focus, clipboard, gestures, and terminal resize claiming. CI covers the pure renderer/runtime behavior and daemon byte-stream parity, but the affected native UI still needs a simulator or device smoke pass with screenshots/video before merge.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)