Revert "Prevent stale terminal pixels on tab reveal" (#7073)

This commit is contained in:
Brennan Benson 2026-07-01 22:51:46 -07:00 committed by GitHub
parent d6578af833
commit 7d3944c8ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 7 additions and 1180 deletions

View File

@ -1,106 +0,0 @@
import type { PaneManager } from '@/lib/pane-manager/pane-manager'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { resumeTerminalVisibility } from './terminal-visibility-resume'
const mocks = vi.hoisted(() => ({
enforceTerminalCurrentScrollIntent: vi.fn(),
fitAndFocusPanes: vi.fn(),
fitPanes: vi.fn(),
flushTerminalOutput: vi.fn(),
focusActivePane: vi.fn(),
requestTerminalBacklogRecovery: vi.fn(),
resetAndRefreshAllTerminalWebglAtlases: vi.fn(),
scheduleTerminalVisibilityWebglRecovery: vi.fn()
}))
vi.mock('@/lib/pane-manager/pane-manager-registry', () => ({
resetAndRefreshAllTerminalWebglAtlases: mocks.resetAndRefreshAllTerminalWebglAtlases
}))
vi.mock('@/lib/pane-manager/pane-terminal-output-scheduler', () => ({
flushTerminalOutput: mocks.flushTerminalOutput,
requestTerminalBacklogRecovery: mocks.requestTerminalBacklogRecovery
}))
vi.mock('@/lib/pane-manager/terminal-scroll-intent', () => ({
enforceTerminalCurrentScrollIntent: mocks.enforceTerminalCurrentScrollIntent
}))
vi.mock('./pane-helpers', () => ({
fitAndFocusPanes: mocks.fitAndFocusPanes,
fitPanes: mocks.fitPanes,
focusActivePane: mocks.focusActivePane
}))
vi.mock('./terminal-webgl-atlas-recovery', () => ({
scheduleTerminalVisibilityWebglRecovery: mocks.scheduleTerminalVisibilityWebglRecovery
}))
describe('resumeTerminalVisibility', () => {
beforeEach(() => {
vi.clearAllMocks()
})
function createManager(): PaneManager {
return {
getPanes: vi.fn(() => [{ terminal: 'first-terminal' }, { terminal: 'second-terminal' }]),
resumeRendering: vi.fn()
} as unknown as PaneManager
}
function resume(
options: {
isActive?: boolean
shouldUseLightTabResume?: boolean
wasVisible?: boolean
} = {}
): {
captureViewportPositions: ReturnType<typeof vi.fn>
manager: PaneManager
withSuppressedScrollTracking: ReturnType<typeof vi.fn>
} {
const manager = createManager()
const captureViewportPositions = vi.fn(() => new Map())
const withSuppressedScrollTracking = vi.fn((callback: () => void) => callback())
resumeTerminalVisibility({
manager,
isActive: options.isActive ?? true,
wasVisible: options.wasVisible ?? false,
shouldUseLightTabResume: options.shouldUseLightTabResume ?? true,
captureViewportPositions,
withSuppressedScrollTracking
})
return { captureViewportPositions, manager, withSuppressedScrollTracking }
}
it('uses visibility-owned WebGL recovery on light regular-tab resume', () => {
const { captureViewportPositions, manager, withSuppressedScrollTracking } = resume()
expect(captureViewportPositions).toHaveBeenCalledWith(true)
expect(withSuppressedScrollTracking).toHaveBeenCalledTimes(1)
expect(mocks.requestTerminalBacklogRecovery).toHaveBeenCalledWith('first-terminal')
expect(mocks.requestTerminalBacklogRecovery).toHaveBeenCalledWith('second-terminal')
expect(mocks.flushTerminalOutput).not.toHaveBeenCalled()
expect(manager.resumeRendering).not.toHaveBeenCalled()
expect(mocks.fitAndFocusPanes).not.toHaveBeenCalled()
expect(mocks.fitPanes).not.toHaveBeenCalled()
expect(mocks.scheduleTerminalVisibilityWebglRecovery).toHaveBeenCalledTimes(1)
expect(mocks.resetAndRefreshAllTerminalWebglAtlases).not.toHaveBeenCalled()
expect(mocks.focusActivePane).toHaveBeenCalledWith(manager)
expect(mocks.enforceTerminalCurrentScrollIntent).toHaveBeenCalledTimes(2)
})
it('preserves direct reset and refresh on heavy visibility resume', () => {
const { manager } = resume({ shouldUseLightTabResume: false })
expect(mocks.scheduleTerminalVisibilityWebglRecovery).not.toHaveBeenCalled()
expect(mocks.requestTerminalBacklogRecovery).toHaveBeenCalledTimes(2)
expect(mocks.flushTerminalOutput).toHaveBeenCalledTimes(2)
expect(manager.resumeRendering).toHaveBeenCalledTimes(1)
expect(mocks.fitAndFocusPanes).toHaveBeenCalledWith(manager)
expect(mocks.resetAndRefreshAllTerminalWebglAtlases).toHaveBeenCalledTimes(1)
expect(mocks.enforceTerminalCurrentScrollIntent).toHaveBeenCalledTimes(2)
})
})

View File

@ -7,7 +7,7 @@ import {
} from '@/lib/pane-manager/pane-terminal-output-scheduler'
import { enforceTerminalCurrentScrollIntent } from '@/lib/pane-manager/terminal-scroll-intent'
import { fitAndFocusPanes, fitPanes, focusActivePane } from './pane-helpers'
import { scheduleTerminalVisibilityWebglRecovery } from './terminal-webgl-atlas-recovery'
import { scheduleTerminalWebglAtlasRecovery } from './terminal-webgl-atlas-recovery'
const VISIBLE_RESUME_FLUSH_CHARS = 256 * 1024
const WINDOW_WAKE_FLUSH_CHARS = 64 * 1024
@ -62,7 +62,7 @@ export function resumeTerminalVisibility({
// overlay's delayed geometry fit. Still request hidden-output recovery:
// agent TUIs can suppress hidden bytes until the pane is foregrounded.
requestLightTabBacklogRecovery(manager)
scheduleTerminalVisibilityWebglRecovery()
scheduleTerminalWebglAtlasRecovery()
if (isActive) {
focusActivePane(manager)
}

View File

@ -5,7 +5,6 @@ import {
} from '@/lib/pane-manager/pane-manager-registry'
import {
scheduleImagePasteWebglAtlasRecovery,
scheduleTerminalVisibilityWebglRecovery,
scheduleTerminalWebglAtlasRecovery
} from './terminal-webgl-atlas-recovery'
@ -179,81 +178,4 @@ describe('terminal WebGL atlas recovery', () => {
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(6)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(6)
})
it('does not coalesce visibility recovery behind in-flight terminal-output recovery', () => {
vi.useFakeTimers()
const rafCallbacks: FrameRequestCallback[] = []
vi.stubGlobal(
'requestAnimationFrame',
vi.fn((callback: FrameRequestCallback) => {
rafCallbacks.push(callback)
return rafCallbacks.length
})
)
const manager = registerManager()
scheduleTerminalWebglAtlasRecovery()
expect(rafCallbacks).toHaveLength(1)
rafCallbacks.shift()?.(0)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(1)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(120)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(2)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(2)
scheduleTerminalVisibilityWebglRecovery()
scheduleTerminalVisibilityWebglRecovery()
expect(rafCallbacks).toHaveLength(1)
rafCallbacks.shift()?.(0)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(3)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(3)
vi.advanceTimersByTime(120)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(4)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(4)
vi.advanceTimersByTime(380)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(6)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(6)
scheduleTerminalVisibilityWebglRecovery()
expect(rafCallbacks).toHaveLength(1)
rafCallbacks.shift()?.(0)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(7)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(7)
vi.advanceTimersByTime(500)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(9)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(9)
})
it('allows another first-frame tab reveal while coalescing the delayed tail', () => {
vi.useFakeTimers()
const rafCallbacks: FrameRequestCallback[] = []
vi.stubGlobal(
'requestAnimationFrame',
vi.fn((callback: FrameRequestCallback) => {
rafCallbacks.push(callback)
return rafCallbacks.length
})
)
const manager = registerManager()
scheduleTerminalVisibilityWebglRecovery()
expect(rafCallbacks).toHaveLength(1)
rafCallbacks.shift()?.(0)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(1)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(1)
scheduleTerminalVisibilityWebglRecovery()
expect(rafCallbacks).toHaveLength(1)
rafCallbacks.shift()?.(0)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(2)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(2)
vi.advanceTimersByTime(120)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(3)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(3)
vi.advanceTimersByTime(380)
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(4)
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(4)
})
})

View File

@ -1,23 +1,8 @@
import { resetAndRefreshAllTerminalWebglAtlases } from '@/lib/pane-manager/pane-manager-registry'
import { e2eConfig } from '@/lib/e2e-config'
const ATLAS_RECOVERY_DELAYS_MS = [120, 500]
let terminalOutputRecoveryScheduled = false
let terminalVisibilityFrameRecoveryScheduled = false
let terminalVisibilityDelayedRecoveryScheduled = false
type TerminalRecoveryTestWindow = Window & {
__terminalTabOverlapSuppressVisibilityRecovery?: boolean
}
function isVisibilityRecoverySuppressedForTest(): boolean {
return (
e2eConfig.exposeStore &&
typeof window !== 'undefined' &&
Boolean((window as TerminalRecoveryTestWindow).__terminalTabOverlapSuppressVisibilityRecovery)
)
}
function scheduleNextFrame(callback: () => void): void {
if (typeof globalThis.requestAnimationFrame === 'function') {
@ -37,21 +22,13 @@ function resetAtlasesAndRefreshPanes(): void {
}
}
type AtlasRecoveryBurstCallbacks = {
onComplete?: () => void
onFirstReset?: () => void
}
function scheduleAtlasRecoveryBurst(callbacks: AtlasRecoveryBurstCallbacks = {}): void {
scheduleNextFrame(() => {
resetAtlasesAndRefreshPanes()
callbacks.onFirstReset?.()
})
function scheduleAtlasRecoveryBurst(onComplete?: () => void): void {
scheduleNextFrame(() => resetAtlasesAndRefreshPanes())
for (const [index, delayMs] of ATLAS_RECOVERY_DELAYS_MS.entries()) {
globalThis.setTimeout(() => {
resetAtlasesAndRefreshPanes()
if (index === ATLAS_RECOVERY_DELAYS_MS.length - 1) {
callbacks.onComplete?.()
onComplete?.()
}
}, delayMs)
}
@ -70,39 +47,7 @@ export function scheduleTerminalWebglAtlasRecovery(): void {
terminalOutputRecoveryScheduled = true
// Why: TUI redraw bursts can corrupt xterm's shared WebGL glyph atlas without
// a context-loss event; coalesce resets so output storms do not queue timers.
scheduleAtlasRecoveryBurst({
onComplete: () => {
terminalOutputRecoveryScheduled = false
}
scheduleAtlasRecoveryBurst(() => {
terminalOutputRecoveryScheduled = false
})
}
export function scheduleTerminalVisibilityWebglRecovery(): void {
if (isVisibilityRecoverySuppressedForTest()) {
return
}
if (terminalVisibilityFrameRecoveryScheduled) {
return
}
terminalVisibilityFrameRecoveryScheduled = true
// Why: tab reveal is a separate repaint boundary from hidden-output parsing,
// so an in-flight output recovery must not suppress the returned tab's repaint.
// The first-frame repaint is per reveal, while the delayed tail is coalesced
// so rapid tab cycling does not queue unbounded global refresh bursts.
scheduleNextFrame(() => {
resetAtlasesAndRefreshPanes()
terminalVisibilityFrameRecoveryScheduled = false
})
if (terminalVisibilityDelayedRecoveryScheduled) {
return
}
terminalVisibilityDelayedRecoveryScheduled = true
for (const [index, delayMs] of ATLAS_RECOVERY_DELAYS_MS.entries()) {
globalThis.setTimeout(() => {
resetAtlasesAndRefreshPanes()
if (index === ATLAS_RECOVERY_DELAYS_MS.length - 1) {
terminalVisibilityDelayedRecoveryScheduled = false
}
}, delayMs)
}
}

View File

@ -1,66 +0,0 @@
import type { Page } from '@stablyai/playwright-test'
type RecoveryCounterWindow = Window & {
__terminalTabOverlapResetCounts?: Record<string, number>
__terminalTabOverlapResetTimes?: Record<string, number[]>
__terminalTabOverlapSuppressVisibilityRecovery?: boolean
}
export async function instrumentTabResetCounters(page: Page, tabIds: string[]): Promise<void> {
const instrumented = await page.evaluate((tabIds) => {
const counterWindow = window as RecoveryCounterWindow
counterWindow.__terminalTabOverlapResetCounts = Object.fromEntries(
tabIds.map((tabId) => [tabId, 0])
)
counterWindow.__terminalTabOverlapResetTimes = Object.fromEntries(
tabIds.map((tabId) => [tabId, []])
)
return tabIds.map((tabId) => {
const manager = window.__paneManagers?.get(tabId)
if (!manager?.resetWebglTextureAtlases) {
return false
}
const originalReset = manager.resetWebglTextureAtlases.bind(manager)
manager.resetWebglTextureAtlases = () => {
const counts = counterWindow.__terminalTabOverlapResetCounts ?? {}
counts[tabId] = (counts[tabId] ?? 0) + 1
counterWindow.__terminalTabOverlapResetCounts = counts
const times = counterWindow.__terminalTabOverlapResetTimes ?? {}
times[tabId] = [...(times[tabId] ?? []), performance.now()]
counterWindow.__terminalTabOverlapResetTimes = times
originalReset()
}
return true
})
}, tabIds)
if (!instrumented.every(Boolean)) {
throw new Error(`could not instrument WebGL reset counters for tabs ${tabIds.join(', ')}`)
}
}
export async function readTabResetCount(page: Page, tabId: string): Promise<number> {
return page.evaluate(
(tabId) => (window as RecoveryCounterWindow).__terminalTabOverlapResetCounts?.[tabId] ?? 0,
tabId
)
}
export async function readTabResetSnapshot(page: Page, tabId: string) {
return page.evaluate((tabId) => {
const counterWindow = window as RecoveryCounterWindow
const times = counterWindow.__terminalTabOverlapResetTimes?.[tabId] ?? []
return {
count: counterWindow.__terminalTabOverlapResetCounts?.[tabId] ?? 0,
latestAt: times.at(-1) ?? 0
}
}, tabId)
}
export async function setVisibilityRecoverySuppressed(
page: Page,
suppressed: boolean
): Promise<void> {
await page.evaluate((suppressed) => {
;(window as RecoveryCounterWindow).__terminalTabOverlapSuppressVisibilityRecovery = suppressed
}, suppressed)
}

View File

@ -1,38 +0,0 @@
import type { Page } from '@stablyai/playwright-test'
export const STRETCHED_HIDDEN_OUTPUT_FALLBACK_MS = 3_000
type TimeoutStretchWindow = Window & {
__terminalTabOverlapOriginalSetTimeout?: typeof window.setTimeout
}
export async function stretchFiveHundredMillisecondTimeouts(page: Page): Promise<void> {
await page.evaluate((stretchedDelayMs) => {
const testWindow = window as TimeoutStretchWindow
if (testWindow.__terminalTabOverlapOriginalSetTimeout) {
return
}
const originalSetTimeout = window.setTimeout
const boundSetTimeout = originalSetTimeout.bind(window)
testWindow.__terminalTabOverlapOriginalSetTimeout = originalSetTimeout
// Why: terminal reveal recovery tests must prove reveal repaint, so the old
// hidden-output fallback is stretched out of the assertion window.
window.setTimeout = ((handler: TimerHandler, timeout?: number, ...args: unknown[]) =>
boundSetTimeout(
handler,
timeout === 500 ? stretchedDelayMs : timeout,
...args
)) as typeof window.setTimeout
}, STRETCHED_HIDDEN_OUTPUT_FALLBACK_MS)
}
export async function restoreFiveHundredMillisecondTimeouts(page: Page): Promise<void> {
await page.evaluate(() => {
const testWindow = window as TimeoutStretchWindow
if (!testWindow.__terminalTabOverlapOriginalSetTimeout) {
return
}
window.setTimeout = testWindow.__terminalTabOverlapOriginalSetTimeout
delete testWindow.__terminalTabOverlapOriginalSetTimeout
})
}

View File

@ -1,830 +0,0 @@
import { writeFileSync } from 'node:fs'
import path from 'node:path'
import type { Page, TestInfo } from '@stablyai/playwright-test'
import { PNG } from 'pngjs'
import { test, expect } from './helpers/orca-app'
import {
ensureTerminalVisible,
getActiveTabId,
getActiveWorktreeId,
waitForActiveWorktree,
waitForSessionReady
} from './helpers/store'
import {
getTerminalContent,
sendToTerminal,
waitForActiveTerminalManager
} from './helpers/terminal'
import {
restoreFiveHundredMillisecondTimeouts,
STRETCHED_HIDDEN_OUTPUT_FALLBACK_MS,
stretchFiveHundredMillisecondTimeouts
} from './terminal-recovery-timeout-stretch'
import {
instrumentTabResetCounters,
readTabResetCount,
readTabResetSnapshot,
setVisibilityRecoverySuppressed
} from './terminal-recovery-reset-counters'
import { compareTerminalScreenshots } from './terminal-screenshot-diff'
const TAB_A_MARKER = 'ORCA_OVERLAP_REPRO_TAB_A_ONLY'
const TAB_B_MARKER = 'ORCA_OVERLAP_REPRO_TAB_B_ONLY'
const TAB_B_GLYPHS = 'ZYXWVUTSRQPONMLKJIHGFEDCBA 9876543210 !?^"\'();:,.|$_-'
const TAB_B_COLOR = { red: 255, green: 75, blue: 170 }
const VISUAL_OVERLAP_PROBE_CYCLES = 140
const SIBLING_COLOR_PIXEL_FLOOR = 150
const SIBLING_COLOR_PIXEL_DELTA = 100
const REVEAL_RECOVERY_TIMEOUT_MS = 180
const REAL_CLAUDE_PROBE_CYCLES = 160
const REFRESH_REPAIR_DIFF_RATIO = 0.035
type TabIdentity = {
tabId: string
leafId: string
ptyId: string
}
type TabBufferProbe = {
containsOwnMarker: boolean
containsSiblingMarker: boolean
visibleText: string
}
type RefreshRepairProbe = {
diffRatio: number
diffPixels: number
bufferUnchanged: boolean
beforeText: string
afterText: string
beforeScreenshot: Buffer
afterScreenshot: Buffer
}
type HiddenOutputWindow = Window & {
__terminalPtyDataInjection?: {
inject: (paneKey: string, data: string, meta?: { seq?: number; rawLength?: number }) => boolean
}
}
async function setTerminalGpuOn(page: Page): Promise<void> {
await page.evaluate(() => {
const store = window.__store
const state = store?.getState()
if (!store || !state?.settings) {
throw new Error('Store unavailable')
}
store.setState({
settings: {
...state.settings,
terminalGpuAcceleration: 'on'
}
})
})
}
async function ensureTwoTerminalTabs(
page: Page
): Promise<{ firstTabId: string; secondTabId: string }> {
const worktreeId = (await getActiveWorktreeId(page))!
const result = await page.evaluate((worktreeId) => {
const store = window.__store
if (!store) {
throw new Error('Store unavailable')
}
const state = store.getState()
const existing = state.tabsByWorktree[worktreeId] ?? []
const first = existing.find((tab) => tab.type === 'terminal') ?? state.createTab(worktreeId)
let second = existing.find((tab) => tab.type === 'terminal' && tab.id !== first.id)
if (!second) {
second = state.createTab(worktreeId, undefined, undefined, { activate: false })
}
state.setActiveTab(first.id)
state.setActiveTabType('terminal')
return { firstTabId: first.id, secondTabId: second.id }
}, worktreeId)
await waitForTerminalManagerOnTab(page, result.firstTabId)
await waitForTerminalManagerOnTab(page, result.secondTabId)
return result
}
async function ensureThreeTerminalTabs(
page: Page
): Promise<{ firstTabId: string; secondTabId: string; thirdTabId: string }> {
const worktreeId = (await getActiveWorktreeId(page))!
const result = await page.evaluate((worktreeId) => {
const store = window.__store
if (!store) {
throw new Error('Store unavailable')
}
const state = store.getState()
const terminalTabs = (state.tabsByWorktree[worktreeId] ?? []).filter(
(tab) => tab.type === 'terminal'
)
const first = terminalTabs[0] ?? state.createTab(worktreeId)
const second =
terminalTabs.find((tab) => tab.id !== first.id) ??
state.createTab(worktreeId, undefined, undefined, { activate: false })
const third =
terminalTabs.find((tab) => tab.id !== first.id && tab.id !== second.id) ??
state.createTab(worktreeId, undefined, undefined, { activate: false })
state.setActiveTab(first.id)
state.setActiveTabType('terminal')
return { firstTabId: first.id, secondTabId: second.id, thirdTabId: third.id }
}, worktreeId)
await waitForTerminalManagerOnTab(page, result.firstTabId)
await waitForTerminalManagerOnTab(page, result.secondTabId)
await waitForTerminalManagerOnTab(page, result.thirdTabId)
return result
}
async function waitForTerminalManagerOnTab(page: Page, tabId: string): Promise<void> {
await expect
.poll(
() =>
page.evaluate((tabId) => {
const manager = window.__paneManagers?.get(tabId)
return Boolean(manager?.getActivePane?.() ?? manager?.getPanes?.()[0])
}, tabId),
{ timeout: 15_000, message: `terminal manager did not mount for ${tabId}` }
)
.toBe(true)
}
async function activateTerminalTab(page: Page, tabId: string): Promise<void> {
await page.evaluate((tabId) => {
const state = window.__store?.getState()
state?.setActiveTab(tabId)
state?.setActiveTabType('terminal')
}, tabId)
await expect
.poll(
() =>
page
.locator(`[data-testid="sortable-tab"][data-active="true"]`)
.getAttribute('data-tab-id'),
{ timeout: 5_000, message: `terminal tab ${tabId} did not become active` }
)
.toBe(tabId)
}
async function waitForWebglOnTab(page: Page, tabId: string): Promise<boolean> {
await page.evaluate((tabId) => {
window.__paneManagers?.get(tabId)?.setTerminalGpuAcceleration?.('on')
}, tabId)
return page
.waitForFunction(
(tabId) => {
const diagnostics = window.__paneManagers?.get(tabId)?.getRenderingDiagnostics?.() ?? []
return diagnostics.some((entry) => entry.hasWebgl)
},
tabId,
{ timeout: 15_000 }
)
.then(() => true)
.catch(() => false)
}
async function readTabIdentity(page: Page, tabId: string): Promise<TabIdentity> {
await expect
.poll(
() =>
page.evaluate((tabId) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
return pane?.container.dataset.ptyId ?? null
}, tabId),
{ timeout: 20_000, message: `terminal tab ${tabId} did not bind a PTY` }
)
.not.toBeNull()
const identity = await page.evaluate((tabId) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
if (!pane) {
return null
}
return {
tabId,
leafId: pane.container.dataset.leafId ?? null,
ptyId: pane.container.dataset.ptyId ?? null
}
}, tabId)
if (!identity?.leafId || !identity.ptyId) {
throw new Error(`terminal tab ${tabId} did not bind a PTY`)
}
return identity
}
async function readTabBufferProbe(
page: Page,
tabId: string,
ownMarker: string,
siblingMarker: string
): Promise<TabBufferProbe> {
return page.evaluate(
({ tabId, ownMarker, siblingMarker }) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
if (!pane) {
throw new Error(`No terminal pane for ${tabId}`)
}
const activeBuffer = pane.terminal.buffer.active
const rows: string[] = []
for (let row = 0; row < pane.terminal.rows; row += 1) {
rows.push(activeBuffer.getLine(activeBuffer.viewportY + row)?.translateToString(true) ?? '')
}
const visibleText = rows.join('\n')
return {
containsOwnMarker: visibleText.includes(ownMarker),
containsSiblingMarker: visibleText.includes(siblingMarker),
visibleText
}
},
{ tabId, ownMarker, siblingMarker }
)
}
async function startTuiStream(
page: Page,
ptyId: string,
scriptPath: string,
marker: string,
glyphs: string,
color: { red: number; green: number; blue: number },
cadenceMs: number
): Promise<void> {
const script = [
`const marker=${JSON.stringify(marker)};`,
`const glyphs=${JSON.stringify(glyphs)};`,
`const color=${JSON.stringify(color)};`,
`const cadence=${cadenceMs};`,
'let frame=0;',
'function bar(width){return "█".repeat((frame % width) + 1) + "░".repeat(width - ((frame % width) + 1));}',
'function paint(text){return `\\x1b[38;2;${color.red};${color.green};${color.blue}m${text}\\x1b[0m`; }',
'function emit(){',
' const lines=[];',
' lines.push("\\x1b[?2026h");',
' lines.push("\\x1b[?1049h");',
' lines.push("\\x1b[2J\\x1b[H\\x1b[?25l");',
' lines.push(paint(`╭────────────────────────────────────────────────────────────────────╮`));',
' lines.push(paint(`│ ${marker} frame ${String(frame).padStart(4,"0")} ${bar(18)} │`));',
' lines.push(paint(`├────────────────────────────────────────────────────────────────────┤`));',
' for (let row=0; row<18; row++) {',
' lines.push(paint(`│ row ${String(row).padStart(2,"0")} ${glyphs} ${bar(10)} │`));',
' }',
' lines.push(paint(`╰────────────────────────────────────────────────────────────────────╯`));',
' lines.push("\\x1b[?25h\\x1b[?2026l");',
' process.stdout.write(lines.join("\\r\\n"));',
' frame += 1;',
'}',
'emit();',
'setInterval(emit, cadence);'
].join('')
writeFileSync(scriptPath, script)
await sendToTerminal(page, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
}
async function writeSparsePromptFrame(
page: Page,
ptyId: string,
scriptPath: string
): Promise<void> {
const script = [
`const marker=${JSON.stringify(TAB_A_MARKER)};`,
'process.stdout.write("\\x1b[2J\\x1b[3J\\x1b[H\\x1b[?25h");',
'process.stdout.write(`${marker}\\r\\n`);',
'process.stdout.write("Please make more to 5 more different random files. Explore orca a bit actually, then do it. BBB\\r\\n");'
].join('')
writeFileSync(scriptPath, script)
await sendToTerminal(page, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
}
async function writeDenseStaticFrame(
page: Page,
ptyId: string,
scriptPath: string,
marker: string
): Promise<void> {
const script = [
`const marker=${JSON.stringify(marker)};`,
'const glyphs="ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 []{}<>/\\\\#@%&*+=~";',
'process.stdout.write("\\x1b[2J\\x1b[3J\\x1b[H\\x1b[?25l");',
'for (let row = 0; row < 22; row += 1) {',
' process.stdout.write(`${marker} row ${String(row).padStart(2, "0")} | ${glyphs} |\\r\\n`);',
'}'
].join('')
writeFileSync(scriptPath, script)
await sendToTerminal(page, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
}
async function injectHiddenTuiFrame(
page: Page,
identity: TabIdentity,
marker: string
): Promise<void> {
const paneKey = `${identity.tabId}:${identity.leafId}`
const frame = [
'\x1b[?2026h',
'\x1b[?1049h',
'\x1b[2J\x1b[H\x1b[?25l',
`╭────────────────────────────────────────────────────────╮`,
`${marker} hidden atlas recovery frame │`,
`├────────────────────────────────────────────────────────┤`,
...Array.from(
{ length: 12 },
(_, row) => `│ hidden row ${String(row).padStart(2, '0')} ███████████████████ │`
),
`╰────────────────────────────────────────────────────────╯`,
'\x1b[?25h\x1b[?2026l'
].join('\r\n')
const injected = await page.evaluate(
({ paneKey, frame }) =>
(window as HiddenOutputWindow).__terminalPtyDataInjection?.inject(paneKey, frame, {
seq: frame.length,
rawLength: frame.length
}) ?? false,
{ paneKey, frame }
)
if (!injected) {
throw new Error(`No terminal PTY data injector registered for ${paneKey}`)
}
}
async function corruptTabAtlas(page: Page, tabId: string): Promise<number> {
return page.evaluate((tabId) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
if (!pane) {
return 0
}
const canvases = Array.from(
document.querySelectorAll<HTMLCanvasElement>(`[data-terminal-tab-id="${tabId}"] canvas`)
)
const noise = new Uint8Array(64 * 64 * 4)
for (let index = 0; index < noise.length; index += 4) {
noise[index] = (index * 7) % 256
noise[index + 1] = (index * 13) % 256
noise[index + 2] = (index * 29) % 256
noise[index + 3] = 255
}
let corrupted = 0
for (const canvas of canvases) {
const gl =
(canvas.getContext('webgl2') as WebGL2RenderingContext | null) ??
(canvas.getContext('webgl') as WebGLRenderingContext | null)
if (!gl) {
continue
}
const maxUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS) as number
for (let unit = 0; unit < maxUnits; unit += 1) {
gl.activeTexture(gl.TEXTURE0 + unit)
if (!gl.getParameter(gl.TEXTURE_BINDING_2D)) {
continue
}
for (const [x, y] of [
[0, 0],
[64, 0],
[128, 0],
[192, 0],
[0, 64],
[64, 64],
[128, 64],
[192, 64]
]) {
gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, noise)
if (gl.getError() === gl.NO_ERROR) {
corrupted += 1
}
}
if (corrupted > 0) {
break
}
}
gl.activeTexture(gl.TEXTURE0)
if (corrupted > 0) {
break
}
}
pane.terminal.refresh(0, pane.terminal.rows - 1)
return corrupted
}, tabId)
}
async function resetAndRefreshTab(page: Page, tabId: string): Promise<void> {
await page.evaluate((tabId) => {
const manager = window.__paneManagers?.get(tabId)
manager?.resetWebglTextureAtlases?.()
manager?.refreshAllPanes?.()
}, tabId)
await waitForTwoAnimationFrames(page)
}
async function waitForTwoAnimationFrames(page: Page): Promise<void> {
await page.evaluate(
() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
)
}
async function captureTabScreen(page: Page, tabId: string): Promise<Buffer> {
const screen = page.locator(`[data-terminal-tab-id="${tabId}"] .xterm-screen`).first()
await expect(screen).toBeVisible()
return screen.screenshot({ animations: 'disabled' })
}
function countColorPixels(
buffer: Buffer,
target: { red: number; green: number; blue: number }
): number {
const image = PNG.sync.read(buffer)
let pixels = 0
for (let offset = 0; offset < image.data.length; offset += 4) {
const alpha = image.data[offset + 3] ?? 0
if (alpha < 120) {
continue
}
const red = image.data[offset] ?? 0
const green = image.data[offset + 1] ?? 0
const blue = image.data[offset + 2] ?? 0
const distance =
Math.abs(red - target.red) + Math.abs(green - target.green) + Math.abs(blue - target.blue)
if (distance <= 95) {
pixels += 1
}
}
return pixels
}
async function attachArtifact(
testInfo: TestInfo,
name: string,
body: Buffer | string
): Promise<void> {
await testInfo.attach(name, {
body: typeof body === 'string' ? Buffer.from(body) : body,
contentType: typeof body === 'string' ? 'text/plain' : 'image/png'
})
}
async function refreshActiveTabAndCompare(page: Page, tabId: string): Promise<RefreshRepairProbe> {
const beforeText = (await readTabBufferProbe(page, tabId, '', '\u0000')).visibleText
const beforeScreenshot = await captureTabScreen(page, tabId)
await page.evaluate((tabId) => {
const manager = window.__paneManagers?.get(tabId)
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
if (!pane) {
throw new Error(`No terminal pane for ${tabId}`)
}
pane.terminal.refresh(0, pane.terminal.rows - 1)
}, tabId)
await waitForTwoAnimationFrames(page)
const afterScreenshot = await captureTabScreen(page, tabId)
const afterText = (await readTabBufferProbe(page, tabId, '', '\u0000')).visibleText
const diff = compareTerminalScreenshots(beforeScreenshot, afterScreenshot)
return {
diffRatio: diff.diffRatio,
diffPixels: diff.diffPixels,
bufferUnchanged: beforeText === afterText,
beforeText,
afterText,
beforeScreenshot,
afterScreenshot
}
}
async function startClaudeEditSession(
page: Page,
ptyId: string,
marker: string,
filePrefix: string
): Promise<void> {
const prompt = [
`This is an Orca terminal renderer reproduction run. Marker: ${marker}.`,
'Create or edit 10 small scratch files in the current repo.',
`Name them ${filePrefix}-01.txt through ${filePrefix}-10.txt.`,
'After each file edit, run a tiny shell command that prints the marker and sleep 1 second.',
'Keep working without asking questions. The visible terminal activity is the point.'
].join(' ')
await sendToTerminal(
page,
ptyId,
`claude --model sonnet --effort low --dangerously-skip-permissions --permission-mode bypassPermissions ${JSON.stringify(prompt)}\r`
)
}
test.describe('Terminal tab switch visual overlap repro @headful', () => {
test('manual probe for visible sibling TUI pixels while tab buffers stay isolated', async ({
orcaPage,
testRepoPath
}, testInfo) => {
test.skip(
process.env.ORCA_E2E_VISUAL_OVERLAP_PROBE !== '1',
'Set ORCA_E2E_VISUAL_OVERLAP_PROBE=1 to run the best-effort visual overlap probe'
)
test.setTimeout(180_000)
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
await setTerminalGpuOn(orcaPage)
const { firstTabId, secondTabId } = await ensureTwoTerminalTabs(orcaPage)
await activateTerminalTab(orcaPage, firstTabId)
const firstWebgl = await waitForWebglOnTab(orcaPage, firstTabId)
const firstIdentity = await readTabIdentity(orcaPage, firstTabId)
await activateTerminalTab(orcaPage, secondTabId)
const secondWebgl = await waitForWebglOnTab(orcaPage, secondTabId)
const secondIdentity = await readTabIdentity(orcaPage, secondTabId)
test.skip(!firstWebgl || !secondWebgl, 'WebGL did not attach on both regular terminal tabs')
await writeSparsePromptFrame(
orcaPage,
firstIdentity.ptyId,
path.join(testRepoPath, 'orca-overlap-tab-a-sparse.mjs')
)
await startTuiStream(
orcaPage,
secondIdentity.ptyId,
path.join(testRepoPath, 'orca-overlap-tab-b.mjs'),
TAB_B_MARKER,
TAB_B_GLYPHS,
TAB_B_COLOR,
41
)
await activateTerminalTab(orcaPage, firstTabId)
await expect
.poll(() => getTerminalContent(orcaPage, 12_000), {
timeout: 20_000,
message: 'tab A sparse prompt frame did not reach the visible terminal'
})
.toContain(TAB_A_MARKER)
const baselineA = await captureTabScreen(orcaPage, firstTabId)
const baselineAHasSiblingColor = countColorPixels(baselineA, TAB_B_COLOR)
await activateTerminalTab(orcaPage, secondTabId)
await expect
.poll(() => getTerminalContent(orcaPage, 12_000), {
timeout: 20_000,
message: 'tab B TUI stream did not reach the visible terminal'
})
.toContain(`${TAB_B_MARKER} frame`)
const baselineB = await captureTabScreen(orcaPage, secondTabId)
const baselineBHasOwnColor = countColorPixels(baselineB, TAB_B_COLOR)
expect(baselineBHasOwnColor).toBeGreaterThan(500)
const reports: string[] = []
for (let cycle = 0; cycle < VISUAL_OVERLAP_PROBE_CYCLES; cycle += 1) {
await activateTerminalTab(orcaPage, secondTabId)
await orcaPage.waitForTimeout(15)
await activateTerminalTab(orcaPage, firstTabId)
const activeTabId = (await getActiveTabId(orcaPage))!
const screenshot = await captureTabScreen(orcaPage, activeTabId)
const probe = await readTabBufferProbe(orcaPage, activeTabId, TAB_A_MARKER, TAB_B_MARKER)
if (!probe.containsOwnMarker || probe.containsSiblingMarker) {
reports.push(
`cycle ${cycle} buffer mismatch active=${activeTabId} own=${probe.containsOwnMarker} sibling=${probe.containsSiblingMarker}`
)
await attachArtifact(testInfo, `buffer-mismatch-cycle-${cycle}.txt`, probe.visibleText)
break
}
const siblingColorPixels = countColorPixels(screenshot, TAB_B_COLOR)
if (
siblingColorPixels >
Math.max(SIBLING_COLOR_PIXEL_FLOOR, baselineAHasSiblingColor + SIBLING_COLOR_PIXEL_DELTA)
) {
reports.push(
`cycle ${cycle} visual sibling color active=${activeTabId} siblingPixels=${siblingColorPixels} baselineSiblingPixels=${baselineAHasSiblingColor} bufferSibling=${probe.containsSiblingMarker}`
)
await attachArtifact(testInfo, `overlap-candidate-${cycle}.png`, screenshot)
await attachArtifact(testInfo, `overlap-buffer-${cycle}.txt`, probe.visibleText)
break
}
await orcaPage.waitForTimeout(20)
}
await attachArtifact(testInfo, 'tab-a-baseline.png', baselineA)
await attachArtifact(testInfo, 'tab-b-baseline.png', baselineB)
expect(
reports,
reports.length > 0
? `active terminal showed sibling-colored pixels while buffers stayed isolated:\n${reports.join('\n')}`
: undefined
).toEqual([])
})
test('repairs returned tab pixels even while hidden-output recovery is in flight', async ({
orcaPage,
testRepoPath
}, testInfo) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
await setTerminalGpuOn(orcaPage)
const { firstTabId, secondTabId, thirdTabId } = await ensureThreeTerminalTabs(orcaPage)
await activateTerminalTab(orcaPage, firstTabId)
const firstWebgl = await waitForWebglOnTab(orcaPage, firstTabId)
const firstIdentity = await readTabIdentity(orcaPage, firstTabId)
await writeDenseStaticFrame(
orcaPage,
firstIdentity.ptyId,
path.join(testRepoPath, 'orca-overlap-static-a.mjs'),
'ORCA_DETERMINISTIC_A'
)
await expect
.poll(() => getTerminalContent(orcaPage, 12_000), {
timeout: 20_000,
message: 'tab A static frame did not reach the visible terminal'
})
.toContain('ORCA_DETERMINISTIC_A row 21')
await resetAndRefreshTab(orcaPage, firstTabId)
const baseline = await captureTabScreen(orcaPage, firstTabId)
const activeCorruptedTiles = await corruptTabAtlas(orcaPage, firstTabId)
test.skip(activeCorruptedTiles === 0, 'Could not inject WebGL atlas corruption')
const activeCorruption = await captureTabScreen(orcaPage, firstTabId)
expect(
compareTerminalScreenshots(baseline, activeCorruption).matches,
'atlas corruption precondition should visibly change active tab glyphs'
).toBe(false)
await resetAndRefreshTab(orcaPage, firstTabId)
const restored = await captureTabScreen(orcaPage, firstTabId)
expect(compareTerminalScreenshots(baseline, restored).matches).toBe(true)
await activateTerminalTab(orcaPage, thirdTabId)
const thirdWebgl = await waitForWebglOnTab(orcaPage, thirdTabId)
const thirdIdentity = await readTabIdentity(orcaPage, thirdTabId)
await activateTerminalTab(orcaPage, secondTabId)
const secondWebgl = await waitForWebglOnTab(orcaPage, secondTabId)
test.skip(
!firstWebgl || !secondWebgl || !thirdWebgl,
'WebGL did not attach on all regular terminal tabs'
)
await instrumentTabResetCounters(orcaPage, [firstTabId, secondTabId, thirdTabId])
await stretchFiveHundredMillisecondTimeouts(orcaPage)
try {
await injectHiddenTuiFrame(orcaPage, thirdIdentity, 'ORCA_HIDDEN_OUTPUT_RECOVERY_IN_FLIGHT')
// Why: the regression is the output-recovery latch suppressing reveal
// recovery. Prove the hidden-output burst reached its rAF and 120ms passes
// before reveal, while its old 500ms fallback is stretched out of range.
await expect
.poll(() => readTabResetCount(orcaPage, thirdTabId), {
timeout: 2_000,
message: 'hidden-output recovery did not fire its first two reset passes'
})
.toBeGreaterThanOrEqual(2)
} finally {
await restoreFiveHundredMillisecondTimeouts(orcaPage)
}
const hiddenOutputResetCount = await readTabResetCount(orcaPage, thirdTabId)
expect(hiddenOutputResetCount).toBeGreaterThanOrEqual(2)
expect(
hiddenOutputResetCount,
'hidden-output fallback fired before the reveal precondition could be exercised'
).toBeLessThanOrEqual(2)
const hiddenOutputResetWindow = await readTabResetSnapshot(orcaPage, thirdTabId)
const resetCountBeforeSuppressedReveal = await readTabResetCount(orcaPage, firstTabId)
expect(resetCountBeforeSuppressedReveal).toBeGreaterThanOrEqual(2)
expect(
resetCountBeforeSuppressedReveal,
'hidden-output fallback fired before tab reveal'
).toBeLessThanOrEqual(2)
let suppressedReveal: Buffer | null = null
try {
await setVisibilityRecoverySuppressed(orcaPage, true)
await activateTerminalTab(orcaPage, firstTabId)
await waitForTwoAnimationFrames(orcaPage)
const staleCorruptedTiles = await corruptTabAtlas(orcaPage, firstTabId)
expect(staleCorruptedTiles).toBeGreaterThan(0)
suppressedReveal = await captureTabScreen(orcaPage, firstTabId)
const suppressedRevealDiff = compareTerminalScreenshots(baseline, suppressedReveal)
expect(
suppressedRevealDiff.matches,
'simulated stale returned-tab pixels must be visible when recovery is suppressed'
).toBe(false)
await activateTerminalTab(orcaPage, secondTabId)
await waitForTwoAnimationFrames(orcaPage)
} finally {
await setVisibilityRecoverySuppressed(orcaPage, false)
}
const resetCountBeforeReveal = await readTabResetCount(orcaPage, firstTabId)
expect(resetCountBeforeReveal).toBe(resetCountBeforeSuppressedReveal)
expect(resetCountBeforeReveal).toBeGreaterThanOrEqual(2)
expect(
resetCountBeforeReveal,
'hidden-output fallback fired before tab reveal'
).toBeLessThanOrEqual(2)
const revealStartedAt = await orcaPage.evaluate(() => performance.now())
expect(
revealStartedAt - hiddenOutputResetWindow.latestAt,
'tab reveal started too close to the stretched hidden-output fallback to prove independent recovery'
).toBeLessThan(STRETCHED_HIDDEN_OUTPUT_FALLBACK_MS - REVEAL_RECOVERY_TIMEOUT_MS)
await activateTerminalTab(orcaPage, firstTabId)
await expect
.poll(() => readTabResetCount(orcaPage, firstTabId), {
timeout: REVEAL_RECOVERY_TIMEOUT_MS,
message: 'tab reveal did not schedule an independent WebGL recovery reset'
})
.toBeGreaterThan(resetCountBeforeReveal)
const resetAfterReveal = await readTabResetSnapshot(orcaPage, firstTabId)
expect(
resetAfterReveal.latestAt - revealStartedAt,
'reveal recovery must beat the old hidden-output 500ms fallback reset'
).toBeLessThan(REVEAL_RECOVERY_TIMEOUT_MS)
expect(
resetAfterReveal.latestAt - hiddenOutputResetWindow.latestAt,
'post-reveal reset must occur before the hidden-output 500ms fallback could explain it'
).toBeLessThan(STRETCHED_HIDDEN_OUTPUT_FALLBACK_MS)
await waitForTwoAnimationFrames(orcaPage)
const afterReveal = await captureTabScreen(orcaPage, firstTabId)
const afterRevealDiff = compareTerminalScreenshots(baseline, afterReveal)
await attachArtifact(testInfo, 'deterministic-baseline.png', baseline)
await attachArtifact(
testInfo,
'deterministic-suppressed-reveal.png',
suppressedReveal ?? Buffer.from('')
)
await attachArtifact(testInfo, 'deterministic-after-reveal.png', afterReveal)
await attachArtifact(
testInfo,
'deterministic-reset-counts.txt',
`beforeReveal=${resetCountBeforeReveal}\nafterReveal=${resetAfterReveal.count}\nrevealDelayMs=${resetAfterReveal.latestAt - revealStartedAt}\nhiddenSecondResetToRevealMs=${revealStartedAt - hiddenOutputResetWindow.latestAt}\nstretchedHiddenOutputFallbackMs=${STRETCHED_HIDDEN_OUTPUT_FALLBACK_MS}\n`
)
expect(
afterRevealDiff.matches,
`returned tab still had stale WebGL pixels after reveal: ${afterRevealDiff.diffPixels} px (${(afterRevealDiff.diffRatio * 100).toFixed(2)}%)`
).toBe(true)
})
test('real Claude sessions do not leave refresh-repairable stale pixels', async ({
orcaPage
}, testInfo) => {
test.skip(
process.env.ORCA_E2E_REAL_CLAUDE_OVERLAP_REPRO !== '1',
'Set ORCA_E2E_REAL_CLAUDE_OVERLAP_REPRO=1 to spend real Claude Code tokens on this repro'
)
test.setTimeout(420_000)
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
await setTerminalGpuOn(orcaPage)
const { firstTabId, secondTabId } = await ensureTwoTerminalTabs(orcaPage)
await activateTerminalTab(orcaPage, firstTabId)
const firstWebgl = await waitForWebglOnTab(orcaPage, firstTabId)
const firstIdentity = await readTabIdentity(orcaPage, firstTabId)
await activateTerminalTab(orcaPage, secondTabId)
const secondWebgl = await waitForWebglOnTab(orcaPage, secondTabId)
const secondIdentity = await readTabIdentity(orcaPage, secondTabId)
test.skip(!firstWebgl || !secondWebgl, 'WebGL did not attach on both regular terminal tabs')
await startClaudeEditSession(
orcaPage,
firstIdentity.ptyId,
'ORCA_REAL_CLAUDE_A',
'orca-real-claude-a'
)
await startClaudeEditSession(
orcaPage,
secondIdentity.ptyId,
'ORCA_REAL_CLAUDE_B',
'orca-real-claude-b'
)
const reports: string[] = []
for (let cycle = 0; cycle < REAL_CLAUDE_PROBE_CYCLES; cycle += 1) {
const tabId = cycle % 2 === 0 ? firstTabId : secondTabId
await activateTerminalTab(orcaPage, tabId)
await orcaPage.waitForTimeout(120)
const repair = await refreshActiveTabAndCompare(orcaPage, tabId)
if (repair.bufferUnchanged && repair.diffRatio > REFRESH_REPAIR_DIFF_RATIO) {
reports.push(
`cycle ${cycle} tab=${tabId} refresh changed ${(repair.diffRatio * 100).toFixed(2)}% of pixels without buffer change (${repair.diffPixels} px)`
)
await attachArtifact(
testInfo,
`real-claude-before-refresh-${cycle}.png`,
repair.beforeScreenshot
)
await attachArtifact(
testInfo,
`real-claude-after-refresh-${cycle}.png`,
repair.afterScreenshot
)
await attachArtifact(testInfo, `real-claude-buffer-${cycle}.txt`, repair.beforeText)
break
}
await orcaPage.waitForTimeout(500)
}
expect(
reports,
reports.length > 0
? `real Claude terminal surface had refresh-repairable stale pixels:\n${reports.join('\n')}`
: undefined
).toEqual([])
})
})