From 7cbef2bd4286678dc0d292f86c0e2145ce0ca2af Mon Sep 17 00:00:00 2001 From: nouseman666 Date: Sun, 3 May 2026 22:51:58 +0800 Subject: [PATCH] fix(dashboard): route browser wheel into inner TUI scrolling --- hermes_cli/web_server.py | 7 ------- web/src/pages/ChatPage.tsx | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 63d8455647..773fe71807 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -2949,13 +2949,6 @@ def _resolve_chat_argv( argv, cwd = _make_tui_argv(PROJECT_ROOT / "ui-tui", tui_dev=False) env = os.environ.copy() env.setdefault("NODE_ENV", "production") - # Embedded browser chat should render into the primary screen buffer, not - # the terminal alternate screen. Alt-screen is ideal for the native CLI, - # but it intentionally has no host scrollback; in the web dashboard that - # makes mouse-wheel history feel broken even when xterm itself is healthy. - # INLINE mode keeps transcript rows in the normal buffer so browser-side - # scrollback works predictably. - env.setdefault("HERMES_TUI_INLINE", "1") # Browser-embedded chat should prefer stable wheel-based scrollback over # native terminal mouse tracking. When mouse tracking is enabled, wheel # events are consumed by the TUI and forwarded as terminal input, which diff --git a/web/src/pages/ChatPage.tsx b/web/src/pages/ChatPage.tsx index 80cf0778a3..79e84cf3b6 100644 --- a/web/src/pages/ChatPage.tsx +++ b/web/src/pages/ChatPage.tsx @@ -366,6 +366,40 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { fitRef.current = fit; term.loadAddon(fit); + // Single-scroll-system experiment: + // keep browser xterm as a display/input bridge only, and let the inner + // Hermes TUI own transcript scrolling. + // + // In practice, the most reliable path here is NOT terminal mouse-wheel + // protocol emulation — that can vary by terminal mode and parser path. + // The inner TUI already handles keyboard-driven transcript scrolling + // correctly (`Shift+Up` / `Shift+Down`, `PageUp` / `PageDown`), so we + // translate browser wheel gestures into those known-good key sequences. + term.attachCustomWheelEventHandler((ev) => { + if (wsRef.current?.readyState !== WebSocket.OPEN) { + return false; + } + + const delta = ev.deltaY; + if (!delta) { + return false; + } + + // Shift+Up / Shift+Down: the TUI maps these to line-by-line + // transcript scrolling, which feels much closer to wheel behavior + // than PageUp/PageDown's half-page jumps. + const step = Math.max(1, Math.round(Math.abs(delta) / 50)); + const seq = delta > 0 ? "\x1b[1;2B" : "\x1b[1;2A"; + + for (let i = 0; i < step; i++) { + wsRef.current.send(seq); + } + + ev.preventDefault(); + ev.stopPropagation(); + return false; + }); + const unicode11 = new Unicode11Addon(); term.loadAddon(unicode11); term.unicode.activeVersion = "11";