From 11f99114506ad2cc759cb389663edaf7546cbe8c Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Mon, 12 Oct 2026 14:21:08 +0200 Subject: [PATCH] fix(tui): let unhandled wheel input scroll the transcript under a focused overlay Wheel events that no component handles now scroll the transcript instead of reaching the focused overlay's handleInput as raw input. Overlays that scroll with the wheel must implement handleMouse. fixes #10769 --- packages/tui/CHANGELOG.md | 1 + packages/tui/src/tui-alt-screen.ts | 1 - packages/tui/test/tui-alt-screen.test.ts | 25 +++++++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index b654906cd..713c29ce8 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -17,6 +17,7 @@ ### Fixed - Fixed overlay compositing dropping `CURSOR_MARKER` when an overlay ends directly left of the cursor, which lost the hardware cursor position +- Fixed focused fullscreen overlays blocking mouse-wheel scrolling of the transcript. Wheel events that no component handles now scroll the transcript instead of reaching the focused overlay's `handleInput` as raw input; overlays that scroll with the wheel must implement `handleMouse` ([#10769](https://github.com/earendil-works/pi/issues/10769)) - Fixed fragmented terminal color replies leaking hex values into the prompt and triggering the external editor via their BEL terminator (related reports: [#10256](https://github.com/earendil-works/pi/issues/10256), [#10657](https://github.com/earendil-works/pi/issues/10657)) ## [1.1.0] - 2026-10-07 diff --git a/packages/tui/src/tui-alt-screen.ts b/packages/tui/src/tui-alt-screen.ts index a0ffeb89a..e92f5c953 100644 --- a/packages/tui/src/tui-alt-screen.ts +++ b/packages/tui/src/tui-alt-screen.ts @@ -715,7 +715,6 @@ export class TuiAltScreen extends TuiBase implements ViewportTUI { if (this.applyMouseDispatchResult(event, result)) this.requestRender(); return { consume: true }; } - if (this.shouldDeferViewportInputToOverlay()) return undefined; this.routeWheel(wheelEvent, wheelDelta); return { consume: true }; } diff --git a/packages/tui/test/tui-alt-screen.test.ts b/packages/tui/test/tui-alt-screen.test.ts index 34a9b2e0b..25772b54c 100644 --- a/packages/tui/test/tui-alt-screen.test.ts +++ b/packages/tui/test/tui-alt-screen.test.ts @@ -1862,6 +1862,26 @@ describe("TuiAltScreen", () => { tui.stop(); }); + // #10769 + it("scrolls the transcript on wheel input a focused overlay does not handle", async () => { + const terminal = new VirtualTerminal(20, 6); + const tui = new TuiAltScreen(terminal); + tui.addChild(new Text(Array.from({ length: 12 }, (_, index) => `line ${index + 1}`).join("\n"), 0, 0)); + const overlay = new InputOverlay(); + tui.start(); + await terminal.waitForRender(); + const topBefore = tui.viewportTop; + tui.showOverlay(overlay, { anchor: "top-left", width: 10 }); + await terminal.waitForRender(); + assert.strictEqual(overlay.focused, true); + + terminal.sendInput("\x1b[<64;3;1M"); + await terminal.waitForRender(); + assert.ok(tui.viewportTop < topBefore); + assert.deepStrictEqual(overlay.inputs, []); + tui.stop(); + }); + it("focuses and captures drag gestures for mouse-aware components", async () => { const terminal = new VirtualTerminal(20, 2); const tui = new TuiAltScreen(terminal); @@ -2001,7 +2021,7 @@ describe("TuiAltScreen", () => { } }); - it("gives wheel and viewport keys to a focused overlay", async () => { + it("gives viewport keys to a focused overlay", async () => { const terminal = new VirtualTerminal(20, 6); const tui = new TuiAltScreen(terminal); tui.addChild(new Text(Array.from({ length: 12 }, (_, index) => `line ${index + 1}`).join("\n"), 0, 0)); @@ -2013,8 +2033,7 @@ describe("TuiAltScreen", () => { await terminal.waitForRender(); assert.strictEqual(overlay.focused, true); - const wheel = "\x1b[<64;10;3M"; - const keys = ["\x1b[5~", "\x1b[6~", "\x1bOH", "\x1bOF", wheel]; + const keys = ["\x1b[5~", "\x1b[6~", "\x1bOH", "\x1bOF"]; for (const key of keys) terminal.sendInput(key); await terminal.waitForRender();