Add robust fallback mechanism with timeout-based initialization

- Wrapped initial fit logic in comprehensive try-catch with multiple fallback strategies
- Added timeout-based fallback (2 seconds) to ensure terminal always gets initialized
- Enhanced error handling to prevent blank terminal on initialization failure
- Added cleanup of fallback timeout when WebSocket connects successfully
- Maintains all existing functionality and improves reliability

Bump version to 0.3.30

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
GitHub Copilot
2026-01-28 00:37:11 +00:00
parent 35aa0c0968
commit a3b0d46fa8
3 changed files with 69 additions and 17 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual-webterm" name = "textual-webterm"
version = "0.3.29" version = "0.3.30"
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"
+30 -7
View File
@@ -14665,13 +14665,20 @@ class WebTerminal {
this.send(["resize", { width: fallback.cols, height: fallback.rows }]); this.send(["resize", { width: fallback.cols, height: fallback.rows }]);
return; return;
} }
if (dims && this.isValidSize(dims.cols, dims.rows) && this.isTerminalReady()) { try {
this.terminal.resize(dims.cols, dims.rows); if (dims && this.isValidSize(dims.cols, dims.rows) && this.isTerminalReady()) {
this.resizeState.lastValidSize = dims; this.terminal.resize(dims.cols, dims.rows);
this.send(["resize", { width: dims.cols, height: dims.rows }]); this.resizeState.lastValidSize = dims;
} else { this.send(["resize", { width: dims.cols, height: dims.rows }]);
const reason = !dims ? "proposeDimensions failed" : !this.isValidSize(dims.cols, dims.rows) ? `invalid dimensions: ${dims.cols}x${dims.rows}` : "terminal not ready"; } else {
console.warn(`Initial fit ${reason}, 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 }]);
}
} catch (e) {
console.warn(`Initial fit failed with exception: ${e.message}, using fallback`);
this.terminal.resize(fallback.cols, fallback.rows); this.terminal.resize(fallback.cols, fallback.rows);
this.resizeState.lastValidSize = fallback; this.resizeState.lastValidSize = fallback;
this.send(["resize", { width: fallback.cols, height: fallback.rows }]); this.send(["resize", { width: fallback.cols, height: fallback.rows }]);
@@ -14684,6 +14691,22 @@ class WebTerminal {
} else { } else {
init(); init();
} }
const fallbackTimeout = setTimeout(() => {
if (!this.resizeState.lastValidSize) {
console.warn("Initial fit timed out, applying fallback dimensions");
const fallback = { cols: 80, rows: 24 };
try {
this.terminal.resize(fallback.cols, fallback.rows);
this.resizeState.lastValidSize = fallback;
this.send(["resize", { width: fallback.cols, height: fallback.rows }]);
} catch (e) {
console.error("Fallback resize failed:", e);
}
}
}, 2000);
this.socket?.addEventListener("open", () => {
clearTimeout(fallbackTimeout);
});
this.terminal.focus(); this.terminal.focus();
}); });
this.socket.addEventListener("close", () => { this.socket.addEventListener("close", () => {
+38 -9
View File
@@ -373,15 +373,24 @@ class WebTerminal {
} }
// Validate dimensions and terminal readiness before applying // Validate dimensions and terminal readiness before applying
if (dims && this.isValidSize(dims.cols, dims.rows) && this.isTerminalReady()) { // Use a defensive approach with multiple fallback strategies
this.terminal.resize(dims.cols, dims.rows); try {
this.resizeState.lastValidSize = dims; if (dims && this.isValidSize(dims.cols, dims.rows) && this.isTerminalReady()) {
this.send(["resize", { width: dims.cols, height: dims.rows }]); this.terminal.resize(dims.cols, dims.rows);
} else { this.resizeState.lastValidSize = dims;
const reason = !dims ? "proposeDimensions failed" : this.send(["resize", { width: dims.cols, height: dims.rows }]);
!this.isValidSize(dims.cols, dims.rows) ? `invalid dimensions: ${dims.cols}x${dims.rows}` : } else {
"terminal not ready"; const reason = !dims ? "proposeDimensions failed" :
console.warn(`Initial fit ${reason}, using fallback`); !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 }]);
}
} catch (e) {
console.warn(`Initial fit failed with exception: ${e.message}, using fallback`);
// If anything goes wrong, use the fallback dimensions
this.terminal.resize(fallback.cols, fallback.rows); this.terminal.resize(fallback.cols, fallback.rows);
this.resizeState.lastValidSize = fallback; this.resizeState.lastValidSize = fallback;
this.send(["resize", { width: fallback.cols, height: fallback.rows }]); this.send(["resize", { width: fallback.cols, height: fallback.rows }]);
@@ -397,6 +406,26 @@ class WebTerminal {
init(); init();
} }
// Add a timeout-based fallback in case the initial fit never succeeds
const fallbackTimeout = setTimeout(() => {
if (!this.resizeState.lastValidSize) {
console.warn("Initial fit timed out, applying fallback dimensions");
const fallback = { cols: 80, rows: 24 };
try {
this.terminal.resize(fallback.cols, fallback.rows);
this.resizeState.lastValidSize = fallback;
this.send(["resize", { width: fallback.cols, height: fallback.rows }]);
} catch (e) {
console.error("Fallback resize failed:", e);
}
}
}, 2000);
// Clean up timeout when WebSocket connects
this.socket?.addEventListener('open', () => {
clearTimeout(fallbackTimeout);
});
// Focus terminal // Focus terminal
this.terminal.focus(); this.terminal.focus();
}); });