From 35aa0c0968b6d78af73bb664534831f194c3f178 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Wed, 28 Jan 2026 00:35:21 +0000 Subject: [PATCH] Enhance terminal readiness checks with comprehensive FitAddon validation - Added more robust terminal readiness checking - Explicitly check for viewport.scrollBarWidth being defined - Added FitAddon function type checking before calling fit() - Improved error handling for FitAddon initialization issues - Prevents 'undefined is not an object' errors during terminal setup - Maintains all existing functionality and test compatibility Bump version to 0.3.29 Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- pyproject.toml | 2 +- src/textual_webterm/static/js/terminal.js | 33 +++++++++++--- src/textual_webterm/static/js/terminal.ts | 52 +++++++++++++++++++---- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4cc9d9..95cb5f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual-webterm" -version = "0.3.28" +version = "0.3.29" description = "Serve terminal sessions over the web" authors = ["Will McGugan "] license = "MIT" diff --git a/src/textual_webterm/static/js/terminal.js b/src/textual_webterm/static/js/terminal.js index 63e1935..9d7db88 100644 --- a/src/textual_webterm/static/js/terminal.js +++ b/src/textual_webterm/static/js/terminal.js @@ -14551,8 +14551,12 @@ class WebTerminal { this.resizeState.isResizing = true; this.resizeState.resizeAttempts++; this.lastResizeTime = now; - this.fitAddon.fit(); - this.resizeState.resizeAttempts = 0; + if (this.fitAddon && typeof this.fitAddon.fit === "function") { + this.fitAddon.fit(); + this.resizeState.resizeAttempts = 0; + } else { + throw new Error("FitAddon not properly initialized"); + } } catch (e) { console.warn("Fit failed:", e); this.handleResizeFailure(); @@ -14576,21 +14580,40 @@ class WebTerminal { } isTerminalReady() { try { - if (!this.terminal || !this.terminal._core) { + if (!this.terminal) { + return false; + } + if (!this.terminal._core) { return false; } const core = this.terminal._core; - if (!core.viewport || !core.viewport.scrollBarWidth) { + if (!core.viewport) { + return false; + } + if (core.viewport.scrollBarWidth === undefined) { + return false; + } + if (!core._renderService) { return false; } const renderService = core._renderService; - if (!renderService || !renderService.dimensions) { + if (!renderService.dimensions) { return false; } const dims = renderService.dimensions; if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { return false; } + if (this.fitAddon) { + try { + const fitTerminal = this.fitAddon._terminal; + if (!fitTerminal || fitTerminal !== this.terminal) { + return false; + } + } catch (e) { + return false; + } + } return true; } catch (e) { console.warn("Terminal readiness check failed:", e); diff --git a/src/textual_webterm/static/js/terminal.ts b/src/textual_webterm/static/js/terminal.ts index 3d0bc72..9158a8e 100644 --- a/src/textual_webterm/static/js/terminal.ts +++ b/src/textual_webterm/static/js/terminal.ts @@ -209,8 +209,13 @@ class WebTerminal { this.resizeState.resizeAttempts++; this.lastResizeTime = now; - this.fitAddon.fit(); - this.resizeState.resizeAttempts = 0; // Reset on success + // Wrap FitAddon operation in additional safety check + if (this.fitAddon && typeof this.fitAddon.fit === 'function') { + this.fitAddon.fit(); + this.resizeState.resizeAttempts = 0; // Reset on success + } else { + throw new Error("FitAddon not properly initialized"); + } } catch (e) { console.warn("Fit failed:", e); @@ -244,28 +249,59 @@ 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) { + // Check if terminal exists + if (!this.terminal) { return false; } + // Check if terminal core is initialized + if (!this.terminal._core) { + return false; + } + + const core = this.terminal._core; + // Check if viewport is available (FitAddon requirement) - const core = this.terminal._core; - if (!core.viewport || !core.viewport.scrollBarWidth) { + if (!core.viewport) { return false; } - // Check if render service has valid dimensions + // Check if viewport has scrollBarWidth (this is what was failing) + if (core.viewport.scrollBarWidth === undefined) { + return false; + } + + // Check if render service exists + if (!core._renderService) { + return false; + } + + // Check if render service has dimensions const renderService = core._renderService; - if (!renderService || !renderService.dimensions) { + if (!renderService.dimensions) { return false; } + // Check if cell dimensions are valid const dims = renderService.dimensions; if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { return false; } + // Additional safety check for FitAddon internal state + if (this.fitAddon) { + try { + // Try to access FitAddon's terminal reference + const fitTerminal = (this.fitAddon as any)._terminal; + if (!fitTerminal || fitTerminal !== this.terminal) { + return false; + } + } catch (e) { + // FitAddon might not have the expected structure + return false; + } + } + return true; } catch (e) { console.warn("Terminal readiness check failed:", e);