fix: mobile viewport cutoff (100dvh) + fit view content anchoring (flex-end)

Mobile: 100vh includes browser chrome on mobile — bottom row clipped.
Fix: 100dvh with 100vh fallback (progressive enhancement) in .view
and .session-tile--expanded.

Fit view: scrollTop hack reset every 2s by poll cycle and had no effect
when content didn't overflow. Fix: replace position:absolute+scrollTop
with flex+justify-content:flex-end on tile-body. Content naturally
anchors to bottom without JS. No scrollbar hiding needed.

Tests: remove test_fit_view_pre_has_top_zero (old behavior), add
test_view_uses_dvh_fallback, test_fit_view_tile_body_uses_flex_end,
test_fit_view_pre_is_static. Update JS test for new flex approach.
This commit is contained in:
Brian Krabach
2026-03-31 05:54:41 -07:00
parent 0bb66b1801
commit b282e42ae8
4 changed files with 88 additions and 26 deletions
+1 -4
View File
@@ -656,10 +656,7 @@ function renderGrid(sessions) {
grid.classList.add('session-grid--fit');
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;
});
// No scrollTop hack needed — CSS flex + justify-content:flex-end anchors content to bottom
});
}
+14 -10
View File
@@ -83,7 +83,8 @@ body {
============================================================ */
.view {
height: 100vh;
height: 100vh; /* fallback for browsers without dvh support */
height: 100dvh; /* dynamic viewport height — adjusts for mobile browser chrome */
overflow: hidden;
}
@@ -593,7 +594,8 @@ body {
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
height: 100vh !important; /* fallback for browsers without dvh support */
height: 100dvh !important; /* dynamic viewport height — adjusts for mobile browser chrome */
border-radius: 0;
}
@@ -1380,16 +1382,18 @@ body {
overflow: hidden;
}
/* 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: scroll; /* enable scrollTop positioning (content anchored via JS scrollTop=scrollHeight) */
scrollbar-width: none; /* Firefox: hide scrollbar */
-ms-overflow-style: none; /* IE/Edge: hide scrollbar */
/* In fit mode, tile body uses flex to anchor content at the bottom (like a real terminal) */
.session-grid--fit .tile-body {
display: flex;
flex-direction: column;
justify-content: flex-end; /* content anchored to bottom */
}
.session-grid--fit .tile-body pre::-webkit-scrollbar {
display: none; /* Chrome/Safari: hide scrollbar */
/* In fit mode, pre participates in flex layout (not absolute-positioned) */
.session-grid--fit .tile-body pre {
position: static; /* remove absolute positioning — flex layout controls position */
max-height: 100%; /* don't overflow the tile body */
overflow: hidden; /* clip excess content at the top */
}
/* ============================================================
+10 -3
View File
@@ -2234,10 +2234,17 @@ test('buildTileHTML shows up to 80 lines in fit mode', () => {
);
});
test('CSS style.css has scrollbar-width none for fit mode pre to hide scrollbar', () => {
test('CSS style.css uses flex layout (not scrollbar hack) for fit mode content anchoring', () => {
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
// New approach: flex + justify-content:flex-end anchors content to bottom without JS scrollTop hacks
assert.ok(
source.includes('scrollbar-width: none'),
'style.css must have scrollbar-width: none for hidden scrollbar in fit mode pre'
source.includes('.session-grid--fit .tile-body'),
'style.css must have .session-grid--fit .tile-body rule for flex layout'
);
assert.ok(
source.includes('justify-content: flex-end') || source.includes('justify-content:flex-end'),
'style.css must use justify-content: flex-end to anchor fit mode content at bottom'
);
// Old scrollbar-hiding hack is no longer needed with flex approach
// (scrollbar-width: none was only needed when overflow-y:scroll was used for scrollTop positioning)
});