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 <vibe@mistral.ai>
This commit is contained in:
GitHub Copilot
2026-01-28 00:35:21 +00:00
parent fae80d308a
commit 35aa0c0968
3 changed files with 73 additions and 14 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual-webterm" name = "textual-webterm"
version = "0.3.28" version = "0.3.29"
description = "Serve terminal sessions over the web" description = "Serve terminal sessions over the web"
authors = ["Will McGugan <will@textualize.io>"] authors = ["Will McGugan <will@textualize.io>"]
license = "MIT" license = "MIT"
+26 -3
View File
@@ -14551,8 +14551,12 @@ class WebTerminal {
this.resizeState.isResizing = true; this.resizeState.isResizing = true;
this.resizeState.resizeAttempts++; this.resizeState.resizeAttempts++;
this.lastResizeTime = now; this.lastResizeTime = now;
if (this.fitAddon && typeof this.fitAddon.fit === "function") {
this.fitAddon.fit(); this.fitAddon.fit();
this.resizeState.resizeAttempts = 0; this.resizeState.resizeAttempts = 0;
} else {
throw new Error("FitAddon not properly initialized");
}
} catch (e) { } catch (e) {
console.warn("Fit failed:", e); console.warn("Fit failed:", e);
this.handleResizeFailure(); this.handleResizeFailure();
@@ -14576,21 +14580,40 @@ class WebTerminal {
} }
isTerminalReady() { isTerminalReady() {
try { try {
if (!this.terminal || !this.terminal._core) { if (!this.terminal) {
return false;
}
if (!this.terminal._core) {
return false; return false;
} }
const core = this.terminal._core; 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; return false;
} }
const renderService = core._renderService; const renderService = core._renderService;
if (!renderService || !renderService.dimensions) { if (!renderService.dimensions) {
return false; return false;
} }
const dims = renderService.dimensions; const dims = renderService.dimensions;
if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { if (dims.css.cell.width === 0 || dims.css.cell.height === 0) {
return false; return false;
} }
if (this.fitAddon) {
try {
const fitTerminal = this.fitAddon._terminal;
if (!fitTerminal || fitTerminal !== this.terminal) {
return false;
}
} catch (e) {
return false;
}
}
return true; return true;
} catch (e) { } catch (e) {
console.warn("Terminal readiness check failed:", e); console.warn("Terminal readiness check failed:", e);
+42 -6
View File
@@ -209,8 +209,13 @@ class WebTerminal {
this.resizeState.resizeAttempts++; this.resizeState.resizeAttempts++;
this.lastResizeTime = now; this.lastResizeTime = now;
// Wrap FitAddon operation in additional safety check
if (this.fitAddon && typeof this.fitAddon.fit === 'function') {
this.fitAddon.fit(); this.fitAddon.fit();
this.resizeState.resizeAttempts = 0; // Reset on success this.resizeState.resizeAttempts = 0; // Reset on success
} else {
throw new Error("FitAddon not properly initialized");
}
} catch (e) { } catch (e) {
console.warn("Fit failed:", e); console.warn("Fit failed:", e);
@@ -244,28 +249,59 @@ class WebTerminal {
/** Check if terminal is ready for resize operations */ /** Check if terminal is ready for resize operations */
private isTerminalReady(): boolean { private isTerminalReady(): boolean {
try { try {
// Check if terminal and its core components are initialized // Check if terminal exists
if (!this.terminal || !this.terminal._core) { if (!this.terminal) {
return false; 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) // Check if viewport is available (FitAddon requirement)
const core = this.terminal._core; if (!core.viewport) {
if (!core.viewport || !core.viewport.scrollBarWidth) {
return false; 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; const renderService = core._renderService;
if (!renderService || !renderService.dimensions) { if (!renderService.dimensions) {
return false; return false;
} }
// Check if cell dimensions are valid
const dims = renderService.dimensions; const dims = renderService.dimensions;
if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { if (dims.css.cell.width === 0 || dims.css.cell.height === 0) {
return false; 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; return true;
} catch (e) { } catch (e) {
console.warn("Terminal readiness check failed:", e); console.warn("Terminal readiness check failed:", e);