Preserve viewport/context when exiting transcript overlay

Resolved 💬 6 comments Opened Mar 9, 2026 by dikelps Closed May 31, 2026

What variant of Codex are you using?

CLI

What feature would you like to see?

Exiting transcript mode currently leaves me disoriented because the main TUI appears reset/cleared and the composer is placed at the top, so I lose visual continuity with the conversation I was just reviewing.

Request:

  • restore the prior viewport/scroll position when closing transcript mode
  • ~~keep the composer in its normal anchored position~~

This is particularly important for long sessions and multi-agent workflows, where user checks different window and need the context for the conversation.

Additional information

_No response_

View original on GitHub ↗

6 Comments

mvanhorn · 4 months ago

Analysis

I traced the transcript overlay lifecycle through the TUI code.

How it works today:

  • codex-rs/tui/src/pager_overlay.rs renders the transcript overlay (Ctrl+T) with its own scroll_offset: usize (line 137)
  • The overlay takes over the full terminal area when open
  • codex-rs/tui/src/chatwidget.rs manages the main conversation scroll state separately
  • When the overlay closes, the main chat widget re-renders but doesn't restore the prior scroll position

Root cause: There's no save/restore of the main viewport's scroll offset around the overlay open/close lifecycle. The overlay has its own scroll state, but the main conversation view's position isn't preserved when the overlay takes over and then releases the terminal.

The scroll infrastructure exists: codex-rs/tui/src/bottom_pane/scroll_state.rs is a reusable ScrollState module used by multiple widgets (list_selection_view, multi_select_picker, file_search_popup, etc.). The chatwidget likely has its own scroll tracking too.

Proposed Fix

Save/restore the chatwidget's scroll position around overlay transitions:

// Before opening overlay (in App-level overlay handler)
self.saved_scroll_offset = Some(self.chat_widget.scroll_offset());

// After closing overlay
if let Some(offset) = self.saved_scroll_offset.take() {
    self.chat_widget.set_scroll_offset(offset);
}

This is a small, targeted change - just adding a save field and two assignment points. The scroll offset getter/setter would need to be exposed on the chatwidget if not already public.

This is especially valuable for long sessions and multi-agent workflows (as the reporter notes). Happy to submit a PR if invited.

dagelf · 3 months ago

Just re-rendering the scrollback buffer is a potential solution to this: https://github.com/dagelf/codex/tree/displayfix

oxysoft · 2 months ago

Related: #21945 — the transcript overlay has O(n) rendering that rebuilds all cells from scratch on open/close. The viewport disorientation on exit is partly a consequence of this architecture: since the overlay doesn't track a mapping between scroll position and the underlying terminal scrollback, it can't restore viewport position. Virtualized rendering with a spatial index would make viewport preservation tractable.

dikelps · 1 month ago

Closing as now TUI does not clear screen when transcript is toggled.

Jurio0304 · 1 month ago

I ran into the same issue during long terminal sessions, especially when using Codex over SSH and inside tmux.

I implemented a lightweight community fork called Codex Sticky that adds this sticky-on-scroll composer behavior:

https://github.com/Jurio0304/codex-sticky

When scrolling back through older transcript content, the composer remains reachable at the bottom of the terminal. The fork is installed side by side as codex-sticky, so it does not overwrite the official codex command.

It is currently intended as a small, unofficial workaround for Linux x86_64 users while keeping the experience close to upstream. Feedback is very welcome.

dikelps · 1 month ago

@Jurio0304 I crossed out the anchored composer part from the main content, because it likely is not obvious from the issue title. It will likely require a whole new TUI mode for that so you can open an new topic if needed.