Skip to content

fix(terminal): place cursor correctly after restart for persistent TUIs#1324

Open
Jinwoo-H wants to merge 1 commit intomainfrom
Jinwoo-H/cursor-bug
Open

fix(terminal): place cursor correctly after restart for persistent TUIs#1324
Jinwoo-H wants to merge 1 commit intomainfrom
Jinwoo-H/cursor-bug

Conversation

@Jinwoo-H
Copy link
Copy Markdown
Contributor

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

Summary

  • Append an absolute CSI row;col H cursor positioner after SerializeAddon output in both capture sites (renderer captureBuffers, daemon HeadlessEmulator.getSnapshot). SerializeAddon's relative cursor-move tail drifts when a TUI drew rows below the cursor (border/status lines) and parked the cursor back on an input prompt above — on replay the cursor lands one row below the prompt.
  • Remove the alt-screen trim and the unconditional \r\n append in restoreScrollbackBuffers (both contributed to the drift) and document the pre-paint/authoritative-reattach contract on the function.
  • Design doc in docs/design/fix-persistent-terminal-cursor-after-restart.md captures the two restore paths, root cause, and chosen fix.

Test plan

  • pnpm testsrc/main/daemon/headless-emulator.test.ts (23 pass, +2 new) and src/renderer/src/components/terminal-pane/layout-serialization.test.ts (26 pass, +3 new)
  • Full terminal-pane suite (180 pass)
  • Full daemon suite (303 pass; 3 pre-existing unrelated shell-ready failures unchanged from baseline)
  • pnpm tsc --noEmit clean
  • Manual: restart Orca with Claude Code running, confirm cursor lands on the input prompt row on restore
  • Manual: restart with vim/lazygit in alt-screen — cursor lands where it was
  • Manual: restart with shell at a mid-line prompt (no TUI) — cursor lands mid-line, no spurious newline

Made with Orca 🐋

SerializeAddon emits a relative cursor-move tail (\x1b[NA\x1b[MD)
computed from the last content cell it serialized to the terminal's real
cursor. That math drifts when a TUI drew rows below the cursor (border,
status lines) and then parked the cursor back on an input prompt above
— on replay the cursor lands one row below the prompt.

Append an authoritative absolute CSI row;col H after SerializeAddon
output at both capture sites (renderer captureBuffers and daemon
HeadlessEmulator.getSnapshot) using the terminal's live
buffer.active.cursorY/cursorX. The absolute positioner is the last
cursor-move seen on replay, overriding SerializeAddon's drifty relative
tail.

Also remove two cooperating bugs in restoreScrollbackBuffers and
document the contract:

  1. Alt-screen trim (slice(0, lastIndexOf('\x1b[?1049h'))) discarded
     not only alt-buffer content but the cursor-position tail, mode
     bits, and scroll region — fueling the "cursor lands below input
     box" bug for Claude Code, vim, lazygit.
  2. Unconditional trailing \r\n append for PROMPT_EOL_MARK pushed the
     cursor one extra row down on top of the alt-trim drift.

The reattach path in pty-connection.ts is authoritative when it has
data; restoreScrollbackBuffers is the pre-paint and must replay the
serialized buffer verbatim + POST_REPLAY_MODE_RESET only.

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