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