mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
fix(renderer): clear xterm render pause before the reveal fit
RenderService.handleResize branches purely on _isPaused: latched, it parks the renderer resize in _pausedResizeTask while the css box moves, so the canvas composites its pre-hide backing store into the new box. Clearing the latch before safeFit sends terminal.resize down the inline branch, where the backing store, css box and dimensions are written inside xterm's own resize path and its own _fullRefresh() follows. Unlike the reverted 60bfa6bed0, this owns no repaint and leaves both of xterm's safety nets armed: _needsFullRefresh is untouched, and no refreshRows/refresh is issued, so the intersection observer can still recover the frame. Raster campaign, 20 pressure tabs + splits, static fixture: blur reproduced in 6/6 warm arms on HEAD, 0/6 with this change, controls detector-confirmed and blank-pane clean. Does not address the separate window where a TUI holds DEC 2026 across the reveal; xterm buffers all repaints while sync is set. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
readFitClientSize,
|
||||
safeFit
|
||||
} from './pane-fit'
|
||||
import { releaseRenderPauseLatch } from './terminal-render-pause-release'
|
||||
import { requestStablePaneFit } from './pane-fit-resize-observer'
|
||||
import { clearPaneFitContinuationRetry } from './pane-fit-continuation-retry'
|
||||
import { resumePendingFitScrollRestoreAfterFit } from './pane-scroll'
|
||||
@@ -62,6 +63,12 @@ function releaseMeasurableFitContinuations(pane: ManagedPane): void {
|
||||
// mismatch refits but a transient metric wobble does not reflow;
|
||||
// - grid already correct → leave it alone.
|
||||
export function fitRevealedPane(pane: ManagedPane): void {
|
||||
// Why before the fit, not after: while the render service is latched, every
|
||||
// resize below parks the renderer half and composites the pre-hide backing
|
||||
// store into the new box. Measurable only — a hidden pane must stay paused.
|
||||
if (canMeasurePaneForFit(pane)) {
|
||||
releaseRenderPauseLatch(pane.terminal)
|
||||
}
|
||||
// Why first: the checks below can both say "nothing to do" and return without
|
||||
// fitting, stranding metric options parked while the pane was unmeasurable.
|
||||
const flushed = flushDeferredPaneMetricOptionsIfMeasurable(pane)
|
||||
|
||||
@@ -41,6 +41,30 @@ function getRenderService(terminal: unknown): PausableRenderService | null {
|
||||
: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the pause latch only — no repaint, no other state touched.
|
||||
*
|
||||
* Why: `RenderService.handleResize` branches purely on `_isPaused`. Paused, it
|
||||
* parks the renderer resize in `_pausedResizeTask`, so a reveal fit resizes the
|
||||
* css box while the canvas keeps compositing its pre-hide backing store — the
|
||||
* blurry reveal. Unlatched first, the same fit's `terminal.resize` takes the
|
||||
* inline branch: backing store, css box and dimensions are all written inside
|
||||
* xterm's own resize path, and xterm's own `_fullRefresh()` follows it.
|
||||
*
|
||||
* Deliberately leaves `_needsFullRefresh` armed and drives no repaint of its
|
||||
* own: xterm's observer recovery is what repaints if this frame doesn't, and
|
||||
* owning the repaint here is what made the previous attempt paint nothing.
|
||||
* Returns true when it cleared the latch.
|
||||
*/
|
||||
export function releaseRenderPauseLatch(terminal: unknown): boolean {
|
||||
const service = getRenderService(terminal)
|
||||
if (!service || service._isPaused !== true) {
|
||||
return false
|
||||
}
|
||||
service._isPaused = false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* If xterm's renderer is paused (observer hasn't caught up to the reveal),
|
||||
* clear the pause latch and force a synchronous full-viewport repaint.
|
||||
|
||||
Reference in New Issue
Block a user