fix(terminal): clear transparent WebGL frames before redraw

This commit is contained in:
Neil
2026-07-11 02:37:54 -07:00
parent dcfa91919f
commit 18bf54ffcc
4 changed files with 90 additions and 9 deletions
File diff suppressed because one or more lines are too long
+9 -1
View File
@@ -644,9 +644,11 @@
"invariant": "Search, link, WebGL, decoration, input-protocol, or keyboard-navigation errors must not unmount the terminal surface, crash React/window, break focus, or stop PTY input/output.",
"oracle": "The current executable slice injects a core addon load throw and asserts pane opening continues through later addons, covers link provider throws as pane-scoped no-link results, covers search decoration positive-integer failures as false results instead of renderer crashes, and covers WebGL attach/refresh/recovery containment. Focused typed input echo after addon failure still needs a live/component follow-up.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts src/renderer/src/components/terminal-search-safe-find.test.ts src/renderer/src/lib/pane-manager/pane-webgl-refresh-lifecycle.test.ts src/renderer/src/components/terminal-pane/terminal-webgl-atlas-recovery.test.ts src/renderer/src/lib/pane-manager/pane-webgl-context-recovery.test.ts src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts src/renderer/src/lib/pane-manager/pane-reveal-repaint.test.ts src/renderer/src/components/terminal-pane/terminal-visibility-resume.test.ts"
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts src/renderer/src/components/terminal-search-safe-find.test.ts src/renderer/src/lib/pane-manager/pane-webgl-refresh-lifecycle.test.ts src/renderer/src/components/terminal-pane/terminal-webgl-atlas-recovery.test.ts src/renderer/src/lib/pane-manager/pane-webgl-context-recovery.test.ts src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts src/renderer/src/lib/pane-manager/pane-reveal-repaint.test.ts src/renderer/src/components/terminal-pane/terminal-visibility-resume.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts config/scripts/xterm-webgl-transparent-frame-patch.test.mjs src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts src/renderer/src/components/terminal-search-safe-find.test.ts src/renderer/src/lib/pane-manager/pane-webgl-refresh-lifecycle.test.ts src/renderer/src/components/terminal-pane/terminal-webgl-atlas-recovery.test.ts src/renderer/src/lib/pane-manager/pane-webgl-context-recovery.test.ts src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts src/renderer/src/lib/pane-manager/pane-reveal-repaint.test.ts src/renderer/src/components/terminal-pane/terminal-visibility-resume.test.ts"
],
"testFiles": [
"config/scripts/xterm-webgl-transparent-frame-patch.test.mjs",
"src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts",
"src/renderer/src/components/terminal-search-safe-find.test.ts",
"src/renderer/src/lib/pane-manager/pane-webgl-refresh-lifecycle.test.ts",
@@ -657,6 +659,12 @@
"src/renderer/src/components/terminal-pane/terminal-visibility-resume.test.ts"
],
"assertionRefs": [
{
"file": "config/scripts/xterm-webgl-transparent-frame-patch.test.mjs",
"assertions": [
"the installed xterm source plus CommonJS and ESM bundles clear exactly once at the transparent-only renderer boundary"
]
},
{
"file": "src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts",
"assertions": [
@@ -0,0 +1,54 @@
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const projectDir = resolve(import.meta.dirname, '../..')
const packageDir = join(projectDir, 'node_modules', '@xterm', 'addon-webgl')
const transparentClearSource = [
'if (this._terminal.options.allowTransparency) {',
" // Preserve xterm's RGB blend while avoiding SRC_ALPHA being applied to",
' // alpha itself, which would turn configured opacity `a` into `a²`.',
' this._gl.blendFuncSeparate(',
' this._gl.SRC_ALPHA,',
' this._gl.ONE_MINUS_SRC_ALPHA,',
' this._gl.ONE,',
' this._gl.ONE_MINUS_SRC_ALPHA',
' );',
' this._gl.clearColor(0, 0, 0, 0);',
' this._gl.clear(this._gl.COLOR_BUFFER_BIT);',
' }'
].join('\n')
const transparentClearBundle =
'this._terminal.options.allowTransparency&&(this._gl.blendFuncSeparate(this._gl.SRC_ALPHA,this._gl.ONE_MINUS_SRC_ALPHA,this._gl.ONE,this._gl.ONE_MINUS_SRC_ALPHA),this._gl.clearColor(0,0,0,0),this._gl.clear(this._gl.COLOR_BUFFER_BIT))'
describe('xterm transparent WebGL frame patch', () => {
it("applies the transparent-only clear at xterm's renderer boundary", () => {
const source = readFileSync(join(packageDir, 'src', 'WebglRenderer.ts'), 'utf8')
const clearIndex = source.indexOf(transparentClearSource)
const backgroundRenderIndex = source.indexOf(
'this._rectangleRenderer.value.renderBackgrounds()',
clearIndex
)
expect(clearIndex).toBeGreaterThan(-1)
expect(backgroundRenderIndex).toBeGreaterThan(clearIndex)
})
it.each(['addon-webgl.js', 'addon-webgl.mjs'])(
'keeps the generated %s runtime in parity with the source patch',
(bundleName) => {
const bundle = readFileSync(join(packageDir, 'lib', bundleName), 'utf8')
const occurrences = bundle.split(transparentClearBundle).length - 1
const clearIndex = bundle.indexOf(transparentClearBundle)
const backgroundRenderIndex = bundle.indexOf(
'this._rectangleRenderer.value.renderBackgrounds()',
clearIndex
)
// Why: exactly one inline gate means one opacity-correct clear per transparent
// xterm frame, zero for opaque frames, and no second lifecycle owner.
expect(occurrences).toBe(1)
expect(backgroundRenderIndex).toBeGreaterThan(clearIndex)
}
)
})
+3 -3
View File
@@ -15,7 +15,7 @@ patchedDependencies:
hash: 81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9
path: config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch
'@xterm/addon-webgl@0.20.0-beta.286':
hash: c32044ff5e9bfa651090eedbec19a13b2343fca3cc163fa8eec04c97880a3e19
hash: 3cf95ef9d41d7ce55b9ab09fd116f12c963fbe6166e05767e8d637bcf169adc4
path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch
node-pty@1.1.0:
hash: 407ae07e1e0e2ff2e8b58696449c54c31e51d87535bc6aa4a7a7b0b561407282
@@ -214,7 +214,7 @@ importers:
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287)
'@xterm/addon-webgl':
specifier: 0.20.0-beta.286
version: 0.20.0-beta.286(patch_hash=c32044ff5e9bfa651090eedbec19a13b2343fca3cc163fa8eec04c97880a3e19)(@xterm/xterm@6.1.0-beta.287)
version: 0.20.0-beta.286(patch_hash=3cf95ef9d41d7ce55b9ab09fd116f12c963fbe6166e05767e8d637bcf169adc4)(@xterm/xterm@6.1.0-beta.287)
'@xterm/xterm':
specifier: 6.1.0-beta.287
version: 6.1.0-beta.287
@@ -9398,7 +9398,7 @@ snapshots:
dependencies:
'@xterm/xterm': 6.1.0-beta.287
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=c32044ff5e9bfa651090eedbec19a13b2343fca3cc163fa8eec04c97880a3e19)(@xterm/xterm@6.1.0-beta.287)':
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=3cf95ef9d41d7ce55b9ab09fd116f12c963fbe6166e05767e8d637bcf169adc4)(@xterm/xterm@6.1.0-beta.287)':
dependencies:
'@xterm/xterm': 6.1.0-beta.287