diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 117820d..980afa4 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -311,6 +311,37 @@ export const TerminalComponent = React.forwardRef( // Open xterm first (must happen before fit) term.open(container); term.focus(); + + // Mobile: allow native touch scroll on xterm viewport. + // xterm.js 5.x+ uses SmoothScrollableElement which can block native + // touch scrolling. We add passive touch listeners that call + // stopPropagation (not preventDefault) so the browser handles the + // gesture natively while xterm.js internal handlers don't fire. + let touchCleanup: (() => void) | undefined; + if (isMobile) { + const viewport = container.querySelector( + ".xterm-viewport", + ) as HTMLElement | null; + if (viewport) { + const onTouchStart = (e: TouchEvent) => { + e.stopPropagation(); + }; + const onTouchMove = (e: TouchEvent) => { + e.stopPropagation(); + }; + viewport.addEventListener("touchstart", onTouchStart, { + passive: true, + }); + viewport.addEventListener("touchmove", onTouchMove, { + passive: true, + }); + touchCleanup = () => { + viewport.removeEventListener("touchstart", onTouchStart); + viewport.removeEventListener("touchmove", onTouchMove); + }; + } + } + const ws = connectWebSocket(); // Initial fit after layout settles (terminal must be opened first) @@ -429,8 +460,6 @@ export const TerminalComponent = React.forwardRef( }; document.addEventListener("visibilitychange", handleVisibilityChange); - - return () => { isUnmountingRef.current = true; clearTimeout(resizeTimeout); @@ -442,6 +471,7 @@ export const TerminalComponent = React.forwardRef( "visibilitychange", handleVisibilityChange, ); + if (touchCleanup) touchCleanup(); if (ws) { ws.close(1000, "Component unmounting"); } diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index a77c97a..9a0ec76 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -3716,6 +3716,11 @@ a.nav-item, /* Mobile: allow native vertical touch panning on xterm viewport */ .terminal-wrapper.mobile .xterm .xterm-viewport { touch-action: pan-y; + overflow-y: scroll !important; + -webkit-overflow-scrolling: touch !important; + overscroll-behavior-y: contain; + transform: translate3d(0, 0, 0); + scroll-behavior: smooth; } /* Special Keys Strip */