From bf76affef257b2e69d02d6ca564b5258eaad4a9d Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sun, 29 Mar 2026 18:44:55 -0700 Subject: [PATCH] refactor: remove dimmer overlay, add cyan border to preview popover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ANSI colors make the overlay content readable without dimming. Removed preview-dimmer, liftHoveredTile(), tile--previewing/item--previewing — all dead code now. Added var(--accent) 2px border to .preview-popover for visual separation. --- muxplex/frontend/app.js | 38 +---------------------------- muxplex/frontend/style.css | 14 +---------- muxplex/frontend/tests/test_app.mjs | 26 ++++---------------- muxplex/tests/test_frontend_css.py | 9 ++++--- 4 files changed, 13 insertions(+), 74 deletions(-) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 53baf34..b883084 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -127,7 +127,7 @@ let _notificationPermission = 'default'; let _pollFailCount = 0; let _previewPopover = null; let _previewTimer = null; -var _previewDimmer = null; + var _previewSessionName = null; // track by NAME, not DOM element // ─── DOM helpers ────────────────────────────────────────────────────────────── @@ -465,8 +465,6 @@ function renderSidebar(sessions, currentSession) { }); } - // Re-apply z-index lift after innerHTML rebuild (preview survives re-renders) - if (_previewSessionName) liftHoveredTile(); } const SIDEBAR_KEY = 'muxplex.sidebarOpen'; @@ -606,8 +604,6 @@ function renderGrid(sessions) { updatePillBell(); } - // Re-apply z-index lift after innerHTML rebuild (preview survives re-renders) - if (_previewSessionName) liftHoveredTile(); } // --------------------------------------------------------------------------- @@ -638,12 +634,6 @@ function showPreview(name) { hidePreviewDOM(); _previewSessionName = name; - // Dimmer - var dimmer = document.createElement('div'); - dimmer.className = 'preview-dimmer'; - document.body.appendChild(dimmer); - _previewDimmer = dimmer; - // Full-window overlay var popover = document.createElement('div'); popover.className = 'preview-popover'; @@ -653,9 +643,6 @@ function showPreview(name) { document.body.appendChild(popover); _previewPopover = popover; - // Lift the original tile above dimmer - liftHoveredTile(); - // Auto-scroll to bottom (prompt area) popover.scrollTop = popover.scrollHeight; @@ -663,22 +650,6 @@ function showPreview(name) { document.addEventListener('click', _previewClickHandler, true); } -function liftHoveredTile() { - // Remove any existing lift class from all tiles/items - document.querySelectorAll('.tile--previewing, .item--previewing').forEach(function (el) { - el.classList.remove('tile--previewing', 'item--previewing'); - }); - if (!_previewSessionName) return; - var el = document.querySelector( - '.session-tile[data-session="' + _previewSessionName + '"]' - ); - if (el) el.classList.add('tile--previewing'); - var item = document.querySelector( - '.sidebar-item[data-session="' + _previewSessionName + '"]' - ); - if (item) item.classList.add('item--previewing'); -} - // hidePreviewDOM: removes the visual elements only (no render trigger) function hidePreviewDOM() { document.removeEventListener('click', _previewClickHandler, true); @@ -686,13 +657,6 @@ function hidePreviewDOM() { _previewPopover.remove(); _previewPopover = null; } - if (_previewDimmer) { - _previewDimmer.remove(); - _previewDimmer = null; - } - document.querySelectorAll('.tile--previewing, .item--previewing').forEach(function (el) { - el.classList.remove('tile--previewing', 'item--previewing'); - }); } // hidePreview: full cleanup including timer and session name diff --git a/muxplex/frontend/style.css b/muxplex/frontend/style.css index 837bb81..1b31f6b 100644 --- a/muxplex/frontend/style.css +++ b/muxplex/frontend/style.css @@ -967,7 +967,7 @@ body { position: fixed; z-index: 500; background: var(--bg-secondary); - border: 1px solid var(--border); + border: 2px solid var(--accent); border-radius: 8px; padding: 16px 20px; /* Cover most of the viewport — centered with margin */ @@ -991,19 +991,7 @@ body { overflow-x: hidden; } -.preview-dimmer { - position: fixed; - inset: 0; - z-index: 499; /* behind popover (500), above everything else */ - background: rgba(0, 0, 0, 0.55); - pointer-events: none; /* clicks pass through — don't block mouseleave */ -} -.session-tile.tile--previewing, -.sidebar-item.item--previewing { - position: relative; - z-index: 500; /* same as popover — above dimmer */ -} /* ============================================================ Responsive overlay sidebar at <960px diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index a473af0..e6dcf54 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2075,13 +2075,12 @@ test('hover preview popover works for both grid tiles and sidebar items', () => 'must handle sidebar items too'); }); -test('hover preview popover has dim overlay and highlights hovered element', () => { +test('hover preview popover uses cyan border and no dimmer', () => { const source = fs.readFileSync( new URL('../app.js', import.meta.url), 'utf8' ); - assert.ok(source.includes('preview-dimmer'), 'must create dimmer overlay'); - assert.ok(source.includes('tile--previewing'), 'must lift grid tile above dimmer'); - assert.ok(source.includes('item--previewing'), 'must lift sidebar item above dimmer'); + assert.ok(source.includes('preview-popover'), 'must create popover'); + assert.ok(!source.includes('preview-dimmer'), 'must NOT use dimmer overlay (removed)'); }); test('hover preview delay is 1500ms (not 350ms)', () => { @@ -2092,31 +2091,16 @@ test('hover preview delay is 1500ms (not 350ms)', () => { assert.ok(!source.includes(', 350)'), 'old 350ms delay must be removed'); }); -test('hover preview uses full-window overlay with text wrapping', () => { +test('hover preview uses full-window overlay with click-to-navigate', () => { const source = fs.readFileSync( new URL('../app.js', import.meta.url), 'utf8' ); assert.ok(source.includes('_previewSessionName'), 'must track by session name'); - assert.ok(source.includes('liftHoveredTile'), 'must re-lift tile after renders'); assert.ok(source.includes('scrollHeight'), 'must auto-scroll to bottom'); - assert.ok(source.includes('preview-dimmer'), 'must have dim overlay'); assert.ok(source.includes('ontouchstart'), 'must be desktop-only'); assert.ok(source.includes('_previewClickHandler'), 'must have click-to-navigate handler'); - // Must NOT have repositionPreview (old side-positioned approach) assert.ok(!source.includes('repositionPreview'), 'must NOT have repositionPreview (old approach)'); -}); - -test('renderGrid and renderSidebar re-lift hovered tile after innerHTML rebuild', () => { - const source = fs.readFileSync( - new URL('../app.js', import.meta.url), 'utf8' - ); - // renderGrid should call liftHoveredTile after innerHTML - const gridFn = source.substring(source.indexOf('function renderGrid'), source.indexOf('function renderGrid') + 1500); - assert.ok(gridFn.includes('liftHoveredTile'), 'renderGrid must re-lift tile after render'); - - // renderGrid should NOT have the old _previewPopover guard - assert.ok(!gridFn.includes('if (_previewPopover) return'), - 'renderGrid must NOT skip renders while preview active (old approach caused bugs)'); + assert.ok(!source.includes('preview-dimmer'), 'must NOT have dimmer (removed — ANSI colors are readable without it)'); }); test('ansiToHtml converts SGR codes to styled spans', () => { diff --git a/muxplex/tests/test_frontend_css.py b/muxplex/tests/test_frontend_css.py index 4e60904..c22ef07 100644 --- a/muxplex/tests/test_frontend_css.py +++ b/muxplex/tests/test_frontend_css.py @@ -633,7 +633,10 @@ def test_preview_popover_css_exists(): ) -def test_preview_dimmer_css_exists(): - """Dim overlay behind popover must have CSS rules.""" +def test_preview_popover_has_accent_border(): + """Preview popover must use brand cyan border.""" css = read_css() - assert ".preview-dimmer" in css + start = css.index(".preview-popover {") + end = css.index("}", start) + block = css[start:end] + assert "var(--accent)" in block, ".preview-popover must use var(--accent) border"