From c82e628e6fd47dc6c21d3681367e4284d1afc333 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 20:24:41 +0200 Subject: [PATCH] fix: send stty resize on every resize with hidden command output Backend: - Remove _stty_sent guard to send stty on EVERY resize - Use stty -echo to hide command, then delete the command line with ANSI escapes - Change log level from info to debug Frontend: - Add window resize listener as fallback to ResizeObserver - 250ms debounce to avoid excessive refits - Proper cleanup on unmount --- apps/api/src/services/terminal_session.py | 14 ++++++++------ apps/web/src/components/terminal.tsx | 12 ++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/api/src/services/terminal_session.py b/apps/api/src/services/terminal_session.py index 396f9d1..db26918 100644 --- a/apps/api/src/services/terminal_session.py +++ b/apps/api/src/services/terminal_session.py @@ -154,12 +154,14 @@ class TerminalSession: # Docker exec doesn't forward PTY resize to the container process, # so we need to explicitly set the size inside the container shell. - # Only do this on the first resize to avoid interfering with user input. - if not getattr(self, '_stty_sent', False): - self._stty_sent = True - stty_cmd = f"stty cols {cols} rows {rows}\n".encode() - await self.write_input(stty_cmd) - logger.info(f"Sent stty command to container for session {self.session_id}: {cols}x{rows}") + # Send on every resize so the container shell always matches the frontend. + # Use stty -echo to prevent the command from being visible, then clear the line. + stty_cmd = ( + f"stty -echo; stty cols {cols} rows {rows}; stty echo\n" + f"\x1b[A\x1b[M" # Move up 1 line and delete it (clears the stty command) + ).encode() + await self.write_input(stty_cmd) + logger.debug(f"Sent stty resize to container for session {self.session_id}: {cols}x{rows}") async def reset(self) -> None: """Reset the session by killing the process and clearing state.""" diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 68434e4..dc441aa 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -284,6 +284,16 @@ export const TerminalComponent: React.FC = ({ }); resizeObserver.observe(container); + // Window resize fallback (for viewport changes that don't affect container dimensions) + let windowResizeTimeout: ReturnType; + const handleWindowResize = () => { + clearTimeout(windowResizeTimeout); + windowResizeTimeout = setTimeout(() => { + requestAnimationFrame(() => fitTerminal()); + }, 250); + }; + window.addEventListener("resize", handleWindowResize); + // Refit after mobile header auto-hides (3s delay + 0.3s transition) const headerHideTimeout = setTimeout(() => { fitTerminal(); @@ -316,8 +326,10 @@ export const TerminalComponent: React.FC = ({ return () => { clearTimeout(resizeTimeout); + clearTimeout(windowResizeTimeout); clearTimeout(headerHideTimeout); resizeObserver.disconnect(); + window.removeEventListener("resize", handleWindowResize); document.removeEventListener("visibilitychange", handleVisibilityChange); if (ws) { ws.close();