mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +00:00
feat: improve terminal creation and resizing logic
- Added a debouncing mechanism for terminal creation to prevent rapid requests. - Enhanced terminal resizing with rate limiting and suppression of output during resize to avoid duplicates. - Updated scrollback handling to clear pending output when establishing new WebSocket connections. - Improved stability of terminal fitting logic by ensuring dimensions are stable before fitting.
This commit is contained in:
@@ -21,6 +21,9 @@ const MIN_FONT_SIZE = 8;
|
||||
const MAX_FONT_SIZE = 32;
|
||||
const DEFAULT_FONT_SIZE = 14;
|
||||
|
||||
// Resize constraints
|
||||
const RESIZE_DEBOUNCE_MS = 100; // Short debounce for responsive feel
|
||||
|
||||
interface TerminalPanelProps {
|
||||
sessionId: string;
|
||||
authToken: string | null;
|
||||
@@ -169,20 +172,41 @@ export function TerminalPanel({
|
||||
console.warn("[Terminal] WebGL addon not available, falling back to canvas");
|
||||
}
|
||||
|
||||
// Fit terminal to container using requestAnimationFrame for better timing
|
||||
requestAnimationFrame(() => {
|
||||
if (fitAddon && terminalRef.current) {
|
||||
const rect = terminalRef.current.getBoundingClientRect();
|
||||
// Only fit if container has valid dimensions
|
||||
if (rect.width >= 100 && rect.height >= 50) {
|
||||
try {
|
||||
fitAddon.fit();
|
||||
} catch (err) {
|
||||
console.error("[Terminal] Initial fit error:", err);
|
||||
}
|
||||
// Fit terminal to container - wait for stable dimensions
|
||||
// Use multiple RAFs to let react-resizable-panels finish layout
|
||||
let fitAttempts = 0;
|
||||
const MAX_FIT_ATTEMPTS = 5;
|
||||
let lastWidth = 0;
|
||||
let lastHeight = 0;
|
||||
|
||||
const attemptFit = () => {
|
||||
if (!fitAddon || !terminalRef.current || fitAttempts >= MAX_FIT_ATTEMPTS) return;
|
||||
|
||||
const rect = terminalRef.current.getBoundingClientRect();
|
||||
fitAttempts++;
|
||||
|
||||
// Check if dimensions are stable (same as last attempt) and valid
|
||||
if (
|
||||
rect.width === lastWidth &&
|
||||
rect.height === lastHeight &&
|
||||
rect.width > 0 &&
|
||||
rect.height > 0
|
||||
) {
|
||||
try {
|
||||
fitAddon.fit();
|
||||
} catch (err) {
|
||||
console.error("[Terminal] Initial fit error:", err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Dimensions still changing or too small, try again
|
||||
lastWidth = rect.width;
|
||||
lastHeight = rect.height;
|
||||
requestAnimationFrame(attemptFit);
|
||||
};
|
||||
|
||||
requestAnimationFrame(attemptFit);
|
||||
|
||||
xtermRef.current = terminal;
|
||||
fitAddonRef.current = fitAddon;
|
||||
@@ -299,10 +323,11 @@ export function TerminalPanel({
|
||||
terminal.write(msg.data);
|
||||
break;
|
||||
case "scrollback":
|
||||
// Clear terminal before replaying scrollback to prevent duplicates on reconnection
|
||||
terminal.clear();
|
||||
// Replay scrollback buffer (previous terminal output)
|
||||
if (msg.data) {
|
||||
// Only process scrollback if there's actual data
|
||||
// Don't clear if empty - prevents blank terminal issue
|
||||
if (msg.data && msg.data.length > 0) {
|
||||
// Use reset() which is more reliable than clear() or escape sequences
|
||||
terminal.reset();
|
||||
terminal.write(msg.data);
|
||||
}
|
||||
break;
|
||||
@@ -371,7 +396,7 @@ export function TerminalPanel({
|
||||
};
|
||||
}, [sessionId, authToken, wsUrl, isTerminalReady]);
|
||||
|
||||
// Handle resize with debouncing and validation
|
||||
// Handle resize with debouncing
|
||||
const handleResize = useCallback(() => {
|
||||
// Clear any pending resize
|
||||
if (resizeDebounceRef.current) {
|
||||
@@ -382,14 +407,11 @@ export function TerminalPanel({
|
||||
resizeDebounceRef.current = setTimeout(() => {
|
||||
if (!fitAddonRef.current || !xtermRef.current || !terminalRef.current) return;
|
||||
|
||||
// Validate minimum dimensions before resizing
|
||||
const container = terminalRef.current;
|
||||
const rect = container.getBoundingClientRect();
|
||||
const MIN_WIDTH = 100;
|
||||
const MIN_HEIGHT = 50;
|
||||
|
||||
if (rect.width < MIN_WIDTH || rect.height < MIN_HEIGHT) {
|
||||
console.log("[Terminal] Container too small to resize:", rect.width, "x", rect.height);
|
||||
// Only skip if container has no size at all
|
||||
if (rect.width <= 0 || rect.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -404,7 +426,7 @@ export function TerminalPanel({
|
||||
} catch (err) {
|
||||
console.error("[Terminal] Resize error:", err);
|
||||
}
|
||||
}, 100); // 100ms debounce
|
||||
}, RESIZE_DEBOUNCE_MS);
|
||||
}, []);
|
||||
|
||||
// Resize observer
|
||||
@@ -439,12 +461,16 @@ export function TerminalPanel({
|
||||
if (xtermRef.current && isTerminalReady) {
|
||||
xtermRef.current.options.fontSize = fontSize;
|
||||
// Refit after font size change
|
||||
if (fitAddonRef.current) {
|
||||
fitAddonRef.current.fit();
|
||||
// Notify server of new dimensions
|
||||
const { cols, rows } = xtermRef.current;
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({ type: "resize", cols, rows }));
|
||||
if (fitAddonRef.current && terminalRef.current) {
|
||||
const rect = terminalRef.current.getBoundingClientRect();
|
||||
// Only fit if container has any size
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
fitAddonRef.current.fit();
|
||||
// Notify server of new dimensions
|
||||
const { cols, rows } = xtermRef.current;
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({ type: "resize", cols, rows }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user