From 4c12882cc70c0a0da477532be201cc7e9ac335be Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Thu, 2 Jul 2026 04:02:18 -0700 Subject: [PATCH] fix(agents): terminal fills the panel on maximize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mount xterm as an absolute inset-0 fill so it always has an explicit box and fit() measures the real size when the dock expands (was collapsing via the flex/percentage height chain). - Acquire the pty slave as the child's controlling terminal (setsid + TIOCSCTTY) so the kernel delivers SIGWINCH on a browser resize. Previously the initial size was read once but live resizes were dropped, so the pane stayed at its startup size — content never grew with the window. --- app/src/components/AgentTerminal.tsx | 17 +++++++++++------ castle-api/src/castle_api/pty_session.py | 18 +++++++++++++++++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/src/components/AgentTerminal.tsx b/app/src/components/AgentTerminal.tsx index 10318bd..87916c2 100644 --- a/app/src/components/AgentTerminal.tsx +++ b/app/src/components/AgentTerminal.tsx @@ -219,13 +219,18 @@ export function AgentTerminal({ )} + {/* Sized region + an absolutely-filled xterm mount. The absolute inset-0 + child always has an explicit box equal to its parent, so `fit()` never + measures a stale/percentage-collapsed height — the terminal tracks the + panel as it expands. */}
+ className={cn("relative", maximized || fill ? "flex-1 min-h-0" : "h-[560px]")} + > +
+
) } diff --git a/castle-api/src/castle_api/pty_session.py b/castle-api/src/castle_api/pty_session.py index 172bf41..a69a432 100644 --- a/castle-api/src/castle_api/pty_session.py +++ b/castle-api/src/castle_api/pty_session.py @@ -32,6 +32,20 @@ def _set_winsize(fd: int, rows: int, cols: int) -> None: fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize) +def _acquire_controlling_tty() -> None: + """Child-side setup (preexec), run after the pty slave is dup'd to fd 0/1/2. + + ``setsid()`` starts a new session (own process group, so cleanup can + ``killpg`` the whole tree). ``TIOCSCTTY`` then makes the slave our + *controlling terminal* — without it the kernel has no foreground process + group to deliver ``SIGWINCH`` to, so a later winsize change (a browser resize) + is silently dropped and the app never reflows. This is what makes live resize + work, not just the initial size. + """ + os.setsid() + fcntl.ioctl(0, termios.TIOCSCTTY, 0) + + @dataclass class PtySession: """A child process attached to a pty master. @@ -77,7 +91,9 @@ class PtySession: stderr=slave_fd, cwd=str(cwd) if cwd else None, env=proc_env, - start_new_session=True, # own session/group → controlling tty + killpg + # setsid + TIOCSCTTY in the child: own session/group (for killpg) AND + # the slave as controlling tty (for SIGWINCH resize delivery). + preexec_fn=_acquire_controlling_tty, close_fds=True, ) os.close(slave_fd) # parent keeps only the master