fix: fit view — clear stale layout on recalc, revert to position:absolute bottom:0
Bug 1: applyFitLayout now clears grid-template-rows and tile heights before recalculating. Prevents stale layout from empty-grid calls on page reload from interfering with subsequent calculations. Bug 2: Removed flex justify-content:flex-end approach — didn't work because pre filled 100% of parent (flex-end had no effect). Reverted to base CSS position:absolute + bottom:0 which anchors content to the bottom. With 80 lines in fit mode, content always overflows the tile and excess is clipped at the top by tile-body overflow:hidden.
This commit is contained in:
@@ -1103,6 +1103,13 @@ function saveDisplaySettings(settings) {
|
|||||||
* @param {Element} grid - The session grid element
|
* @param {Element} grid - The session grid element
|
||||||
*/
|
*/
|
||||||
function applyFitLayout(grid) {
|
function applyFitLayout(grid) {
|
||||||
|
// Clear stale layout from previous calls (prevents interference on page reload and
|
||||||
|
// when returning from a session where the grid was display:none during measurement)
|
||||||
|
grid.style.removeProperty('grid-template-rows');
|
||||||
|
grid.querySelectorAll('.session-tile').forEach(function(t) {
|
||||||
|
t.style.removeProperty('height');
|
||||||
|
});
|
||||||
|
|
||||||
var count = grid.querySelectorAll('.session-tile').length;
|
var count = grid.querySelectorAll('.session-tile').length;
|
||||||
if (count === 0) return;
|
if (count === 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1382,19 +1382,12 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* In fit mode, tile body uses flex to anchor content at the bottom (like a real terminal) */
|
/* In fit mode, tile body and pre use the base CSS:
|
||||||
.session-grid--fit .tile-body {
|
.tile-body { position: relative; overflow: hidden; }
|
||||||
display: flex;
|
.tile-body pre { position: absolute; bottom: 0; left: 0; right: 0; }
|
||||||
flex-direction: column;
|
The pre's natural height from 80 lines of content exceeds the tile height, so it
|
||||||
justify-content: flex-end; /* content anchored to bottom */
|
overflows from bottom:0 upward, and tile-body clips excess at the top — showing
|
||||||
}
|
the bottom-most content (the prompt area) just like a real terminal. */
|
||||||
|
|
||||||
/* 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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Responsive overlay sidebar at <960px
|
Responsive overlay sidebar at <960px
|
||||||
|
|||||||
@@ -2234,17 +2234,70 @@ test('buildTileHTML shows up to 80 lines in fit mode', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('CSS style.css uses flex layout (not scrollbar hack) for fit mode content anchoring', () => {
|
test('CSS style.css uses base position:absolute bottom:0 for fit mode content anchoring (no flex override)', () => {
|
||||||
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
|
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
|
// Reverted approach: base CSS position:absolute + bottom:0 anchors content to bottom.
|
||||||
|
// The flex + justify-content:flex-end approach failed because <pre> filled 100% of parent,
|
||||||
|
// making flex-end a no-op (content started at top, excess clipped at bottom).
|
||||||
|
// The fit-mode tile-body flex override must be REMOVED.
|
||||||
assert.ok(
|
assert.ok(
|
||||||
source.includes('.session-grid--fit .tile-body'),
|
!source.includes('.session-grid--fit .tile-body {'),
|
||||||
'style.css must have .session-grid--fit .tile-body rule for flex layout'
|
'style.css must NOT have .session-grid--fit .tile-body flex override — base position:absolute handles anchoring'
|
||||||
);
|
);
|
||||||
|
// The pre static-positioning override must also be removed
|
||||||
assert.ok(
|
assert.ok(
|
||||||
source.includes('justify-content: flex-end') || source.includes('justify-content:flex-end'),
|
!source.includes('.session-grid--fit .tile-body pre {'),
|
||||||
'style.css must use justify-content: flex-end to anchor fit mode content at bottom'
|
'style.css must NOT have .session-grid--fit .tile-body pre override — base position:absolute + bottom:0 is correct'
|
||||||
|
);
|
||||||
|
// Base .tile-body pre must still use position:absolute + bottom:0
|
||||||
|
assert.ok(
|
||||||
|
source.includes('position: absolute') && source.includes('bottom: 0'),
|
||||||
|
'base .tile-body pre must retain position:absolute and bottom:0 for content anchoring'
|
||||||
);
|
);
|
||||||
// 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)
|
|
||||||
|
// --- Fit view Bug 1: applyFitLayout clears stale layout before recalculating ---
|
||||||
|
|
||||||
|
test('applyFitLayout clears stale grid-template-rows and tile heights before recalculating', () => {
|
||||||
|
const removedGridProps = [];
|
||||||
|
const removedTileProps = [];
|
||||||
|
|
||||||
|
const mockTile = {
|
||||||
|
style: { removeProperty: (prop) => removedTileProps.push(prop) },
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockGrid = {
|
||||||
|
style: {
|
||||||
|
removeProperty: (prop) => removedGridProps.push(prop),
|
||||||
|
gridTemplateColumns: '',
|
||||||
|
gridTemplateRows: 'repeat(2, 1fr)', // stale value from previous call
|
||||||
|
},
|
||||||
|
querySelectorAll: (sel) => {
|
||||||
|
if (sel === '.session-tile') return [mockTile];
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
clientWidth: 800,
|
||||||
|
parentElement: { clientHeight: 600 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const origGetComputedStyle = globalThis.getComputedStyle;
|
||||||
|
globalThis.getComputedStyle = () => ({
|
||||||
|
paddingTop: '16px', paddingBottom: '16px',
|
||||||
|
paddingLeft: '16px', paddingRight: '16px',
|
||||||
|
gap: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
app.applyFitLayout(mockGrid);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
removedGridProps.includes('grid-template-rows'),
|
||||||
|
'applyFitLayout must removeProperty("grid-template-rows") before recalculating to prevent stale layout interference'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
removedTileProps.includes('height'),
|
||||||
|
'applyFitLayout must removeProperty("height") from each tile before recalculating'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (origGetComputedStyle) globalThis.getComputedStyle = origGetComputedStyle;
|
||||||
|
else delete globalThis.getComputedStyle;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1744,41 +1744,35 @@ def test_view_uses_dvh_fallback() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_fit_view_tile_body_uses_flex_end() -> None:
|
def test_fit_view_no_tile_body_flex_override() -> None:
|
||||||
"""Fit mode tile-body must use flex + justify-content: flex-end for bottom anchoring.
|
"""Bug fix: .session-grid--fit .tile-body must NOT have a flex override.
|
||||||
|
|
||||||
Bug: .tile-body pre had position:absolute + scrollTop=scrollHeight hack.
|
The flex + justify-content:flex-end approach failed because the <pre> with
|
||||||
The scrollTop hack resets every 2s when innerHTML is rebuilt by the poll cycle,
|
max-height:100% fills the parent entirely, making flex-end a no-op. Content
|
||||||
and has no effect when content doesn't overflow.
|
started at the top and excess was clipped at the bottom — the opposite of what
|
||||||
Fix: make tile-body a flex container with justify-content:flex-end so the pre
|
we want.
|
||||||
naturally anchors to the bottom without JS.
|
|
||||||
|
Fix: delete the .session-grid--fit .tile-body rule entirely. The base CSS
|
||||||
|
position:absolute + bottom:0 on the <pre> anchors content to the bottom.
|
||||||
"""
|
"""
|
||||||
css = read_css()
|
css = read_css()
|
||||||
assert ".session-grid--fit .tile-body" in css, (
|
assert ".session-grid--fit .tile-body {" not in css, (
|
||||||
"Missing .session-grid--fit .tile-body rule — needed for flex-end bottom anchoring"
|
".session-grid--fit .tile-body must be removed — flex-end approach does not work "
|
||||||
)
|
"when <pre> fills 100% of the parent. Use base position:absolute + bottom:0."
|
||||||
block = _extract_rule_block(css, ".session-grid--fit .tile-body {")
|
|
||||||
assert "display: flex" in block or "display:flex" in block, (
|
|
||||||
".session-grid--fit .tile-body must use display: flex"
|
|
||||||
)
|
|
||||||
assert "justify-content: flex-end" in block or "justify-content:flex-end" in block, (
|
|
||||||
".session-grid--fit .tile-body must use justify-content: flex-end to anchor content to bottom"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_fit_view_pre_is_static() -> None:
|
def test_fit_view_no_pre_static_override() -> None:
|
||||||
"""Fit mode pre must use position:static (not absolute) for flex layout.
|
"""Bug fix: .session-grid--fit .tile-body pre must NOT override position to static.
|
||||||
|
|
||||||
Bug: .tile-body pre uses position:absolute which takes it out of flex flow.
|
The position:static override removed the pre from absolute positioning, breaking
|
||||||
In fit mode, we need position:static so flex layout controls the pre's position.
|
the bottom anchoring. Base CSS already has position:absolute + bottom:0 which
|
||||||
The parent .tile-body is a flex column with justify-content:flex-end, so the
|
anchors content to the bottom of the tile.
|
||||||
pre will naturally sit at the bottom.
|
|
||||||
|
Fix: delete the .session-grid--fit .tile-body pre rule entirely.
|
||||||
"""
|
"""
|
||||||
css = read_css()
|
css = read_css()
|
||||||
assert ".session-grid--fit .tile-body pre" in css, (
|
assert ".session-grid--fit .tile-body pre {" not in css, (
|
||||||
"Missing .session-grid--fit .tile-body pre rule"
|
".session-grid--fit .tile-body pre override must be removed — revert to base "
|
||||||
)
|
"position:absolute + bottom:0 for correct bottom anchoring."
|
||||||
block = _extract_rule_block(css, ".session-grid--fit .tile-body pre {")
|
|
||||||
assert "position: static" in block or "position:static" in block, (
|
|
||||||
".session-grid--fit .tile-body pre must have position: static to participate in flex layout"
|
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user