diff --git a/src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.tsx b/src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.tsx index ec3d65aaf74..9e16d169e93 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.tsx @@ -12,6 +12,7 @@ import { import TerminalPane from './TerminalPane' import { closeTerminalTab } from '../terminal/terminal-tab-actions' import { useNativeChatToggleShortcut } from '../native-chat/use-native-chat-toggle-shortcut' +import { getTerminalOverlayPaintStyle } from './terminal-overlay-paint-style' import { shouldDeferParkedPtyExitTabClose } from './terminal-parked-tab-watchers' import { useTerminalTabColdParking } from './use-terminal-tab-cold-parking' @@ -187,9 +188,7 @@ const TerminalOverlaySlot = memo(function TerminalOverlaySlot({ left: `anchor(${anchorName} left)`, width: `anchor-size(${anchorName} width)`, height: `anchor-size(${anchorName} height)`, - display: isVisible || shouldMeasureHiddenStartup ? 'flex' : 'none', - opacity: isVisible ? 1 : 0, - pointerEvents: isVisible ? 'auto' : 'none' + ...getTerminalOverlayPaintStyle(isVisible, shouldMeasureHiddenStartup) } : anchorName ? { @@ -201,9 +200,7 @@ const TerminalOverlaySlot = memo(function TerminalOverlaySlot({ left: measuredFallbackRect?.left ?? 0, width: measuredFallbackRect?.width ?? '100%', height: measuredFallbackRect?.height ?? 'calc(100% - 32px)', - display: isVisible || shouldMeasureHiddenStartup ? 'flex' : 'none', - opacity: isVisible ? 1 : 0, - pointerEvents: isVisible ? 'auto' : 'none' + ...getTerminalOverlayPaintStyle(isVisible, shouldMeasureHiddenStartup) } : { position: 'absolute', diff --git a/src/renderer/src/components/terminal-pane/terminal-overlay-paint-style.test.ts b/src/renderer/src/components/terminal-pane/terminal-overlay-paint-style.test.ts new file mode 100644 index 00000000000..fa9fdc46067 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-overlay-paint-style.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { getTerminalOverlayPaintStyle } from './terminal-overlay-paint-style' + +describe('getTerminalOverlayPaintStyle', () => { + it('keeps a hidden startup terminal measurable without letting it paint', () => { + expect(getTerminalOverlayPaintStyle(false, true)).toEqual({ + display: 'flex', + opacity: 0, + visibility: 'hidden', + pointerEvents: 'none' + }) + }) + + it('shows only the active terminal overlay', () => { + expect(getTerminalOverlayPaintStyle(true, false)).toEqual({ + display: 'flex', + opacity: 1, + visibility: 'visible', + pointerEvents: 'auto' + }) + expect(getTerminalOverlayPaintStyle(false, false)).toEqual({ + display: 'none', + opacity: 0, + visibility: 'hidden', + pointerEvents: 'none' + }) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/terminal-overlay-paint-style.ts b/src/renderer/src/components/terminal-pane/terminal-overlay-paint-style.ts new file mode 100644 index 00000000000..1cd501c3453 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-overlay-paint-style.ts @@ -0,0 +1,21 @@ +import type { CSSProperties } from 'react' + +type TerminalOverlayPaintStyle = Pick< + CSSProperties, + 'display' | 'opacity' | 'pointerEvents' | 'visibility' +> + +export function getTerminalOverlayPaintStyle( + isVisible: boolean, + shouldMeasureHiddenStartup: boolean +): TerminalOverlayPaintStyle { + const shouldLayout = isVisible || shouldMeasureHiddenStartup + + return { + display: shouldLayout ? 'flex' : 'none', + opacity: isVisible ? 1 : 0, + // Why: opacity alone can leak stale WebGL pixels from a measurable hidden xterm on Windows. + visibility: isVisible ? 'visible' : 'hidden', + pointerEvents: isVisible ? 'auto' : 'none' + } +}