fix: hover preview flashing — skip grid/sidebar re-render while preview active

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.
This commit is contained in:
Brian Krabach
2026-03-29 17:21:15 -07:00
parent 905cbf35e5
commit 06d1e02ce1
2 changed files with 26 additions and 0 deletions
+8
View File
@@ -353,6 +353,7 @@ function buildSidebarHTML(session, currentSession) {
*/ */
function renderSidebar(sessions, currentSession) { function renderSidebar(sessions, currentSession) {
if (_viewMode !== 'fullscreen') return; if (_viewMode !== 'fullscreen') return;
if (_previewPopover) return; // skip re-render while hover preview is active
const list = $('sidebar-list'); const list = $('sidebar-list');
if (!list) return; if (!list) return;
@@ -482,6 +483,8 @@ function bindSidebarClickAway() {
* @param {object[]} sessions * @param {object[]} sessions
*/ */
function renderGrid(sessions) { function renderGrid(sessions) {
if (_previewPopover) return; // skip re-render while hover preview is active
const grid = $('session-grid'); const grid = $('session-grid');
const emptyState = $('empty-state'); const emptyState = $('empty-state');
@@ -612,6 +615,11 @@ function hidePreview() {
_previewEl.classList.remove('tile--previewing', 'item--previewing'); _previewEl.classList.remove('tile--previewing', 'item--previewing');
_previewEl = null; _previewEl = null;
} }
// Catch-up render after preview is dismissed — apply any data missed while paused
if (_currentSessions) {
renderGrid(_currentSessions);
renderSidebar(_currentSessions, _viewingSession);
}
} }
// ─── Notification permission ──────────────────────────────────────────────── // ─── Notification permission ────────────────────────────────────────────────
+18
View File
@@ -2091,4 +2091,22 @@ test('hover preview delay is 1500ms (not 350ms)', () => {
assert.ok(!source.includes(', 350)'), 'old 350ms delay must be removed'); 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');
});