From fcecbb2eff314bc7b18ba2a56bcdd9140ea583e8 Mon Sep 17 00:00:00 2001 From: pfergi42 Date: Fri, 20 Mar 2026 09:48:55 -0700 Subject: [PATCH] tui: stabilize transcript scrollback --- src/tui/components/chat-log.ts | 6 ++++++ src/tui/tui.ts | 13 ++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/tui/components/chat-log.ts b/src/tui/components/chat-log.ts index c8b595a8b3d..4b1f41fa325 100644 --- a/src/tui/components/chat-log.ts +++ b/src/tui/components/chat-log.ts @@ -50,11 +50,16 @@ export class ChatLog extends Container { private append(component: Component) { const wasAtLatest = this.scrollOffset === 0; + const previousLineCount = wasAtLatest ? 0 : this.getRenderedLineCount(this.lastRenderWidth); this.addChild(component); this.pruneOverflow(); if (wasAtLatest) { this.scrollToLatest(); + return; } + const nextLineCount = this.getRenderedLineCount(this.lastRenderWidth); + this.scrollOffset += Math.max(0, nextLineCount - previousLineCount); + this.clampScrollOffset(); } setViewportHeight(height: number | null) { @@ -109,6 +114,7 @@ export class ChatLog extends Container { this.toolById.clear(); this.streamingRuns.clear(); this.btwMessage = null; + this.scrollToLatest(); } addSystem(text: string) { diff --git a/src/tui/tui.ts b/src/tui/tui.ts index f66ce240042..e93c9e43949 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -230,6 +230,10 @@ export function resolveInitialTuiAgentId(params: { return normalizeAgentId(params.fallbackAgentId); } +function isMouseSgrSequence(data: string): boolean { + return /^\x1b\[<\d+;\d+;\d+[Mm]$/.test(data); +} + export function parseMouseWheelEvent(data: string): { direction: "up" | "down"; row: number; col: number } | null { const match = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(data); if (!match) { @@ -552,12 +556,15 @@ export async function runTui(opts: TuiOptions) { const tui = new TUI(new ProcessTerminal()); const dedupeBackspace = createBackspaceDeduper(); tui.addInputListener((data) => { - const mouse = parseMouseWheelEvent(data); - if (!mouse || tui.hasOverlay()) { + if (!isMouseSgrSequence(data)) { return undefined; } + const mouse = parseMouseWheelEvent(data); + if (!mouse || tui.hasOverlay()) { + return { consume: true }; + } if (mouse.row < chatViewportTop || mouse.row > chatViewportBottom) { - return undefined; + return { consume: true }; } if (mouse.direction === "up") { chatLog.scrollLines(3);