fix: terminal resize propagation and redraw

- Frontend: Add ResizeObserver with dimension tracking for accurate resize detection
- Frontend: Fix cleanup function to properly disconnect ResizeObserver
- Frontend: Use CSS grid for terminal wrapper layout
- Backend: Add duplicate dimension check to avoid unnecessary resizes
- Backend: Ensure stty command is sent correctly to container shell
This commit is contained in:
Fusion
2026-05-24 17:44:00 +02:00
parent e7a89a853f
commit 4de312c170
3 changed files with 32 additions and 19 deletions
+18 -7
View File
@@ -260,18 +260,29 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
ws.send(data);
});
// Handle window resize with debounce - use rAF to ensure layout settled
// Handle container resize with ResizeObserver for accurate dimension tracking
let resizeTimeout: ReturnType<typeof setTimeout>;
const handleResize = () => {
let lastWidth = 0;
let lastHeight = 0;
const resizeObserver = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) return;
const { width, height } = entry.contentRect;
// Only trigger if dimensions actually changed
if (width === lastWidth && height === lastHeight) return;
lastWidth = width;
lastHeight = height;
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
requestAnimationFrame(() => {
if (!container.isConnected) return;
fitTerminal();
});
}, 100);
};
window.addEventListener("resize", handleResize);
}, 50);
});
resizeObserver.observe(container);
// Refit after mobile header auto-hides (3s delay + 0.3s transition)
const headerHideTimeout = setTimeout(() => {
@@ -306,7 +317,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
return () => {
clearTimeout(resizeTimeout);
clearTimeout(headerHideTimeout);
window.removeEventListener("resize", handleResize);
resizeObserver.disconnect();
document.removeEventListener("visibilitychange", handleVisibilityChange);
if (ws) {
ws.close();
+9 -12
View File
@@ -2506,8 +2506,8 @@ a.nav-item,
}
.terminal-wrapper {
display: flex;
flex-direction: column;
display: grid;
grid-template-rows: auto 1fr;
flex: 1;
min-height: 0;
border: 1px solid var(--border);
@@ -2592,22 +2592,19 @@ a.nav-item,
}
.terminal-container {
flex: 1;
min-height: 0;
padding: var(--space-2);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
padding: var(--space-2);
overflow: hidden;
}
.terminal-container .xterm {
flex: 1;
min-height: 0;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.terminal-container canvas {