fix(terminal): reduce resize flicker#1968
Conversation
|
Let's gooo |
Greptile SummaryThis PR eliminates terminal flicker during panel/sidebar resizes through three coordinated mechanisms: removing
Confidence Score: 5/5Safe to merge — changes are well-scoped to the resize/flicker path and preserve all existing drag-end measurement invariants. The suppression logic correctly avoids spurious notifications via No files require special attention.
|
| Filename | Overview |
|---|---|
| src/renderer/lib/pty/use-pty.ts | Removes the requestAnimationFrame wrapper from measureAndResize (now synchronous so xterm DOM catches up before paint) and adds leading-edge + trailing-edge debounce in the ResizeObserver callback. |
| src/renderer/lib/layout/panel-drag-store.ts | Extends the store with a time-based suppressFor(ms) mechanism alongside the existing drag-bit, using notifyIfChanged to avoid spurious listener calls; logic is correct. |
| src/renderer/features/tasks/main-panel.tsx | Extracts drag logic into reusable DraggableResizeHandle, adds suppressFor(140) before programmatic panel toggles, and now correctly forwards caller-provided pointer handlers. |
| src/renderer/lib/layout/layout-provider.tsx | Adds suppressFor(140) before programmatic sidebar collapse/expand (cmd+B) so ResizeObserver events during the burst are suppressed and one resize fires after layout settles. |
| src/renderer/lib/pty/pty.ts | Drops CanvasAddon and adds an inline comment explaining the tradeoff; renderer falls back to xterm's DOM renderer which avoids full-canvas repaints on resize. |
Sequence Diagram
sequenceDiagram
participant U as User (cmd+B / drag)
participant LP as layout-provider / main-panel
participant PDS as panelDragStore
participant RO as ResizeObserver
participant MP as measureAndResize
note over U,MP: Programmatic toggle (cmd+B / cmd+J)
U->>LP: trigger collapse/expand
LP->>PDS: suppressFor(140ms)
PDS-->>RO: "isPanelDragging = true (via useSyncExternalStore)"
LP->>LP: panel.collapse() / panel.expand()
LP->>RO: layout changes → ResizeObserver fires
RO->>PDS: isPanelDraggingRef.current? → true → skip
note over PDS: 140ms elapses
PDS-->>RO: "isPanelDragging = false"
PDS->>MP: drag-end useEffect → measureAndResize()
note over U,MP: Manual drag
U->>LP: pointerdown on DraggableResizeHandle
LP->>PDS: setDragging(true)
PDS-->>RO: "isPanelDragging = true"
U->>LP: (dragging — many resize events)
RO->>PDS: isPanelDraggingRef.current? → true → skip
U->>LP: pointerup
LP->>PDS: setDragging(false)
PDS-->>RO: "isPanelDragging = false"
PDS->>MP: drag-end useEffect → measureAndResize()
note over U,MP: Continuous window resize (burst)
U->>RO: ResizeObserver fires (first in quiet period)
RO->>MP: leading-edge → fireResize()
U->>RO: ResizeObserver fires (within 150ms)
RO->>RO: schedule trailing timer (50ms)
note over RO: 50ms elapses
RO->>MP: trailing → fireResize()
Reviews (2): Last reviewed commit: "fix(terminal): address resize review com..." | Re-trigger Greptile
| <ResizableHandle | ||
| {...props} | ||
| onPointerDown={(e) => { | ||
| e.currentTarget.setPointerCapture(e.pointerId); | ||
| if (!draggingRef.current) { | ||
| draggingRef.current = true; | ||
| panelDragStore.setDragging(true); | ||
| } | ||
| }} | ||
| onPointerUp={stop} | ||
| onPointerCancel={stop} | ||
| /> |
There was a problem hiding this comment.
The component spreads
props first and then unconditionally overrides onPointerDown, onPointerUp, and onPointerCancel. Any caller that passes those handlers (or any future wrapper that enriches them) will have them silently dropped. Merging caller-provided handlers alongside the internal ones avoids the footgun.
| <ResizableHandle | |
| {...props} | |
| onPointerDown={(e) => { | |
| e.currentTarget.setPointerCapture(e.pointerId); | |
| if (!draggingRef.current) { | |
| draggingRef.current = true; | |
| panelDragStore.setDragging(true); | |
| } | |
| }} | |
| onPointerUp={stop} | |
| onPointerCancel={stop} | |
| /> | |
| <ResizableHandle | |
| {...props} | |
| onPointerDown={(e) => { | |
| props.onPointerDown?.(e); | |
| e.currentTarget.setPointerCapture(e.pointerId); | |
| if (!draggingRef.current) { | |
| draggingRef.current = true; | |
| panelDragStore.setDragging(true); | |
| } | |
| }} | |
| onPointerUp={(e) => { props.onPointerUp?.(e); stop(); }} | |
| onPointerCancel={(e) => { props.onPointerCancel?.(e); stop(); }} | |
| /> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/features/tasks/main-panel.tsx
Line: 121-132
Comment:
The component spreads `props` first and then unconditionally overrides `onPointerDown`, `onPointerUp`, and `onPointerCancel`. Any caller that passes those handlers (or any future wrapper that enriches them) will have them silently dropped. Merging caller-provided handlers alongside the internal ones avoids the footgun.
```suggestion
<ResizableHandle
{...props}
onPointerDown={(e) => {
props.onPointerDown?.(e);
e.currentTarget.setPointerCapture(e.pointerId);
if (!draggingRef.current) {
draggingRef.current = true;
panelDragStore.setDragging(true);
}
}}
onPointerUp={(e) => { props.onPointerUp?.(e); stop(); }}
onPointerCancel={(e) => { props.onPointerCancel?.(e); stop(); }}
/>
```
How can I resolve this? If you propose a fix, please make it concise.|
@greptile |
|
nice! only thing would be performance tradeoffs when switching from canvas renderer to dom renderer for xterm. wdyt @Davidknp |

summary
fixes the terminal flickering when resizing (sidebar or terminal drawer)
Screen.Recording.2026-05-11.at.14.59.06.mov