From 06d1e02ce1266145a7781e07f996192d163b2894 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sun, 29 Mar 2026 17:21:15 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20hover=20preview=20flashing=20=E2=80=94?= =?UTF-8?q?=20skip=20grid/sidebar=20re-render=20while=20preview=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Poll cycle rebuilds innerHTML every ~2s, destroying the hovered tile's DOM element and tile--previewing class. Fix: renderGrid() and renderSidebar() early-return when _previewPopover is non-null. On hidePreview(), a catch-up render fires so no data is stale. --- muxplex/frontend/app.js | 8 ++++++++ muxplex/frontend/tests/test_app.mjs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index b79e3a8..9f81116 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -353,6 +353,7 @@ function buildSidebarHTML(session, currentSession) { */ function renderSidebar(sessions, currentSession) { if (_viewMode !== 'fullscreen') return; + if (_previewPopover) return; // skip re-render while hover preview is active const list = $('sidebar-list'); if (!list) return; @@ -482,6 +483,8 @@ function bindSidebarClickAway() { * @param {object[]} sessions */ function renderGrid(sessions) { + if (_previewPopover) return; // skip re-render while hover preview is active + const grid = $('session-grid'); const emptyState = $('empty-state'); @@ -612,6 +615,11 @@ function hidePreview() { _previewEl.classList.remove('tile--previewing', 'item--previewing'); _previewEl = null; } + // Catch-up render after preview is dismissed — apply any data missed while paused + if (_currentSessions) { + renderGrid(_currentSessions); + renderSidebar(_currentSessions, _viewingSession); + } } // ─── Notification permission ──────────────────────────────────────────────── diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index d4b1c0c..6ef63c9 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2091,4 +2091,22 @@ test('hover preview delay is 1500ms (not 350ms)', () => { assert.ok(!source.includes(', 350)'), 'old 350ms delay must be removed'); }); +test('renderGrid and renderSidebar skip re-render while preview is active', () => { + const source = fs.readFileSync( + new URL('../app.js', import.meta.url), 'utf8' + ); + + // Find renderGrid function body + const gridFnStart = source.indexOf('function renderGrid'); + const gridFnBody = source.substring(gridFnStart, gridFnStart + 500); + assert.ok(gridFnBody.includes('_previewPopover'), + 'renderGrid must check _previewPopover and skip while active'); + + // Find renderSidebar function body + const sidebarFnStart = source.indexOf('function renderSidebar'); + const sidebarFnBody = source.substring(sidebarFnStart, sidebarFnStart + 500); + assert.ok(sidebarFnBody.includes('_previewPopover'), + 'renderSidebar must check _previewPopover and skip while active'); +}); +