stablyai / stablyai/orca

Hidden Codex terminal can jump to scrollback top after reveal snapshot replay

Open
#9,079 5 comments 0 reactions 1 assignee Claimed by @OrcaWin View on GitHub
bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.