fix(run): stop the terminal wheel from typing arrow keys, re-attach dropped sockets
Three separate reasons the Workspace terminal looked unscrollable: - xterm falls back, on an alt-screen buffer whose program has not enabled mouse tracking, to translating each wheel notch into a cursor-key press. Inside a `claude` pane that reads as arrow up/down, so scrolling walked the prompt history. A custom wheel handler returning false suppresses only that emulation branch; real mouse reports come from a separate listener xterm registers when the program does request wheel events. - TerminalView never reconnected, so a server restart left the pane frozen on its last painted frame — still live-looking, but swallowing every keystroke and mouse report. It now re-attaches 1.5s after an unexpected close and stops only on the server's `exit` frame; tmux keeps the session, so the re-attach repaints in full. - tmux repaints the whole pane, so xterm's own scrollback is always empty and its viewport scrollbar renders as a groove with no thumb that cannot move. Hidden, since scrolling there goes through mouse reports, not the DOM.
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
* @file TerminalView.test.tsx
|
||||
* @description Tests for the xterm.js-backed terminal view: verifies it opens
|
||||
* a WS connection to the right URL, writes incoming binary frames to the
|
||||
* mocked terminal, and forwards typed input as outgoing binary frames.
|
||||
* mocked terminal, forwards typed input as outgoing binary frames, and
|
||||
* re-attaches after the socket drops (server restart) but not after `exit`.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
@@ -13,6 +14,7 @@ const writeMock = vi.fn();
|
||||
const onDataHandlers: Array<(d: string) => void> = [];
|
||||
const openMock = vi.fn();
|
||||
const disposeMock = vi.fn();
|
||||
const wheelHandlerMock = vi.fn();
|
||||
|
||||
vi.mock("@xterm/xterm", () => ({
|
||||
Terminal: vi.fn().mockImplementation(() => ({
|
||||
@@ -24,6 +26,7 @@ vi.mock("@xterm/xterm", () => ({
|
||||
},
|
||||
dispose: disposeMock,
|
||||
loadAddon: vi.fn(),
|
||||
attachCustomWheelEventHandler: wheelHandlerMock,
|
||||
})),
|
||||
}));
|
||||
vi.mock("@xterm/addon-fit", () => ({
|
||||
@@ -56,6 +59,7 @@ describe("TerminalView", () => {
|
||||
MockWebSocket.instances = [];
|
||||
onDataHandlers.length = 0;
|
||||
writeMock.mockClear();
|
||||
wheelHandlerMock.mockClear();
|
||||
openMock.mockClear();
|
||||
});
|
||||
|
||||
@@ -90,4 +94,32 @@ describe("TerminalView", () => {
|
||||
onDataHandlers[0]!("ls -la\r");
|
||||
expect(ws.sent).toEqual(["ls -la\r"]);
|
||||
});
|
||||
|
||||
it("swallows wheel events xterm would otherwise turn into arrow keys", () => {
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
expect(wheelHandlerMock).toHaveBeenCalledTimes(1);
|
||||
// false = xterm skips its alt-screen wheel→cursor-key emulation, which in a
|
||||
// claude pane would walk the prompt history instead of scrolling.
|
||||
expect(wheelHandlerMock.mock.calls[0]![0]!(new Event("wheel"))).toBe(false);
|
||||
});
|
||||
|
||||
it("re-attaches after the socket drops (a server restart must not freeze the pane)", () => {
|
||||
vi.useFakeTimers();
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
MockWebSocket.instances[0]!.close();
|
||||
vi.advanceTimersByTime(1600);
|
||||
expect(MockWebSocket.instances).toHaveLength(2);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("does not re-attach once the server reported the pane exited", () => {
|
||||
vi.useFakeTimers();
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
const ws = MockWebSocket.instances[0]!;
|
||||
ws.onmessage?.({ data: '{"type":"exit","code":0}' });
|
||||
ws.close();
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(MockWebSocket.instances).toHaveLength(1);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user