diff --git a/src/textual_webterm/static/js/terminal.js b/src/textual_webterm/static/js/terminal.js index 4e3762b..63e1935 100644 --- a/src/textual_webterm/static/js/terminal.js +++ b/src/textual_webterm/static/js/terminal.js @@ -14543,6 +14543,10 @@ class WebTerminal { if (this.resizeState.isResizing) { return; } + if (!this.isTerminalReady()) { + console.debug("Terminal not ready for fit operation, skipping"); + return; + } try { this.resizeState.isResizing = true; this.resizeState.resizeAttempts++; @@ -14570,6 +14574,29 @@ class WebTerminal { this.resizeState.resizeAttempts = 0; } } + isTerminalReady() { + try { + if (!this.terminal || !this.terminal._core) { + return false; + } + const core = this.terminal._core; + if (!core.viewport || !core.viewport.scrollBarWidth) { + return false; + } + const renderService = core._renderService; + if (!renderService || !renderService.dimensions) { + return false; + } + const dims = renderService.dimensions; + if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { + return false; + } + return true; + } catch (e) { + console.warn("Terminal readiness check failed:", e); + return false; + } + } isValidSize(cols, rows) { return cols >= 10 && cols <= 500 && rows >= 5 && rows <= 200; } @@ -14615,12 +14642,13 @@ class WebTerminal { this.send(["resize", { width: fallback.cols, height: fallback.rows }]); return; } - if (this.isValidSize(dims.cols, dims.rows)) { + if (dims && this.isValidSize(dims.cols, dims.rows) && this.isTerminalReady()) { this.terminal.resize(dims.cols, dims.rows); this.resizeState.lastValidSize = dims; this.send(["resize", { width: dims.cols, height: dims.rows }]); } else { - console.warn(`Initial fit produced invalid dimensions: ${dims.cols}x${dims.rows}, using fallback`); + const reason = !dims ? "proposeDimensions failed" : !this.isValidSize(dims.cols, dims.rows) ? `invalid dimensions: ${dims.cols}x${dims.rows}` : "terminal not ready"; + console.warn(`Initial fit ${reason}, using fallback`); this.terminal.resize(fallback.cols, fallback.rows); this.resizeState.lastValidSize = fallback; this.send(["resize", { width: fallback.cols, height: fallback.rows }]); diff --git a/src/textual_webterm/static/js/terminal.ts b/src/textual_webterm/static/js/terminal.ts index e23b2ea..3d0bc72 100644 --- a/src/textual_webterm/static/js/terminal.ts +++ b/src/textual_webterm/static/js/terminal.ts @@ -198,6 +198,12 @@ class WebTerminal { return; } + // Check if terminal is ready before attempting fit + if (!this.isTerminalReady()) { + console.debug("Terminal not ready for fit operation, skipping"); + return; + } + try { this.resizeState.isResizing = true; this.resizeState.resizeAttempts++; @@ -235,6 +241,38 @@ class WebTerminal { } } + /** Check if terminal is ready for resize operations */ + private isTerminalReady(): boolean { + try { + // Check if terminal and its core components are initialized + if (!this.terminal || !this.terminal._core) { + return false; + } + + // Check if viewport is available (FitAddon requirement) + const core = this.terminal._core; + if (!core.viewport || !core.viewport.scrollBarWidth) { + return false; + } + + // Check if render service has valid dimensions + const renderService = core._renderService; + if (!renderService || !renderService.dimensions) { + return false; + } + + const dims = renderService.dimensions; + if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { + return false; + } + + return true; + } catch (e) { + console.warn("Terminal readiness check failed:", e); + return false; + } + } + /** Validate terminal dimensions */ private isValidSize(cols: number, rows: number): boolean { return cols >= 10 && cols <= 500 && rows >= 5 && rows <= 200; @@ -298,13 +336,16 @@ class WebTerminal { return; } - // Validate dimensions before applying - if (this.isValidSize(dims.cols, dims.rows)) { + // Validate dimensions and terminal readiness before applying + if (dims && this.isValidSize(dims.cols, dims.rows) && this.isTerminalReady()) { this.terminal.resize(dims.cols, dims.rows); this.resizeState.lastValidSize = dims; this.send(["resize", { width: dims.cols, height: dims.rows }]); } else { - console.warn(`Initial fit produced invalid dimensions: ${dims.cols}x${dims.rows}, using fallback`); + const reason = !dims ? "proposeDimensions failed" : + !this.isValidSize(dims.cols, dims.rows) ? `invalid dimensions: ${dims.cols}x${dims.rows}` : + "terminal not ready"; + console.warn(`Initial fit ${reason}, using fallback`); this.terminal.resize(fallback.cols, fallback.rows); this.resizeState.lastValidSize = fallback; this.send(["resize", { width: fallback.cols, height: fallback.rows }]);