fix: fit view — reapply layout on session close, show more lines, bottom-anchor content

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.
This commit is contained in:
Brian Krabach
2026-03-31 03:41:28 -07:00
parent ebec7c3625
commit 0bb66b1801
3 changed files with 59 additions and 6 deletions
+21 -3
View File
@@ -400,9 +400,11 @@ function buildTileHTML(session, index, mobile) {
bellHtml = `<span class="tile-bell">${countStr}</span>`; bellHtml = `<span class="tile-bell">${countStr}</span>`;
} }
// Last 20 lines of snapshot // Last N lines of snapshot — show more in fit mode so tall tiles fill
const snapshot = session.snapshot || ''; 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 ( return (
`<article class="${classes}" data-session="${escapedName}" tabindex="0" role="listitem" aria-label="${escapedName}">` + `<article class="${classes}" data-session="${escapedName}" tabindex="0" role="listitem" aria-label="${escapedName}">` +
@@ -652,7 +654,13 @@ function renderGrid(sessions) {
var currentMode = currentDs.viewMode || 'auto'; var currentMode = currentDs.viewMode || 'auto';
if (currentMode === 'fit' && grid) { if (currentMode === 'fit' && grid) {
grid.classList.add('session-grid--fit'); 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 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'); const pill = $('session-pill');
if (pill) pill.classList.add('hidden'); if (pill) pill.classList.add('hidden');
+9 -3
View File
@@ -1380,10 +1380,16 @@ body {
overflow: hidden; 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 { .session-grid--fit .tile-body pre {
top: 0; /* stretch to fill full tile body height in fit mode */ top: 0; /* stretch to fill full tile body height in fit mode */
overflow-y: auto; /* scroll if content exceeds the tile */ 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 */
} }
/* ============================================================ /* ============================================================
+29
View File
@@ -2212,3 +2212,32 @@ test('applyFitLayout is called via requestAnimationFrame for correct timing', ()
'applyFitLayout must be deferred via requestAnimationFrame' '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'
);
});