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