tui: allow forward navigation in backtrack preview (#9059)

Fixes #9058

## Summary
When the transcript backtrack preview is armed (press `Esc`), allow
navigating to newer user messages with the `→` arrow, in addition to
navigating backwards with `Esc`/`←`, before confirming with `Enter`.

## Changes
- Backtrack preview navigation: `Esc`/`←` steps to older user messages,
`→` steps to newer ones, `Enter` edits the selected message (clamped at
bounds, no wrap-around).
- Transcript overlay footer hints updated to advertise `esc/←`, `→`, and
`enter` when a message is highlighted.

## Related
- WSL shortcut-overlay snapshot determinism: #9359

## Testing
- `just fmt`
- `just fix -p codex-tui`
- `just fix -p codex-tui2`
- `cargo test -p codex-tui app_backtrack::`
- `cargo test -p codex-tui pager_overlay::`
- `cargo test -p codex-tui2 app_backtrack::`
- `cargo test -p codex-tui2 pager_overlay::`

---------

Co-authored-by: Josh McKinney <joshka@openai.com>
This commit is contained in:
SlKzᵍᵐ
2026-01-18 05:10:24 +01:00
committed by GitHub
parent aeaff26451
commit 0a568a47fd
4 changed files with 166 additions and 28 deletions

View File

@@ -93,8 +93,9 @@ pub(crate) struct PendingBacktrackRollback {
impl App {
/// Route overlay events while the transcript overlay is active.
///
/// If backtrack preview is active, Esc steps the selection and Enter confirms it.
/// Otherwise, Esc begins preview mode and all other events are forwarded to the overlay.
/// If backtrack preview is active, Esc / Left steps selection, Right steps forward, Enter
/// confirms. Otherwise, Esc begins preview mode and all other events are forwarded to the
/// overlay.
pub(crate) async fn handle_backtrack_overlay_event(
&mut self,
tui: &mut tui::Tui,
@@ -110,6 +111,22 @@ impl App {
self.overlay_step_backtrack(tui, event)?;
Ok(true)
}
TuiEvent::Key(KeyEvent {
code: KeyCode::Left,
kind: KeyEventKind::Press | KeyEventKind::Repeat,
..
}) => {
self.overlay_step_backtrack(tui, event)?;
Ok(true)
}
TuiEvent::Key(KeyEvent {
code: KeyCode::Right,
kind: KeyEventKind::Press | KeyEventKind::Repeat,
..
}) => {
self.overlay_step_backtrack_forward(tui, event)?;
Ok(true)
}
TuiEvent::Key(KeyEvent {
code: KeyCode::Enter,
kind: KeyEventKind::Press,
@@ -277,6 +294,27 @@ impl App {
tui.frame_requester().schedule_frame();
}
/// Step selection to the next newer user message and update overlay.
fn step_forward_backtrack_and_highlight(&mut self, tui: &mut tui::Tui) {
let count = user_count(&self.transcript_cells);
if count == 0 {
return;
}
let last_index = count.saturating_sub(1);
let next_selection = if self.backtrack.nth_user_message == usize::MAX {
last_index
} else {
self.backtrack
.nth_user_message
.saturating_add(1)
.min(last_index)
};
self.apply_backtrack_selection_internal(next_selection);
tui.frame_requester().schedule_frame();
}
/// Apply a computed backtrack selection to the overlay and internal counter.
fn apply_backtrack_selection_internal(&mut self, nth_user_message: usize) {
if let Some(cell_idx) = nth_user_position(&self.transcript_cells, nth_user_message) {
@@ -364,6 +402,20 @@ impl App {
Ok(())
}
/// Handle Right in overlay backtrack preview: step selection forward if armed, else forward.
fn overlay_step_backtrack_forward(
&mut self,
tui: &mut tui::Tui,
event: TuiEvent,
) -> Result<()> {
if self.backtrack.base_id.is_some() {
self.step_forward_backtrack_and_highlight(tui);
} else {
self.overlay_forward_event(tui, event)?;
}
Ok(())
}
/// Confirm a primed backtrack from the main view (no overlay visible).
/// Computes the prefill from the selected user message for rollback.
pub(crate) fn confirm_backtrack_from_main(&mut self) -> Option<BacktrackSelection> {