fix(terminal): prevent hidden WebGL overlay bleed

This commit is contained in:
Jinwoo Hong
2026-07-10 15:56:52 -04:00
parent 33256e557d
commit 2200d10e28
3 changed files with 52 additions and 6 deletions
@@ -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',
@@ -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'
})
})
})
@@ -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'
}
}