Skip to content

fix(terminal): skip scroll restore on alt-buffer after split to avoid TUI cursor drift#1413

Open
Jinwoo-H wants to merge 1 commit intomainfrom
Jinwoo-H/tui-cursor-drift-alt-buffer-guard
Open

fix(terminal): skip scroll restore on alt-buffer after split to avoid TUI cursor drift#1413
Jinwoo-H wants to merge 1 commit intomainfrom
Jinwoo-H/tui-cursor-drift-alt-buffer-guard

Conversation

@Jinwoo-H
Copy link
Copy Markdown
Contributor

@Jinwoo-H Jinwoo-H commented May 4, 2026

Summary

Targeted follow-up to #1298. When a full-screen TUI (Claude Code, vim, less) is drawing at the moment a split completes, scheduleSplitScrollRestore's 32ms and 200ms phases race the TUI's output — typed input lands one row below where the TUI expects and a residual fragment is left at the old cursor row.

The fix gates the scroll restore + refresh pair behind buffer.active.type. On alt-buffer we skip it (the TUI owns the screen, and alt-buffer has no scrollback, so scroll restore has nothing legitimate to do). WebGL reattach still fires on every path, so #1298's dead-canvas-after-split fix is preserved.

What stays intact from #1298

  • Dead-terminal-after-split (blank WebGL canvas): ✅ WebGL reattach still runs on alt-buffer
  • Worktree duplicate-key race: ✅ untouched
  • Normal-buffer scroll preservation: ✅ untouched

Evidence

Bisected against origin/main. Bug reproduces at HEAD, persists at 8ae11955 (parent of #1363) and 5dc7fbda (parent of #1344), and is absent at be0e3c2f (parent of #1298). Introduction: b4f1f0ea (#1298).

Root cause in pane-split-scroll.ts::scheduleSplitScrollRestore:

  • forceViewportScrollbarSync does scrollLines(-1); scrollLines(1) mid-draw
  • refresh(0, rows-1) repaints rows from the xterm buffer, racing the TUI's next write

Test plan

  • pnpm vitest run src/renderer/src/lib/pane-manager/ — 76/76 pass (including 3 new tests)
  • pnpm typecheck — clean
  • pnpm lint — clean on changed files
  • Manual: Claude Code in a terminal pane, split + type — cursor lands on correct row, no rt-style residual fragment
  • CI green
  • Verify dead-terminal-after-split (the original fix(terminal): prevent dead terminal after split via WebGL lifecycle and worktree dedup #1298 symptom) still fixed by opening a worktree that previously triggered it

Made with Orca 🐋

… TUI cursor drift

PR #1298 schedules scroll-restore + terminal.refresh(0, rows-1) at ~32ms
and 200ms after a split to preserve scrollback through DOM reparenting.
When a full-screen TUI (Claude Code, vim, less) is drawing at that
moment, forceViewportScrollbarSync's scrollLines(-1)/(+1) jiggle and the
buffer repaint race the TUI's next write — the TUI's cursor lands one
row below where it expects and a residual fragment is left at the old
row.

Alt-buffer has no scrollback, so scroll restore has nothing legitimate
to do there. Gate the restore + refresh behind buffer.active.type, but
keep the WebGL reattach callback firing so #1298's dead-canvas fix is
preserved. Normal-buffer splits are unchanged.

Bisected against origin/main: bug appears at b4f1f0e (#1298), absent at
its parent be0e3c2.

Co-authored-by: Orca <help@stably.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant