debug: add logging and simplify fit logic
- Simplified fit logic: just fit after open, after fonts load, and on resize - Added console logging to debug what FitAddon calculates - Single fitTerminal() function used everywhere - Removed complex retry logic that wasn't working
This commit is contained in:
@@ -201,74 +201,35 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
term.loadAddon(fitAddon);
|
term.loadAddon(fitAddon);
|
||||||
term.loadAddon(new WebLinksAddon());
|
term.loadAddon(new WebLinksAddon());
|
||||||
|
|
||||||
// Wait for container to have non-zero dimensions before opening xterm
|
|
||||||
// xterm docs: "parent must be visible (have dimensions) when open is called"
|
|
||||||
const container = terminalRef.current;
|
const container = terminalRef.current;
|
||||||
let ws: WebSocket;
|
let ws: WebSocket;
|
||||||
|
|
||||||
const initTerminal = () => {
|
// Open xterm immediately
|
||||||
term.open(container);
|
term.open(container);
|
||||||
|
ws = connectWebSocket();
|
||||||
|
|
||||||
// Connect WebSocket
|
// Fit terminal and notify backend
|
||||||
ws = connectWebSocket();
|
const fitTerminal = () => {
|
||||||
|
if (!fitAddonRef.current || !termRef.current) return;
|
||||||
// Robust fitting: wait for fonts and stable dimensions
|
fitAddonRef.current.fit();
|
||||||
const performFit = () => {
|
const { cols, rows } = termRef.current;
|
||||||
fitAddon.fit();
|
console.log(`[Terminal] Fitted: ${cols}x${rows}, container: ${container.clientWidth}x${container.clientHeight}`);
|
||||||
const { cols, rows } = term;
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
ws.send(JSON.stringify({ type: "resize", cols, rows }));
|
||||||
ws.send(
|
}
|
||||||
JSON.stringify({
|
|
||||||
type: "resize",
|
|
||||||
cols,
|
|
||||||
rows,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return { cols, rows };
|
|
||||||
};
|
|
||||||
|
|
||||||
const ensureProperFit = (attempt = 0) => {
|
|
||||||
const { cols, rows } = performFit();
|
|
||||||
// If dimensions are unreasonably small, retry (layout still settling)
|
|
||||||
if ((rows <= 1 || cols <= 10) && attempt < 30) {
|
|
||||||
setTimeout(() => ensureProperFit(attempt + 1), 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wait for fonts to load, then fit with retry logic
|
|
||||||
document.fonts.ready.then(() => {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
ensureProperFit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fit after mobile header auto-hides (3s delay + 0.3s transition)
|
|
||||||
setTimeout(() => {
|
|
||||||
performFit();
|
|
||||||
}, 4000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if container already has dimensions
|
// Initial fit after layout settles
|
||||||
const rect = container.getBoundingClientRect();
|
requestAnimationFrame(() => {
|
||||||
if (rect.width > 0 && rect.height > 0) {
|
requestAnimationFrame(() => {
|
||||||
initTerminal();
|
fitTerminal();
|
||||||
} else {
|
|
||||||
// Wait for container to be laid out
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
|
||||||
for (const entry of entries) {
|
|
||||||
const { width, height } = entry.contentRect;
|
|
||||||
if (width > 0 && height > 0) {
|
|
||||||
resizeObserver.disconnect();
|
|
||||||
initTerminal();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
resizeObserver.observe(container);
|
});
|
||||||
}
|
|
||||||
|
// Refit after font load (metrics may change)
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
requestAnimationFrame(() => fitTerminal());
|
||||||
|
});
|
||||||
|
|
||||||
// Handle terminal input
|
// Handle terminal input
|
||||||
term.onData((data) => {
|
term.onData((data) => {
|
||||||
@@ -288,27 +249,22 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
ws.send(data);
|
ws.send(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle resize with debounce
|
// Handle window resize with debounce
|
||||||
let resizeTimeout: ReturnType<typeof setTimeout>;
|
let resizeTimeout: ReturnType<typeof setTimeout>;
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
clearTimeout(resizeTimeout);
|
clearTimeout(resizeTimeout);
|
||||||
resizeTimeout = setTimeout(() => {
|
resizeTimeout = setTimeout(() => {
|
||||||
fitAddon.fit();
|
fitTerminal();
|
||||||
const { cols, rows } = term;
|
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "resize",
|
|
||||||
cols,
|
|
||||||
rows,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 250);
|
}, 250);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
// Refit after mobile header auto-hides (3s delay + 0.3s transition)
|
||||||
|
const headerHideTimeout = setTimeout(() => {
|
||||||
|
fitTerminal();
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
// Notify parent about terminal readiness
|
// Notify parent about terminal readiness
|
||||||
if (onTerminalReadyRef.current) {
|
if (onTerminalReadyRef.current) {
|
||||||
const sendData = (data: string) => {
|
const sendData = (data: string) => {
|
||||||
@@ -336,6 +292,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(resizeTimeout);
|
clearTimeout(resizeTimeout);
|
||||||
|
clearTimeout(headerHideTimeout);
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
if (ws) {
|
if (ws) {
|
||||||
|
|||||||
Reference in New Issue
Block a user