Skip to content

Improve mobile terminal typing and selection#1607

Open
boudra wants to merge 8 commits into
mainfrom
mobile-terminal
Open

Improve mobile terminal typing and selection#1607
boudra wants to merge 8 commits into
mainfrom
mobile-terminal

Conversation

@boudra

@boudra boudra commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Linked issue

None.

Type of change

  • Bug fix
  • New feature (with prior issue + design alignment)
  • Refactor / code improvement
  • Docs

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 format
  • npm run typecheck
  • npm run lint
  • npx vitest run packages/protocol/src/terminal-input-mode.test.ts packages/protocol/src/terminal-key-input.test.ts --bail=1
  • npx 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=1
  • npx vitest run packages/server/src/server/daemon-e2e/terminal-byte-headless-parity.e2e.test.ts --bail=1

Risk 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

  • One focused change. Unrelated cleanups split out.
  • npm run typecheck passes
  • npm run lint passes
  • npm run format ran (Biome)
  • UI changes include screenshots or video for every affected platform
  • Tests added or updated where it made sense

@boudra boudra force-pushed the mobile-terminal branch from f2b0f11 to ff9991a Compare June 19, 2026 03:20
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the WebView-based native terminal renderer with a fully native implementation backed by @xterm/headless state, and wires up a mode-aware key dispatch path so that bracketed paste and application cursor keys now work correctly on both native and web-mobile paths. It also restructures the virtual keyboard bar (adding Paste and a keyboard toggle, removing Backspace and Space), adds copy/paste selection controls, and ships a daemon byte-stream parity E2E test plus several Maestro flows.

  • Native renderer: New native-renderer/ directory owns a headless xterm drain, selection model, gesture recogniser, grid projection, resize policy, and font measurement — all tested in isolation with adjacent unit tests.
  • Protocol fixes: TerminalInputModeTracker now tracks applicationCursorKeys (mode 1) and bracketedPaste (mode 2004), emits both in getPreamble(), and encodeTerminalKeyInput switches arrow sequences between CSI and SS3 based on the tracked state.
  • Virtual keyboard: Backspace and Space buttons are removed in favour of a Paste action and a keyboard-visibility toggle; Copy appears as a floating overlay only when there is an active selection.

Confidence Score: 5/5

Safe to merge with low risk; the previous reconnect and bracketed-paste encoding bugs are fully resolved, and the new native renderer is well-covered by unit and E2E tests.

The two protocol-level fixes (applicationCursorKeys emitted in getState/getPreamble, bracketedPaste emitted in getPreamble) close the bugs flagged in earlier rounds. The native renderer, selection model, output drain, resize policy, and key-dispatch modules all have adjacent tests and a daemon byte-stream parity E2E. The remaining observations are a performance nit on clipboard polling frequency and two dead-code/interface questions; none affect correctness.

terminal-pane.tsx (clipboard polling on every animation frame) and terminal-emulator.native.tsx (swipeGesturesEnabled prop declared but unused)

Important Files Changed

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]
Loading
%%{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]
Loading

Reviews (7): Last reviewed commit: "Restore terminal toolbar focus handling" | Re-trigger Greptile

Comment thread packages/app/src/components/terminal-emulator.native.tsx
Comment thread packages/app/src/components/terminal-emulator.native.tsx Outdated
Comment thread packages/protocol/src/terminal-input-mode.ts
Comment thread packages/server/src/server/daemon-e2e/terminal-byte-headless-parity.e2e.test.ts Outdated
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