fix: route mobile terminal swipes to the terminal, not the page layout

- Add touch-action: none and overscroll-behavior: none to mobile terminal
  page, content, wrapper, container, and xterm viewport so the browser
  never treats swipes as page/address-bar scrolling or pull-to-refresh.
- Make .terminal-page.mobile fixed-position to prevent viewport layout
  scroll; keep the overlay toolbar as absolute with pointer-events only
  on interactive parts.
- Rework the mobile touch handler in terminal.tsx:
  * Detect normal vs alternate buffer via term.buffer.active.type instead
    of measuring the DOM viewport, which was unreliable in tmux/vim.
  * Accumulate swipe distance and emit SGR 1006 mouse-wheel sequences in
    steps, so tmux pane scrolling tracks the gesture correctly.
  * Prevent default as soon as the swipe is recognized so the page does
    not start a competing scroll gesture.

Quality gates: npm run typecheck, npm run lint clean, npm test -- --run 87 passed.
This commit is contained in:
Developer
2026-06-13 20:30:52 +00:00
parent 1f2c3dbe2a
commit c9b0259993
18 changed files with 111 additions and 49 deletions
+37 -4
View File
@@ -551,6 +551,14 @@ a.nav-item,
height: 100%;
padding: 0;
overflow: hidden;
touch-action: none;
overscroll-behavior: none;
}
/* Ensure xterm's internal viewport also disables browser gestures on mobile */
.terminal-container .xterm-viewport {
touch-action: none;
overscroll-behavior: none;
}
/* xterm.js manages its own positioning and sizing */
@@ -564,6 +572,13 @@ a.nav-item,
/* Mobile terminal container - no padding to maximize space */
.terminal-wrapper.mobile .terminal-container {
padding: 0;
touch-action: none;
overscroll-behavior: none;
}
.terminal-wrapper.mobile {
touch-action: none;
overscroll-behavior: none;
}
/* Ensure xterm viewport fills container properly */
@@ -855,17 +870,23 @@ a.nav-item,
Mobile Terminal Overlay
============================================ */
/* Mobile terminal page — no padding, terminal fills viewport */
/* Mobile terminal page — no padding, terminal fills viewport, locked in place */
.terminal-page.mobile {
padding: 0;
gap: 0;
height: 100vh;
height: 100dvh;
position: relative;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
touch-action: none;
overscroll-behavior: none;
}
/* Overlay status bar — floats over terminal, never resizes it */
/* Overlay status bar — floats over terminal; only its buttons consume pointer events */
.mobile-terminal-overlay {
position: absolute;
top: 0;
@@ -874,11 +895,21 @@ a.nav-item,
z-index: 100;
background: #2d2d2d;
border-bottom: 1px solid #3e3e3e;
pointer-events: none;
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.mobile-terminal-overlay.visible {
pointer-events: auto;
}
.mobile-terminal-overlay > *,
.mobile-terminal-overlay-tabs {
pointer-events: auto;
}
.mobile-terminal-overlay.hidden {
transform: translateY(-100%);
opacity: 0;
@@ -978,13 +1009,15 @@ a.nav-item,
border-bottom: none;
}
/* Terminal content — always fills full viewport on mobile */
/* Terminal content — always fills full viewport on mobile, locked against page scroll */
.terminal-page-content.mobile-full {
flex: 1;
min-height: 0;
border: none;
border-radius: 0;
overflow: hidden;
touch-action: none;
overscroll-behavior: none;
}
/* Mobile fullscreen */