mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
fix: prevent stale terminal buffer flash on worktree switch (#654)
This commit is contained in:
parent
033b8bcc31
commit
901c7335a2
1 changed files with 15 additions and 18 deletions
|
|
@ -36,9 +36,9 @@ export function useTerminalPaneGlobalEffects({
|
|||
// function can cancel it if the pane deactivates mid-flush.
|
||||
const pendingFlushRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
// Why: the deferred rAF (guardedResumeAndFit) must be cancellable when
|
||||
// the pane deactivates before the rAF fires — otherwise it would call
|
||||
// resumeRendering() on an already-suspended manager.
|
||||
// Why: the deferred rAF (guardedFit) must be cancellable when the pane
|
||||
// deactivates before the rAF fires — otherwise it would call
|
||||
// fitAndFocusPanes() on a suspended manager.
|
||||
const pendingRafRef = useRef<number | null>(null)
|
||||
|
||||
// Why: two independent code paths schedule fitPanes() after a worktree
|
||||
|
|
@ -58,12 +58,13 @@ export function useTerminalPaneGlobalEffects({
|
|||
return
|
||||
}
|
||||
if (isActive) {
|
||||
// Why: resumeRendering() creates WebGL contexts for each pane, which
|
||||
// blocks the renderer for 100–500 ms per pane on Windows (ANGLE →
|
||||
// D3D11). Deferring it into the rAF that runs after the pending-write
|
||||
// drain lets the browser paint one frame with the DOM renderer so the
|
||||
// terminal content appears immediately. WebGL takes over seamlessly
|
||||
// in the next frame without a visible flash.
|
||||
// Why: resume WebGL immediately so the terminal shows its last-known
|
||||
// state on the first painted frame. On macOS, WebGL context creation
|
||||
// is ~5 ms — fast enough to feel instant. On Windows (ANGLE → D3D11)
|
||||
// it can take 100–500 ms, but the alternative (deferring to a rAF
|
||||
// after the pending-write drain) leaves the terminal blank for multiple
|
||||
// frames, which is a worse UX tradeoff.
|
||||
manager.resumeRendering()
|
||||
|
||||
fitEpochRef.current++
|
||||
const epoch = fitEpochRef.current
|
||||
|
|
@ -89,16 +90,12 @@ export function useTerminalPaneGlobalEffects({
|
|||
pendingWritesRef.current.set(paneId, '')
|
||||
}
|
||||
|
||||
const guardedResumeAndFit = (): void => {
|
||||
const guardedFit = (): void => {
|
||||
pendingRafRef.current = null
|
||||
// Why: read managerRef.current at rAF time instead of capturing
|
||||
// it at effect entry — the PaneManager instance can change if the
|
||||
// component unmounts and remounts during rapid tab switches.
|
||||
const mgr = managerRef.current
|
||||
if (!mgr) {
|
||||
return
|
||||
}
|
||||
mgr.resumeRendering()
|
||||
// Why: three-layer guard prevents redundant and stale fits.
|
||||
// 1. Staleness — reject callbacks from a superseded activation
|
||||
// (e.g. rapid A→B→C worktree switch).
|
||||
|
|
@ -117,7 +114,7 @@ export function useTerminalPaneGlobalEffects({
|
|||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
pendingRafRef.current = requestAnimationFrame(guardedResumeAndFit)
|
||||
pendingRafRef.current = requestAnimationFrame(guardedFit)
|
||||
} else {
|
||||
let entryIdx = 0
|
||||
let offset = 0
|
||||
|
|
@ -125,7 +122,7 @@ export function useTerminalPaneGlobalEffects({
|
|||
const drainNextChunk = (): void => {
|
||||
if (entryIdx >= entries.length) {
|
||||
pendingFlushRef.current = null
|
||||
pendingRafRef.current = requestAnimationFrame(guardedResumeAndFit)
|
||||
pendingRafRef.current = requestAnimationFrame(guardedFit)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +156,8 @@ export function useTerminalPaneGlobalEffects({
|
|||
clearTimeout(pendingFlushRef.current)
|
||||
pendingFlushRef.current = null
|
||||
}
|
||||
// Cancel any pending rAF so guardedResumeAndFit doesn't call
|
||||
// resumeRendering() on an already-suspended manager.
|
||||
// Cancel any pending rAF so guardedFit doesn't run on a
|
||||
// suspended manager.
|
||||
if (pendingRafRef.current !== null) {
|
||||
cancelAnimationFrame(pendingRafRef.current)
|
||||
pendingRafRef.current = null
|
||||
|
|
|
|||
Loading…
Reference in a new issue