78609c005e
* feat(terminal): add touch-scroll helper for xterm scrollback Pure helper that translates single-finger vertical drags into `terminal.scrollLines(n)` calls. Handles threshold gating so taps still reach xterm, ignores multi-touch, yields to horizontal-dominant drags. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal): wire mobile touch scrollback into passthrough terminal Adds an `enableTouchScroll` prop on PassthroughTerminal and a thin hook that calls `attachTouchScroll` while the terminal is mounted. The two mobile mount points (terminal panel + chat-panel passthrough embed) opt in; desktop callers are unaffected. Fixes the bug where xterm.js's canvas absorbed touch gestures and left the scrollback unreachable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(terminal): mobile e2e for passthrough touch-scroll Pixel 5-emulated Playwright spec: produce 200 lines of output, simulate a single-finger down-swipe, assert `terminal.buffer.active.viewportY` moved into the scrollback, then swipe back to return. Extends the e2e buffer reader to expose viewportY alongside `__xtermReadBuffer`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(terminal): relax mobile swipe-back assertion against late shell output Replace the exact `.toBe(bottomViewportY)` check with `.toBeGreaterThanOrEqual` so the test doesn't flake if shell echo arrives between the snapshot and the post-swipe read. xterm clamps at the buffer boundary, so overshooting back-to-bottom is impossible. Addresses PR #1046 Claude review thread 3293258085. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(terminal): document useTouchScroll's isTerminalReady invariant Adds a comment next to the eslint-disable explaining why the deps array can safely omit terminalRef/xtermRef: useTerminalInit only flips isTerminalReady false→true once per mount. A future refactor that recreates the terminal mid-mount must add a new dep. Addresses PR #1046 Claude review thread 3293258175. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(terminal): clear all exposed buffer-reader fields on dispose clearBufferReader now nulls `__xtermGetFontFamily` and `__xtermGetFontSize` alongside the buffer readers. The two predated this PR and were never cleaned up; keeping the teardown symmetric with exposeBufferReader prevents stale references if the container is ever reused. Addresses PR #1046 Claude review thread 3293274674. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(terminal): reset document.body between touch-scroll cases Adds an afterEach to wipe jsdom's body so orphan divs from one test don't bleed into the next. Avoids cross-test pollution if a future case queries document.body for containers. Addresses PR #1046 Claude review thread 3293274769. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
37 lines
1.4 KiB
TypeScript
37 lines
1.4 KiB
TypeScript
import { useEffect } from "react";
|
|
import type { Terminal } from "@xterm/xterm";
|
|
import { attachTouchScroll } from "@/lib/terminal/touch-scroll";
|
|
|
|
export type TouchScrollOptions = {
|
|
terminalRef: React.RefObject<HTMLDivElement | null>;
|
|
xtermRef: React.MutableRefObject<Terminal | null>;
|
|
/** When false, the hook is a no-op (desktop / non-mobile callers). */
|
|
enabled: boolean;
|
|
/** Gate to wait until the terminal instance has been created. */
|
|
isTerminalReady: boolean;
|
|
};
|
|
|
|
/**
|
|
* Wire touch-drag-to-scrollback on the terminal container while `enabled`.
|
|
* No-op on desktop callers — touchstart/touchmove never fire from mouse input.
|
|
*/
|
|
export function useTouchScroll({
|
|
terminalRef,
|
|
xtermRef,
|
|
enabled,
|
|
isTerminalReady,
|
|
}: TouchScrollOptions) {
|
|
useEffect(() => {
|
|
if (!enabled || !isTerminalReady) return;
|
|
const container = terminalRef.current;
|
|
const terminal = xtermRef.current;
|
|
if (!container || !terminal) return;
|
|
return attachTouchScroll(container, terminal);
|
|
// Invariant: `useTerminalInit` creates the xterm instance once per mount
|
|
// and only flips `isTerminalReady` from false → true. If a future change
|
|
// ever recreates the terminal mid-mount, add the new identity as a dep so
|
|
// the listener re-attaches to the new instance.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable
|
|
}, [enabled, isTerminalReady]);
|
|
}
|