Skip to content

Fix terminal bottom content obscured and enable touch-scroll in TUI apps - #39

Open
goxofy wants to merge 4 commits into
Termix-SSH:mainfrom
goxofy:fix/tui-scroll-and-bottom-obscured
Open

Fix terminal bottom content obscured and enable touch-scroll in TUI apps#39
goxofy wants to merge 4 commits into
Termix-SSH:mainfrom
goxofy:fix/tui-scroll-and-bottom-obscured

Conversation

@goxofy

@goxofy goxofy commented Aug 4, 2026

Copy link
Copy Markdown

Summary

This PR fixes two mobile terminal issues that affect interactive CLI tools such as Claude Code and Codex:

  1. The bottom input row and last few lines of TUI apps were hidden behind the app's toolbar / system keyboard.
  2. Touch-swipe scrolling did not work inside TUI apps, so users could not scroll back through prior output.

1. Terminal bottom content obscured

TUI apps draw their input row at the last row of the pty. The WebView was already sized to the visible area by the parent marginBottom, but xterm computed its row count from #terminal's 100vh CSS height. Inside a WKWebView, 100vh / window.innerHeight is stale after a frame resize, so xterm reported more rows than the visible area — the pty told the shell too many rows, and TUIs painted their input row behind the keyboard bar.

Fix: measure the real visible height with React Native's onLayout, push it into the WebView (debounced to avoid resize storms), pin #terminal's height to it, then re-fit and re-send the pty resize. The height is re-applied on terminalReady and nativeFit/resize prefer the pinned height over the stale 100vh.

2. Touch-swipe scrolling in TUI (alternate screen) apps

Claude Code / Codex run on xterm's alternate screen buffer, which has no scrollback — the old handler's terminal.scrollLines() is a no-op there, so swiping did nothing.

Fix: synthesize a WheelEvent on the xterm root element. xterm routes it correctly:

  • normal buffer → scrollback scroll (unchanged behavior),
  • alternate buffer with mouse tracking enabled → SGR wheel sequences (?1000/?1006),
  • alternate buffer without tracking → arrow-key sequences.

Enabling disableStdin and bridging terminal.onData back to the pty (via a new input WebView message) lets the wheel bytes reach the shell. Regular keyboard input is unaffected — it goes through the RN IME and never hits xterm's onData.

A follow-up commit makes the scroll touchmove non-passive with preventDefault (skipping during text selection) and disables WebView native scrolling, so an up-swipe back to the newest content scrolls the terminal instead of the whole page.

Testing

Verified on iOS and Android physical devices:

  • Bottom input row and last lines of Claude Code / Codex are no longer hidden by the toolbar or keyboard.
  • Swiping down scrolls back through prior TUI output; swiping up returns to the newest content — the content scrolls, not the page.
  • Normal shell scrollback, long-press text selection, and the scroll-to-bottom button still work.

All changes are confined to app/tabs/sessions/terminal/Terminal.tsx.

Tink and others added 4 commits August 4, 2026 14:26
TUI apps (Claude Code, Codex, vim, htop) draw their bottom input row at
the pty's last row. The WebView was sized by marginBottom so it spans only
the visible area, but xterm computed rows from #terminal's 100vh CSS height,
which WKWebView reports stale after a frame resize. The pty told the shell
too many rows, so TUIs painted their input row behind the keyboard bar.

Measure the real visible height with onLayout, push it into the WebView
(debounced to avoid resize storms), pin #terminal's height to it, then fit
and re-send the pty resize. Re-apply on terminalReady and prefer the pinned
height in nativeFit/resize over the stale 100vh.

Co-Authored-By: Claude <noreply@anthropic.com>
The previous touch handler called terminal.scrollLines(), which is a no-op on
the alternate screen buffer that TUI apps run in, so swiping did nothing
inside Claude Code / Codex. Synthesize a wheel event on the xterm root
element instead: xterm routes it to the scrollback in the normal buffer, or
to SGR mouse sequences / arrow keys in the alternate buffer, which TUIs
understand. Enable stdin in the WebView and bridge xterm onData back to the
pty via a new 'input' WebView message so the synthesized wheel bytes reach
the shell. Keyboard input is unaffected (it goes through the RN IME).

Co-Authored-By: Claude <noreply@anthropic.com>
When swiping up in a TUI app to return to the newest content, the native
touch scroll of .xterm-viewport bubbled to the WebView page when the
alternate buffer was at its top, scrolling the whole page instead of the
terminal. Disable native touch-scrolling at the source with touch-action:
none on the terminal elements (including .xterm-screen, the actual touch
hit-target), so the synthetic WheelEvent remains the sole scroll driver.
Works on both iOS WKWebView and Android WebView.

Co-Authored-By: Claude <noreply@anthropic.com>
touch-action:none alone is not enough on iOS WKWebView: a passive
touchmove still lets the page steal the gesture when the alt buffer is
already at its top, so the whole terminal slides instead of the content.
Make the scroll touchmove non-passive, call preventDefault (skipping only
when text selection is active), disable WebView scrollEnabled, and pin
.xterm-viewport overflow so the synthetic WheelEvent remains the only
scroll driver.

Co-Authored-By: Claude <noreply@anthropic.com>
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