Hidden Codex terminal can jump to scrollback top after reveal snapshot replay
- Dominant language
- TypeScript
- Stars
- 71.3k
- Forks
- 4.7k
- Avg merge
- 14h 54m
- Merged PRs (30d)
- 520
Description
## Summary
In Orca 1.4.138 on macOS, a floating Codex terminal that has been hidden for a while can jump abruptly to the very top of its normal-buffer scrollback shortly after being revealed.
The terminal is initially interactive after reveal: wheel/trackpad scrolling works normally for a short period, then the viewport jumps to line 0 without a keyboard shortcut.
## Reproduction
1. Run Codex TUI in a floating terminal.
2. Hide or switch away from that terminal long enough for hidden Codex output to accumulate.
3. Reveal the terminal.
4. Scroll up/down with a mouse wheel or macOS trackpad.
5. After hidden-output snapshot replay finishes, the viewport may jump to the top.
The long-hidden condition is important; normal scrolling in a continuously visible terminal does not reproduce it.
## Root cause
applyMainBufferSnapshot() captures the scroll intent, queues several asynchronous xterm writes (including ED2 + ED3 + CUP and the serialized snapshot), then calls enforceTerminalWriteScrollIntent() immediately:
- src/renderer/src/components/terminal-pane/pty-connection.ts:5985 captures the intent.
- :6013 queues the normal-buffer clear.
- :6028 queues the snapshot.
- :6067 restores the viewport before xterm has parsed those writes.
xterm.write() parses asynchronously. After the premature restore runs against the old buffer, the queued clear/replay can later leave the reconstructed buffer at viewport line 0.
The normal foreground-output path already avoids this race: writeForegroundTerminalChunkWithIntent() enforces scroll intent from its onParsed callback (pane-terminal-output-scheduler.ts:863-872). Snapshot replay bypasses that ordering.
## Minimal reproduction
Using Orca current scroll-intent code with real @xterm/headless:
```text
immediate after replay writes are queued: viewportY=76, baseY=101, parsed=false
after xterm parsing completes: viewportY=0, baseY=501, parsed=true
```
Moving the same enforcement into the final write callback preserves viewportY=76 with baseY=501.
## Test gap
The existing test "preserves a scrolled-up viewport after hidden-backlog snapshot replay" makes the terminal write callback synchronous (pty-connection.test.ts:11571-11575). It therefore passes even though real xterm parsing is asynchronous.
## Expected behavior
Hidden snapshot replay is an internal content-recovery operation and should preserve the conventional terminal follow-output / scroll-lock state machine:
- If the viewport is at the bottom (`followOutput`), replay should finish at the latest output bottom.
- Once wheel/trackpad/scrollbar navigation moves the viewport away from the bottom (`pinnedViewport`), replay and new output must not steal that historical viewport.
- Scrolling back to the bottom should restore `followOutput`.
- Replay may refresh content and grow the scrollback range. It must not independently change the follow/pinned state.
- If the exact historical anchor was pruned, clamp to the nearest retained content; do not reset to line 0 merely because replay parsing completed later.
This matches common terminal behavior: output follows only while the viewport is at the bottom; explicit history navigation engages scroll lock. Typing/input-driven "snap to bottom" behavior is a separate policy and is not part of this bug.
## Suggested fix
- Finish parsing the replay writes before enforcing scroll intent; do not enforce against the pre-replay buffer.
- At replay completion, honor the latest scroll-intent generation. A wheel/trackpad/scrollbar action that occurred while recovery was in flight must win over an older captured intent.
- Preserve a logical history anchor where possible instead of blindly transferring an absolute `viewportY` across a cleared/rebuilt buffer.
- Add asynchronous regression coverage for:
- `followOutput` remaining at the latest bottom;
- `pinnedViewport` retaining history;
- user navigation away from and back to the bottom while replay callbacks are pending.
## Environment
- Orca 1.4.138
- macOS 26.5.1 (Apple Silicon)
- Codex CLI 0.144.5
- xterm normal buffer (alternateScreen=false, mouse tracking disabled)
Contributor guide
Assessment
This issue has not been assessed yet.