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
This commit is contained in:
Fusion
2026-05-24 20:24:41 +02:00
parent 4b09f611d6
commit c82e628e6f
2 changed files with 20 additions and 6 deletions
+8 -6
View File
@@ -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."""
+12
View File
@@ -284,6 +284,16 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
});
resizeObserver.observe(container);
// Window resize fallback (for viewport changes that don't affect container dimensions)
let windowResizeTimeout: ReturnType<typeof setTimeout>;
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<TerminalProps> = ({
return () => {
clearTimeout(resizeTimeout);
clearTimeout(windowResizeTimeout);
clearTimeout(headerHideTimeout);
resizeObserver.disconnect();
window.removeEventListener("resize", handleWindowResize);
document.removeEventListener("visibilitychange", handleVisibilityChange);
if (ws) {
ws.close();