From 0bb66b1801fe8bf2da3a4d560e67df68235ffaa6 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 31 Mar 2026 03:41:28 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20fit=20view=20=E2=80=94=20reapply=20layou?= =?UTF-8?q?t=20on=20session=20close,=20show=20more=20lines,=20bottom-ancho?= =?UTF-8?q?r=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: closeSession() didn't call applyFitLayout when returning to dashboard — tiles kept old heights from before the overview was hidden. Fix: add rAF call after overview.style.display = '' so the layout is remeasured once the overview becomes visible again. Bug 2: Only 20 lines shown in tiles — not enough for tall fit-mode tiles, and content sat at the top. Fix: - buildTileHTML shows 80 lines in fit mode (vs 20 in auto) - renderGrid rAF sets pre.scrollTop = pre.scrollHeight after applyFitLayout so content anchors at the bottom (like a real terminal). Hidden scrollbar via scrollbar-width:none + ::-webkit. Tests: 3 new source-scan tests added and verified RED before GREEN. --- muxplex/frontend/app.js | 24 +++++++++++++++++++++--- muxplex/frontend/style.css | 12 +++++++++--- muxplex/frontend/tests/test_app.mjs | 29 +++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 56bc36a..70c8fe5 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -400,9 +400,11 @@ function buildTileHTML(session, index, mobile) { bellHtml = `${countStr}`; } - // Last 20 lines of snapshot + // Last N lines of snapshot — show more in fit mode so tall tiles fill const snapshot = session.snapshot || ''; - const lastLines = snapshot.split('\n').slice(-20).join('\n'); + var _tileDs = loadDisplaySettings(); + var _lineCount = (_tileDs.viewMode === 'fit') ? -80 : -20; + const lastLines = snapshot.split('\n').slice(_lineCount).join('\n'); return ( `
` + @@ -652,7 +654,13 @@ function renderGrid(sessions) { var currentMode = currentDs.viewMode || 'auto'; if (currentMode === 'fit' && grid) { grid.classList.add('session-grid--fit'); - requestAnimationFrame(function() { applyFitLayout(grid); }); + requestAnimationFrame(function() { + applyFitLayout(grid); + // Scroll each tile's pre to the bottom so content anchors at the bottom (like a real terminal) + grid.querySelectorAll('.tile-body pre').forEach(function(pre) { + pre.scrollTop = pre.scrollHeight; + }); + }); } } @@ -1003,6 +1011,16 @@ function closeSession() { } if (overview) overview.style.display = ''; // overview uses view--active (no !important), style.display clears fine + // Reapply fit layout after overview becomes visible again + var _closDs = loadDisplaySettings(); + if ((_closDs.viewMode || 'auto') === 'fit') { + var _closGrid = document.getElementById('session-grid'); + if (_closGrid) { + _closGrid.classList.add('session-grid--fit'); + requestAnimationFrame(function() { applyFitLayout(_closGrid); }); + } + } + const pill = $('session-pill'); if (pill) pill.classList.add('hidden'); diff --git a/muxplex/frontend/style.css b/muxplex/frontend/style.css index 5a59824..cccbe07 100644 --- a/muxplex/frontend/style.css +++ b/muxplex/frontend/style.css @@ -1380,10 +1380,16 @@ body { overflow: hidden; } -/* In fit mode, stretch pre to fill the full tile body height (not just bottom-anchor) */ +/* In fit mode, stretch pre to fill the full tile body height and anchor content to bottom */ .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 */ + top: 0; /* stretch to fill full tile body height in fit mode */ + overflow-y: scroll; /* enable scrollTop positioning (content anchored via JS scrollTop=scrollHeight) */ + scrollbar-width: none; /* Firefox: hide scrollbar */ + -ms-overflow-style: none; /* IE/Edge: hide scrollbar */ +} + +.session-grid--fit .tile-body pre::-webkit-scrollbar { + display: none; /* Chrome/Safari: hide scrollbar */ } /* ============================================================ diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index 955a963..34d6650 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2212,3 +2212,32 @@ test('applyFitLayout is called via requestAnimationFrame for correct timing', () 'applyFitLayout must be deferred via requestAnimationFrame' ); }); + +// --- Fit view bug fixes: closeSession reapply, more lines, bottom-anchor --- + +test('closeSession reapplies fit layout when returning to dashboard', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + const fnStart = source.indexOf('function closeSession'); + assert.ok(fnStart !== -1, 'closeSession function must exist'); + const fnBody = source.substring(fnStart, fnStart + 1500); + assert.ok( + fnBody.includes('applyFitLayout'), + 'closeSession must call applyFitLayout for fit mode when returning to dashboard' + ); +}); + +test('buildTileHTML shows up to 80 lines in fit mode', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + assert.ok( + source.includes('-80') || source.includes('(-80)'), + 'app.js must use -80 slice for fit mode to show up to 80 lines' + ); +}); + +test('CSS style.css has scrollbar-width none for fit mode pre to hide scrollbar', () => { + const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8'); + assert.ok( + source.includes('scrollbar-width: none'), + 'style.css must have scrollbar-width: none for hidden scrollbar in fit mode pre' + ); +});