From c455f5c6690fc1efd1c60ed2f5f5a71e7ddf2012 Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 18 Sep 2026 20:25:07 -0700 Subject: [PATCH 1/2] test(mobile): avoid generated terminal bundle in cache test --- .../terminal-webview-contrast-cache.test.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/mobile/src/terminal/terminal-webview-contrast-cache.test.ts b/mobile/src/terminal/terminal-webview-contrast-cache.test.ts index fee44310a88..ab18968001d 100644 --- a/mobile/src/terminal/terminal-webview-contrast-cache.test.ts +++ b/mobile/src/terminal/terminal-webview-contrast-cache.test.ts @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import { Script } from 'node:vm' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { XTERM_ENGINE_JS } from './terminal-webview-engine.generated' +import { Terminal } from '@xterm/xterm' type CachedColor = { css: string; rgba: number } type ContrastCache = { @@ -12,9 +11,7 @@ type ContrastCache = { _color: { _data: Record> } _css: { _data: Record> } } -type BundledTerminal = { - open(element: HTMLElement): void - dispose(): void +type TerminalWithInternals = InstanceType & { options: { theme: { background: string } } _core: { _themeService: { colors: { contrastCache: ContrastCache; halfContrastCache: ContrastCache } } @@ -29,7 +26,7 @@ const entries = (cache: ContrastCache): number => ) describe('the mobile bundled xterm contrast caches', () => { - let terminal: BundledTerminal + let terminal: TerminalWithInternals let cache: ContrastCache let dimCache: ContrastCache @@ -40,11 +37,9 @@ describe('the mobile bundled xterm contrast caches', () => { vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({ font: '', measureText: () => ({ width: 8 }) - } as CanvasRenderingContext2D) - const Terminal: new (options: { minimumContrastRatio: number }) => BundledTerminal = new Script( - `${XTERM_ENGINE_JS}\nwindow.Terminal` - ).runInThisContext() - terminal = new Terminal({ minimumContrastRatio: 3 }) + } as unknown as CanvasRenderingContext2D) + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: The test reads xterm's private theme-service cache to verify the patched package; Terminal's public API is unchanged. + terminal = new Terminal({ minimumContrastRatio: 3 }) as TerminalWithInternals document.body.innerHTML = '
' terminal.open(document.getElementById('terminal')!) cache = terminal._core._themeService.colors.contrastCache From 00ed3625f818cb54526556569038f0d5ad8ed8bc Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 18 Sep 2026 20:49:21 -0700 Subject: [PATCH 2/2] test(terminal): verify contrast pixels after fresh calculation --- .github/workflows/mobile.yml | 2 + config/scripts/pr-code-change-scope.mjs | 3 +- config/scripts/pr-code-change-scope.test.mjs | 3 + .../README.md | 7 + .../reproduce.mjs | 11 +- .../review-validation.json | 476 ++++++++++++++++++ 6 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 docs/audits/terminal-contrast-cache-retention/review-validation.json diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index 47f56009739..968610bfb8d 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -9,6 +9,7 @@ on: - ready_for_review paths: - 'mobile/**' + - 'config/scripts/xterm-patch-text*' # Mobile launch contracts exercise the real host dispatcher and durable receipt store. - 'src/main/agent-launch/**' - 'src/main/runtime/rpc/**' @@ -46,6 +47,7 @@ on: - main paths: - 'mobile/**' + - 'config/scripts/xterm-patch-text*' - '.github/workflows/mobile.yml' concurrency: diff --git a/config/scripts/pr-code-change-scope.mjs b/config/scripts/pr-code-change-scope.mjs index a2dfcc0082f..a650e74eaa7 100644 --- a/config/scripts/pr-code-change-scope.mjs +++ b/config/scripts/pr-code-change-scope.mjs @@ -73,7 +73,8 @@ const XTERM_PREFIXES = [ 'config/patches/xterm-upstream.json', 'config/patches/@xterm', 'config/patches/xterm-src/', - 'config/scripts/regenerate-xterm-patches' + 'config/scripts/regenerate-xterm-patches', + 'config/scripts/xterm-patch-text' ] const SHELL_PREFIXES = [ diff --git a/config/scripts/pr-code-change-scope.test.mjs b/config/scripts/pr-code-change-scope.test.mjs index 493ac528c26..3b202302f93 100644 --- a/config/scripts/pr-code-change-scope.test.mjs +++ b/config/scripts/pr-code-change-scope.test.mjs @@ -181,6 +181,9 @@ describe('per-job path classification', () => { expectClassification(['config/patches/@xterm__xterm@6.1.0-beta.287.patch'], { xterm_patch_sync: true }) + expectClassification(['config/scripts/xterm-patch-text.mjs'], { + xterm_patch_sync: true + }) expectClassification(['config/scripts/regenerate-xterm-patches-mobile.mjs'], { xterm_patch_sync: true }) diff --git a/docs/audits/terminal-contrast-cache-retention/README.md b/docs/audits/terminal-contrast-cache-retention/README.md index ac4f67cbedf..88ba89db24b 100644 --- a/docs/audits/terminal-contrast-cache-retention/README.md +++ b/docs/audits/terminal-contrast-cache-retention/README.md @@ -80,3 +80,10 @@ The fixed engine stays within 4,096 entries; the baseline exceeds 10,000. - Revisited evicted pairs incur the existing contrast calculation again. No incident report establishes the distinct-color traffic used in this proof; renderer retention does not explain #19768's separately measured main PID. + +The follow-up [review-validation.json](./review-validation.json) reruns both installed +WebGL bundles in normal and dim modes on Node 24.20.0. Its clear/recompute check +also clears the glyph atlas, requires the contrast cache to repopulate, and compares +the probe pixels after fresh calculation. The original before/after measurements +remain in `results.json`. The focused contrast and CI-scope suites pass 41 tests; +changes to the shared patch-text generator now trigger desktop and mobile checks. diff --git a/docs/audits/terminal-contrast-cache-retention/reproduce.mjs b/docs/audits/terminal-contrast-cache-retention/reproduce.mjs index 960874d93fe..63701e1e3ee 100644 --- a/docs/audits/terminal-contrast-cache-retention/reproduce.mjs +++ b/docs/audits/terminal-contrast-cache-retention/reproduce.mjs @@ -171,12 +171,21 @@ try { const afterAtlasClear = entries() cache.clear() const afterThemeClear = entries() + first.addon.clearTextureAtlas() first.addon._renderer.renderRows(0, 0) + const afterRecompute = entries() const recomputedProbePixels = window.glyphAudit.pixelHash(first, 2) - return { beforeAtlasClear, afterAtlasClear, afterThemeClear, recomputedProbePixels } + return { + beforeAtlasClear, + afterAtlasClear, + afterThemeClear, + afterRecompute, + recomputedProbePixels + } }) assert.equal(checks.beforeAtlasClear, checks.afterAtlasClear) assert.equal(checks.afterThemeClear, 0) + assert.ok(checks.afterRecompute > 0) assert.equal(checks.recomputedProbePixels, samples[0].contrastProbePixels) results.push({ phase, diff --git a/docs/audits/terminal-contrast-cache-retention/review-validation.json b/docs/audits/terminal-contrast-cache-retention/review-validation.json new file mode 100644 index 00000000000..5dd40dd222d --- /dev/null +++ b/docs/audits/terminal-contrast-cache-retention/review-validation.json @@ -0,0 +1,476 @@ +{ + "node": "v24.20.0", + "browser": "146.0.7680.0", + "webglSha256": "c4b646075065e9ed5f885880ff80e2edd54f481f982adaf193cc742de17e1a4f", + "atlasClearEveryUpdates": 1000, + "results": [ + { + "phase": "after", + "format": "cjs", + "mode": "normal", + "sha256": "5a40045331d16d5c30e558576a07f6b13c7d9aa0c9027211af78b12a10ad8f12", + "samples": [ + { + "contrastProbePixels": 482738276, + "contrast": 2, + "dimContrast": 0, + "updates": 0, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 95, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 94, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3350996 + }, + { + "contrastProbePixels": 482738276, + "contrast": 1002, + "dimContrast": 0, + "updates": 1000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1095, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 94, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3913560 + }, + { + "contrastProbePixels": 482738276, + "contrast": 907, + "dimContrast": 0, + "updates": 5000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 4, + "shared": true, + "preserved": true, + "heap": 3839976 + }, + { + "contrastProbePixels": 482738276, + "contrast": 1814, + "dimContrast": 0, + "updates": 10000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 9, + "shared": true, + "preserved": true, + "heap": 3959172 + }, + { + "contrastProbePixels": 482738276, + "contrast": 873, + "dimContrast": 0, + "updates": 50000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 49, + "shared": true, + "preserved": true, + "heap": 3587688 + }, + { + "contrastProbePixels": 482738276, + "contrast": 1746, + "dimContrast": 0, + "updates": 100000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 99, + "shared": true, + "preserved": true, + "heap": 3623580 + } + ], + "checks": { + "beforeAtlasClear": 1746, + "afterAtlasClear": 1746, + "afterThemeClear": 0, + "afterRecompute": 3, + "recomputedProbePixels": 482738276 + } + }, + { + "phase": "after", + "format": "cjs", + "mode": "dim", + "sha256": "5a40045331d16d5c30e558576a07f6b13c7d9aa0c9027211af78b12a10ad8f12", + "samples": [ + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1, + "updates": 0, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 5, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 4, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3303056 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1001, + "updates": 1000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1095, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 94, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 4044360 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 905, + "updates": 5000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 4, + "shared": true, + "preserved": true, + "heap": 3867648 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1811, + "updates": 10000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 9, + "shared": true, + "preserved": true, + "heap": 3951968 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 860, + "updates": 50000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 49, + "shared": true, + "preserved": true, + "heap": 3623496 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1721, + "updates": 100000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 99, + "shared": true, + "preserved": true, + "heap": 3656200 + } + ], + "checks": { + "beforeAtlasClear": 1721, + "afterAtlasClear": 1721, + "afterThemeClear": 0, + "afterRecompute": 2, + "recomputedProbePixels": 3123285829 + } + }, + { + "phase": "after", + "format": "esm", + "mode": "normal", + "sha256": "f644ab7b834e7866be59b48fd35456b4350554f22b6cc815bcdba00d36a2d827", + "samples": [ + { + "contrastProbePixels": 482738276, + "contrast": 2, + "dimContrast": 0, + "updates": 0, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 95, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 94, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3250568 + }, + { + "contrastProbePixels": 482738276, + "contrast": 1002, + "dimContrast": 0, + "updates": 1000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1095, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 94, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3810892 + }, + { + "contrastProbePixels": 482738276, + "contrast": 907, + "dimContrast": 0, + "updates": 5000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 4, + "shared": true, + "preserved": true, + "heap": 3742124 + }, + { + "contrastProbePixels": 482738276, + "contrast": 1814, + "dimContrast": 0, + "updates": 10000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 9, + "shared": true, + "preserved": true, + "heap": 3856056 + }, + { + "contrastProbePixels": 482738276, + "contrast": 873, + "dimContrast": 0, + "updates": 50000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 49, + "shared": true, + "preserved": true, + "heap": 3606684 + }, + { + "contrastProbePixels": 482738276, + "contrast": 1746, + "dimContrast": 0, + "updates": 100000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 99, + "shared": true, + "preserved": true, + "heap": 3642024 + } + ], + "checks": { + "beforeAtlasClear": 1746, + "afterAtlasClear": 1746, + "afterThemeClear": 0, + "afterRecompute": 3, + "recomputedProbePixels": 482738276 + } + }, + { + "phase": "after", + "format": "esm", + "mode": "dim", + "sha256": "f644ab7b834e7866be59b48fd35456b4350554f22b6cc815bcdba00d36a2d827", + "samples": [ + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1, + "updates": 0, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 5, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 4, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3371468 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1001, + "updates": 1000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1095, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 94, + "layoutVersion": 0, + "shared": true, + "preserved": true, + "heap": 3795404 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 905, + "updates": 5000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 4, + "shared": true, + "preserved": true, + "heap": 3760004 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1811, + "updates": 10000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 9, + "shared": true, + "preserved": true, + "heap": 3848448 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 860, + "updates": 50000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 49, + "shared": true, + "preserved": true, + "heap": 3829480 + }, + { + "contrastProbePixels": 3123285829, + "contrast": 1, + "dimContrast": 1721, + "updates": 100000, + "firstCellPixels": 638013437, + "secondCellPixels": 395161943, + "regular": 1003, + "combined": 0, + "empty": 0, + "pages": 1, + "glyphs": 3, + "layoutVersion": 99, + "shared": true, + "preserved": true, + "heap": 3670332 + } + ], + "checks": { + "beforeAtlasClear": 1721, + "afterAtlasClear": 1721, + "afterThemeClear": 0, + "afterRecompute": 2, + "recomputedProbePixels": 3123285829 + } + } + ] +}