fix: fit view — defer layout measurement to rAF, stretch pre to fill tile

Bug 1: applyFitLayout measured parentElement.clientHeight before the
browser reflowed the DOM — returned 0 on first render. Fix: wrap in
requestAnimationFrame so measurement happens after layout pass.
Applies to all three call sites: renderGrid(), applyDisplaySettings(),
and the window resize handler.

Bug 2: .tile-body pre had position:absolute bottom:0 but no top:0.
In fit mode (taller tiles), content sat at the bottom with a gap above.
Fix: .session-grid--fit .tile-body pre { top: 0; overflow-y: auto }
This commit is contained in:
Brian Krabach
2026-03-31 03:19:37 -07:00
parent fe5337840d
commit ebec7c3625
4 changed files with 40 additions and 3 deletions
+3 -3
View File
@@ -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); });
}
});
+6
View File
@@ -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
============================================================ */
+8
View File
@@ -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'
);
});
+23
View File
@@ -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"
)