From ebec7c3625ff004b9f921e912723da68ddc50c30 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 31 Mar 2026 03:19:37 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20fit=20view=20=E2=80=94=20defer=20layout?= =?UTF-8?q?=20measurement=20to=20rAF,=20stretch=20pre=20to=20fill=20tile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: applyFitLayout measured parentElement.clientHeight before the browser reflowed the DOM — returned 0 on first render. Fix: wrap in requestAnimationFrame so measurement happens after layout pass. Applies to all three call sites: renderGrid(), applyDisplaySettings(), and the window resize handler. Bug 2: .tile-body pre had position:absolute bottom:0 but no top:0. In fit mode (taller tiles), content sat at the bottom with a gap above. Fix: .session-grid--fit .tile-body pre { top: 0; overflow-y: auto } --- muxplex/frontend/app.js | 6 +++--- muxplex/frontend/style.css | 6 ++++++ muxplex/frontend/tests/test_app.mjs | 8 ++++++++ muxplex/tests/test_frontend_css.py | 23 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 901c24a..56bc36a 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -652,7 +652,7 @@ function renderGrid(sessions) { var currentMode = currentDs.viewMode || 'auto'; if (currentMode === 'fit' && grid) { grid.classList.add('session-grid--fit'); - applyFitLayout(grid); + requestAnimationFrame(function() { applyFitLayout(grid); }); } } @@ -1191,7 +1191,7 @@ function applyDisplaySettings(ds) { } else if (mode === 'fit') { grid.classList.add('session-grid--fit'); - applyFitLayout(grid); + requestAnimationFrame(function() { applyFitLayout(grid); }); } } @@ -1858,7 +1858,7 @@ window.addEventListener('resize', function() { var ds = loadDisplaySettings(); if ((ds.viewMode || 'auto') === 'fit') { var grid = document.getElementById('session-grid'); - if (grid) applyFitLayout(grid); + if (grid) requestAnimationFrame(function() { applyFitLayout(grid); }); } }); diff --git a/muxplex/frontend/style.css b/muxplex/frontend/style.css index d2638fc..5a59824 100644 --- a/muxplex/frontend/style.css +++ b/muxplex/frontend/style.css @@ -1380,6 +1380,12 @@ body { overflow: hidden; } +/* In fit mode, stretch pre to fill the full tile body height (not just bottom-anchor) */ +.session-grid--fit .tile-body pre { + top: 0; /* stretch to fill full tile body height in fit mode */ + overflow-y: auto; /* scroll if content exceeds the tile */ +} + /* ============================================================ Responsive overlay sidebar at <960px ============================================================ */ diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index 8024a53..955a963 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2204,3 +2204,11 @@ test('bindStaticEventListeners wires view-mode-btn click to cycleViewMode', () = 'bindStaticEventListeners must wire #view-mode-btn click handler' ); }); + +test('applyFitLayout is called via requestAnimationFrame for correct timing', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + assert.ok( + source.includes('requestAnimationFrame') && source.includes('applyFitLayout'), + 'applyFitLayout must be deferred via requestAnimationFrame' + ); +}); diff --git a/muxplex/tests/test_frontend_css.py b/muxplex/tests/test_frontend_css.py index 6b65dd7..94fd6c1 100644 --- a/muxplex/tests/test_frontend_css.py +++ b/muxplex/tests/test_frontend_css.py @@ -1705,3 +1705,26 @@ def test_css_no_compact_tile_height() -> None: assert ".session-grid--compact .session-tile" not in css, ( ".session-grid--compact .session-tile must be removed — compact view mode was removed" ) + + +# ============================================================ +# Fit view bug fixes +# ============================================================ + + +def test_fit_view_pre_has_top_zero() -> None: + """In fit mode, .tile-body pre must have top:0 to fill the full tile height. + + Bug: .tile-body pre uses position:absolute with bottom:0 but no top:0. + In fit mode where tiles are taller than auto mode, the pre is anchored + to the bottom but only takes natural content height, leaving a black gap above. + Fix: .session-grid--fit .tile-body pre { top: 0 } stretches it to fill the tile. + """ + css = read_css() + assert ".session-grid--fit .tile-body pre" in css, ( + "Missing .session-grid--fit .tile-body pre rule — needed to fix pre height in fit mode" + ) + block = _extract_rule_block(css, ".session-grid--fit .tile-body pre {") + assert "top: 0" in block or "top:0" in block, ( + ".session-grid--fit .tile-body pre must have top: 0 to fill full tile body height" + )