From 759485fc47b3829ace87a23101c0e239afaa39ec Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Fri, 22 May 2026 15:29:29 +0100 Subject: [PATCH] fix: ensure session state is removed correctly when closing tabs --- frontend/src/js/app.js | 2 +- frontend/src/js/tabs.js | 7 ++++--- frontend/src/js/terminal.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/frontend/src/js/app.js b/frontend/src/js/app.js index 544a5eb..2d484b5 100644 --- a/frontend/src/js/app.js +++ b/frontend/src/js/app.js @@ -384,8 +384,8 @@ this.rawViewer.destroyViewer(sessionId); window.NexusTerminal.GitHubViewer.destroyViewer(sessionId); window.NexusTerminal.VNCViewer.destroyViewer(sessionId); - this.state.removeSession(sessionId); this.tabs.removeTab(sessionId); + this.state.removeSession(sessionId); this.updateStatusBar(); } diff --git a/frontend/src/js/tabs.js b/frontend/src/js/tabs.js index 237c5e1..5feac49 100644 --- a/frontend/src/js/tabs.js +++ b/frontend/src/js/tabs.js @@ -65,6 +65,7 @@ */ removeTab(sessionId) { const idx = this.order.indexOf(sessionId); + const wasActive = this.app.state.activeSessionId === sessionId; if (idx !== -1) this.order.splice(idx, 1); // Remove tab element @@ -77,8 +78,8 @@ const panel = document.getElementById(`panel-${sessionId}`); if (panel) panel.remove(); - // If this was the active tab, activate the nearest neighbour - if (this.app.state.activeSessionId === sessionId) { + // Activate the nearest neighbour + if (wasActive) { const next = this.order.length > 0 ? this.order[Math.min(idx, this.order.length - 1)] @@ -176,8 +177,8 @@ this.app.terminals.destroyTerminal(sessionId); this.app.rawViewer.destroyViewer(sessionId); window.NexusTerminal.GitHubViewer.destroyViewer(sessionId); - this.app.state.removeSession(sessionId); this.removeTab(sessionId); + this.app.state.removeSession(sessionId); } /** diff --git a/frontend/src/js/terminal.js b/frontend/src/js/terminal.js index 200a652..4fbb47c 100644 --- a/frontend/src/js/terminal.js +++ b/frontend/src/js/terminal.js @@ -112,6 +112,34 @@ }); } + // Alternate-screen scroll passthrough for screen/tmux/byobu. + // When the terminal is in the alternate buffer (used by multiplexers), + // convert mouse-wheel events into arrow-key sequences so the remote + // application handles scrollback instead of xterm.js (which has no + // scrollback content in alternate mode). + const viewportEl = container.querySelector(".xterm-viewport"); + if (viewportEl) { + viewportEl.addEventListener("wheel", (e) => { + if (term.buffer.active !== term.buffer.alternate) return; + // Only intercept when mouse tracking is NOT active (if mouse mode + // is on, xterm.js already forwards events correctly). + if (term.modes && term.modes.mouseTrackingMode && + term.modes.mouseTrackingMode !== "none") return; + + e.preventDefault(); + e.stopPropagation(); + const lines = Math.max(1, Math.round(Math.abs(e.deltaY) / 40)); + const seq = e.deltaY < 0 ? "\x1b[A" : "\x1b[B"; + const data = seq.repeat(lines); + const b64 = btoa(data); + try { + window.go.api.Service.WriteSession(sessionId, b64); + } catch (_) { + /* non-critical */ + } + }, { passive: false }); + } + // Drag-and-drop file handling container.addEventListener("dragover", (e) => { if (