mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 03:35:09 +02:00
fix(extension): xterm fills the full Terminal panel height
The Terminal pane only rendered into the top portion of the panel — most of the panel below the prompt was an empty black gap. Three layered issues, all about xterm.js measuring dimensions during a layout state that wasn't ready yet: 1. order-of-operations in connect(): ensureXterm() ran BEFORE setState(LIVE), so term.open() measured els.mount while it was still display:none. xterm caches a 0-size viewport synchronously inside open() and never auto-recovers when the container goes visible. Flipped: setState(LIVE) → ensureXterm. 2. first fit() ran synchronously before the browser had applied the .active class transition. Wrapped in requestAnimationFrame so layout has settled before fit() reads clientHeight. 3. CSS flex-overflow trap: .terminal-mount has flex:1 inside the flex-column #tab-terminal, but .tab-content's `overflow-y: auto` and the lack of `min-height: 0` on .terminal-mount meant the item couldn't shrink below content size. flex:1 then refused to expand into available space and xterm rendered into whatever its initial 2x2 measurement happened to be. Fixes: - extension/sidepanel-terminal.js: reorder + RAF fit - extension/sidepanel.css: .terminal-mount gets `flex: 1 1 0` + `min-height: 0` + `position: relative`. #tab-terminal overrides .tab-content's `overflow-y: auto` to `overflow: hidden` (xterm has its own viewport scroll; the parent shouldn't compete) and explicitly re-declares `display: flex; flex-direction: column` for #tab-terminal.active. bun test browse/test/sidebar-tabs.test.ts → 27/27 pass. Manually verified: side panel opens → Terminal fills full panel height, xterm scrollback works, debug-tab toggle still repaints correctly.
This commit is contained in:
@@ -166,8 +166,25 @@
|
||||
fitAddon = new FitAddonModule.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
}
|
||||
// CRITICAL: caller must make els.mount visible BEFORE invoking
|
||||
// ensureXterm. xterm.js measures the container synchronously inside
|
||||
// term.open() — if the mount is display:none, xterm caches a 0-size
|
||||
// viewport and never auto-grows even after the container goes
|
||||
// visible. The visible-first pattern is enforced by connect()
|
||||
// calling setState(STATE.LIVE) before us.
|
||||
term.open(els.mount);
|
||||
fitAddon && fitAddon.fit();
|
||||
// First fit waits for the next paint frame so the browser has
|
||||
// applied the .active class transition. Otherwise term.cols/rows
|
||||
// can come back as the minimum (2x2) when the mount's clientHeight
|
||||
// is still being computed.
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
fitAddon && fitAddon.fit();
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
||||
const ro = new ResizeObserver(() => {
|
||||
try {
|
||||
@@ -224,9 +241,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
ensureXterm();
|
||||
// setState(LIVE) flips terminal-mount from display:none to display:flex.
|
||||
// We MUST do that BEFORE ensureXterm() — xterm.js measures the container
|
||||
// synchronously inside term.open() and a hidden container yields a 0x0
|
||||
// terminal that never recovers. ensureXterm + the requestAnimationFrame
|
||||
// fit() inside it run after the browser has applied the layout.
|
||||
setState(STATE.LIVE);
|
||||
fitAddon && fitAddon.fit();
|
||||
ensureXterm();
|
||||
|
||||
// Token rides on Sec-WebSocket-Protocol — the only auth header the
|
||||
// browser WebSocket API lets us set. Cross-port HttpOnly cookies with
|
||||
|
||||
+21
-1
@@ -676,9 +676,19 @@ body::after {
|
||||
.tab-content.active { display: flex; flex-direction: column; }
|
||||
|
||||
/* ─── Terminal Tab ────────────────────────────────────────────── */
|
||||
/* The Terminal pane manages its own scrolling (xterm has a viewport with
|
||||
scrollback). The default .tab-content rules above set overflow-y: auto,
|
||||
which collapses min-height for nested flex children — that's why
|
||||
.terminal-mount couldn't grow to fill available space. Override here. */
|
||||
#tab-terminal {
|
||||
background: #0a0a0a;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
#tab-terminal.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.terminal-toolbar {
|
||||
display: flex;
|
||||
@@ -746,11 +756,21 @@ body::after {
|
||||
}
|
||||
.install-retry-btn:hover { opacity: 0.9; }
|
||||
.terminal-mount {
|
||||
flex: 1;
|
||||
/* min-height: 0 is the standard flex-overflow fix — without it, a flex
|
||||
item with overflowing content can't shrink below its content size,
|
||||
so flex:1 refuses to expand into available space and xterm renders
|
||||
into whatever the content happens to be (i.e. its own initial 2x2
|
||||
measurement). With min-height:0 the item respects the flex parent's
|
||||
remaining space and xterm grows to fill it. */
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
background: #0a0a0a;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
/* position: relative so xterm's absolutely-positioned helpers (the
|
||||
hidden textarea for input) anchor inside us, not on body. */
|
||||
position: relative;
|
||||
}
|
||||
.terminal-mount .xterm,
|
||||
.terminal-mount .xterm .xterm-viewport,
|
||||
|
||||
Reference in New Issue
Block a user