From afffb8d9a56b850f98ba5d696de030c5c46b8030 Mon Sep 17 00:00:00 2001 From: YuanHanzhong <57024493+YuanHanzhong@users.noreply.github.com> Date: Tue, 19 May 2026 00:07:26 -0700 Subject: [PATCH] fix(dashboard): use browser scrollback for chat wheel --- hermes_cli/web_server.py | 1 + tests/hermes_cli/test_web_server.py | 15 +++++++++++++ web/src/pages/ChatPage.tsx | 33 ++++++----------------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 52f7dd79333..7d28ce07617 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -3350,6 +3350,7 @@ def _resolve_chat_argv( # build unchanged for native CLI usage; only disable mouse tracking for # the dashboard PTY path. env.setdefault("HERMES_TUI_DISABLE_MOUSE", "1") + env.setdefault("HERMES_TUI_INLINE", "1") if resume: latest_resume, _latest_path = _session_latest_descendant(resume) diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index ca2876f0f5c..f5c06205621 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -2092,6 +2092,21 @@ class TestPtyWebSocket: q = {"token": tok, **params} return f"/api/pty?{urlencode(q)}" + def test_resolve_chat_argv_uses_dashboard_scroll_env(self, monkeypatch): + """Dashboard chat runs the TUI in browser-scrollback mode.""" + import hermes_cli.main as main_mod + + monkeypatch.setattr( + main_mod, + "_make_tui_argv", + lambda project_root, tui_dev=False: (["node", "dist/entry.js"], "/tmp/ui-tui"), + ) + + _argv, _cwd, env = self.ws_module._resolve_chat_argv() + + assert env["HERMES_TUI_INLINE"] == "1" + assert env["HERMES_TUI_DISABLE_MOUSE"] == "1" + def test_rejects_when_embedded_chat_disabled(self, monkeypatch): monkeypatch.setattr(self.ws_module, "_DASHBOARD_EMBEDDED_CHAT_ENABLED", False) from starlette.websockets import WebSocketDisconnect diff --git a/web/src/pages/ChatPage.tsx b/web/src/pages/ChatPage.tsx index 3e3c2e3268b..d257531f23e 100644 --- a/web/src/pages/ChatPage.tsx +++ b/web/src/pages/ChatPage.tsx @@ -298,10 +298,9 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { // is false; enabling it gives users a single-action selection // path on top of the modifier-based bypass above. rightClickSelectsWord: true, - // Single-scroll-system experiment: - // let the inner Hermes TUI own transcript history/scroll behavior. - // The outer browser xterm should act as a display/input bridge only. - scrollback: 0, + // Browser-embedded chat runs the TUI in inline mode. Keep transcript + // history in xterm.js so the browser wheel can scroll it directly. + scrollback: 5000, theme: TERMINAL_THEME, }); termRef.current = term; @@ -345,7 +344,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { // original keydown event's activation. Log to aid debugging. console.warn("[dashboard clipboard] OSC 52 write failed:", err.message); }); - } catch (e) { + } catch { console.warn("[dashboard clipboard] malformed OSC 52 payload"); } return true; @@ -404,34 +403,16 @@ 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. + // Dashboard chat should scroll the browser-side transcript, not send + // mouse-wheel protocol bytes through the PTY. 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); - } + term.scrollLines(delta > 0 ? step : -step); ev.preventDefault(); ev.stopPropagation();