From f728011b2a70756295d60207b5148607f5463811 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Fri, 29 May 2026 10:53:02 +0200 Subject: [PATCH] fix: prevent cumulative terminal shrinkage in fullscreen mode When switching terminal sessions in fullscreen mode, the viewport shrank cumulatively because .terminal-wrapper uses grid-template-rows: auto 1fr. With showControls=false, the single child (.terminal-container) landed in the auto track instead of 1fr, creating a feedback loop with xterm fit(). - Add .terminal-wrapper.no-controls with grid-template-rows: 1fr so the container fills the wrapper when the header is hidden. - Apply no-controls class in TerminalComponent when showControls=false. - Replace setTimeout(50) with double requestAnimationFrame in TerminalPage for more reliable fit() timing after tab switches. Quality gates: tsc --noEmit (clean), pytest (208 passed, 6 pre-existing) --- apps/web/src/components/terminal.tsx | 2 +- apps/web/src/pages/terminal.tsx | 35 ++++++++++++++----- apps/web/src/styles.css | 7 ++++ .../.openspec.yaml | 4 +-- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 5454b99..681a1e5 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -568,7 +568,7 @@ export const TerminalComponent = React.forwardRef( }; return ( -
+
{showControls && (
diff --git a/apps/web/src/pages/terminal.tsx b/apps/web/src/pages/terminal.tsx index 72e6e3d..4efa750 100644 --- a/apps/web/src/pages/terminal.tsx +++ b/apps/web/src/pages/terminal.tsx @@ -17,7 +17,12 @@ const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] => status: s.status as TerminalSessionInfo["status"], })); -type TerminalStatus = "connecting" | "connected" | "disconnected" | "error" | "resetting"; +type TerminalStatus = + | "connecting" + | "connected" + | "disconnected" + | "error" + | "resetting"; export const TerminalPage: React.FC = () => { const { instanceId } = useParams<{ @@ -30,7 +35,9 @@ export const TerminalPage: React.FC = () => { const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile }); // Track terminal status and callbacks for unified fullscreen header - const [terminalStatuses, setTerminalStatuses] = useState>({}); + const [terminalStatuses, setTerminalStatuses] = useState< + Record + >({}); const changeFontSizeRef = useRef<((delta: number) => void) | null>(null); const [showResetConfirm, setShowResetConfirm] = useState(false); @@ -73,12 +80,19 @@ export const TerminalPage: React.FC = () => { useEffect(() => { if (activeSessionId && terminalRefs.current[activeSessionId]) { const ref = terminalRefs.current[activeSessionId]; - // Small delay to allow display:block to apply - const timer = setTimeout(() => { - ref.current?.fit(); - ref.current?.focus(); - }, 50); - return () => clearTimeout(timer); + // Double rAF ensures layout has settled after the display:block switch + let raf1 = 0; + let raf2 = 0; + raf1 = requestAnimationFrame(() => { + raf2 = requestAnimationFrame(() => { + ref.current?.fit(); + ref.current?.focus(); + }); + }); + return () => { + cancelAnimationFrame(raf1); + cancelAnimationFrame(raf2); + }; } }, [activeSessionId]); @@ -192,7 +206,10 @@ export const TerminalPage: React.FC = () => { _focusInput: () => void, changeFontSize: (delta: number) => void, ) => { - setTerminalStatuses((prev) => ({ ...prev, [activeSessionId ?? "default"]: status })); + setTerminalStatuses((prev) => ({ + ...prev, + [activeSessionId ?? "default"]: status, + })); changeFontSizeRef.current = changeFontSize; }, [activeSessionId], diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 9b86b0b..0bfc895 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -2562,6 +2562,13 @@ a.nav-item, background: #1e1e1e; } +/* When controls are hidden (fullscreen), only the container is in flow; + force it into the 1fr track so it fills the wrapper instead of landing + in the auto track and shrinking on each fit(). */ +.terminal-wrapper.no-controls { + grid-template-rows: 1fr; +} + .terminal-header { display: flex; justify-content: space-between; diff --git a/openspec/changes/terminal-fullscreen-unified-header/.openspec.yaml b/openspec/changes/terminal-fullscreen-unified-header/.openspec.yaml index 314748b..311d4fa 100644 --- a/openspec/changes/terminal-fullscreen-unified-header/.openspec.yaml +++ b/openspec/changes/terminal-fullscreen-unified-header/.openspec.yaml @@ -1,6 +1,6 @@ name: terminal-fullscreen-unified-header -status: in-progress -phase: apply +status: completed +phase: verify type: bugfix description: Fix terminal fullscreen mode where session switcher overlays terminal controls. Combine both elements into a single unified header visible at all times. created_at: 2026-05-28