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' + ); +});