diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index ef7d752..c36b116 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -1050,6 +1050,34 @@ document.addEventListener('DOMContentLoaded', () => { document.addEventListener('keydown', trackInteraction); document.addEventListener('click', trackInteraction); document.addEventListener('touchstart', trackInteraction); + + // --------------------------------------------------------------------------- + // Sidebar touch scroll — manual scrollTop manipulation bypasses the CSS + // touch-scroll path that fails when position:fixed + overflow:hidden is on + // the parent (.session-sidebar) in mobile overlay mode. + // --------------------------------------------------------------------------- + ;(function initSidebarTouchScroll() { + var list = document.querySelector('.sidebar-list'); + if (!list || typeof list.addEventListener !== 'function') return; + + var _lastY = 0; + + list.addEventListener('touchstart', function (e) { + _lastY = e.touches[0].clientY; + }, { passive: true }); + + list.addEventListener('touchmove', function (e) { + e.preventDefault(); // block page scroll + var y = e.touches[0].clientY; + list.scrollTop += _lastY - y; // direct scrollTop — always works + _lastY = y; + }, { passive: false }); // passive:false required for preventDefault + + list.addEventListener('touchend', function () { + _lastY = 0; + }, { passive: true }); + })(); + restoreState() .then(() => { startPolling(); diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index 0933b98..b34e203 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -40,6 +40,7 @@ Object.defineProperty(globalThis, 'navigator', { configurable: true, }); +import fs from 'node:fs'; import { createRequire } from 'node:module'; import { test } from 'node:test'; import assert from 'node:assert/strict'; @@ -2024,3 +2025,14 @@ test('bindSidebarClickAway registers click listener on terminal-container', () = assert.strictEqual(addEventListenerCalledWith, 'click', 'bindSidebarClickAway should register a click listener on terminal-container'); globalThis.document.getElementById = origGetById; }); + +// --- Sidebar touch scroll --- + +test('app.js has sidebar touch scroll handler that uses scrollTop', () => { + const source = fs.readFileSync( + new URL('../app.js', import.meta.url), 'utf8' + ); + assert.ok(source.includes('sidebar-list'), 'must target .sidebar-list'); + assert.ok(source.includes('scrollTop'), 'must use scrollTop (not scrollLines or WheelEvent)'); + assert.ok(source.includes('passive: false'), 'touchmove must be non-passive to allow preventDefault'); +});