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" + )