Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ mod managed_worktree_creation;
mod misalignment_policy;
mod model_defaults;
mod new_session;
mod turn_tips;
pub(crate) use new_session::has_launch_setting;
mod clipboard;
mod native_history;
Expand Down Expand Up @@ -581,6 +582,7 @@ pub(crate) struct App {

pub(crate) transcript_cells: Vec<Arc<dyn HistoryCell>>,
native_history: native_history::NativeHistory,
turn_tips: turn_tips::TurnTips,
pub(crate) transcript_view: crate::transcript_view::TranscriptView,
last_rendered_history_tail: Option<history_ui::RenderedHistoryTail>,
last_thread_usage_status_cell: Option<history_ui::ThreadUsageStatusHistory>,
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,10 @@ impl App {
AppEvent::InsertHistoryCell(cell) => {
self.insert_history_cell(tui, cell);
}
AppEvent::TurnTipReady { thread_id, turn_id } => {
self.turn_tips.ready(thread_id, &turn_id, self.transcript_cells.last());
tui.frame_requester().schedule_frame();
}
AppEvent::EndInitialHistoryReplayBuffer => {
self.scrollback_has_older_history = self
.chat_widget
Expand Down
1 change: 1 addition & 0 deletions codex-rs/tui/src/app/history_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ impl App {
pub(super) fn reset_transcript_state_after_clear(&mut self) {
self.overlay = None;
self.transcript_cells.clear();
self.turn_tips.dismiss();
self.chat_widget.warning_display_state.dismissed.clear();
self.chat_widget.warning_display_state.transcript = Arc::default();
self.chat_widget.warning_display_state.synced_cells = None;
Expand Down
43 changes: 36 additions & 7 deletions codex-rs/tui/src/app/owned_transcript.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Compose the owned transcript above the composer and route their selection gestures.
//! Reserve a cleared row below activity and previews, immediately above the composer.
//! Slash suggestions overlay already-painted rows so opening or closing them leaves transcript
//! geometry unchanged.
//! geometry unchanged. Turn tips remain separate from selectable transcript text.
//! Plain Enter returns an empty composer to latest after transcript interactions and prompt editing.

use super::turn_tips::TipSurface;
use super::*;
use crate::history_cell::HistoryRenderMode;
use crate::keymap::KeymapContext;
Expand Down Expand Up @@ -86,6 +87,16 @@ impl App {
self.sync_owned_transcript(screen_size.width);
let transcript_width = self.chat_widget.history_wrap_width(screen_size.width);
let composer_hint = self.composer_hint(transcript_width);
let now = Instant::now();
let turn_tip = self.turn_tip(transcript_width, now, &tui.frame_requester());
let working_tip = turn_tip
.as_ref()
.filter(|(surface, _)| *surface == TipSurface::Working)
.map(|(_, tip)| tip);
let completion_tip = turn_tip
.as_ref()
.filter(|(surface, _)| *surface == TipSurface::Completion)
.map(|(_, tip)| tip);
let mut composer_gap = (!self.chat_widget.has_active_view()
&& !self.chat_widget.is_external_writer_view())
.then(crate::bottom_pane::ComposerGap::default);
Expand Down Expand Up @@ -114,6 +125,7 @@ impl App {
crate::bottom_pane::CommandPopupPlacement::Overlay
},
composer_gap.as_ref(),
working_tip,
);
let dashboard_visible = chat_widget
.selected_index_for_present_view(AGENTS_OVERVIEW_VIEW_ID)
Expand All @@ -136,21 +148,22 @@ impl App {
let mut rendered_cursor = None;
let mut footer_height_changed = false;
let mut feedback_tick = None;
let now = Instant::now();
let mut transcript_bottom = available.saturating_sub(u16::from(composer_gap.is_none()));
tui.draw(screen_size.height, |frame| {
ratatui::widgets::Clear.render(
Rect::new(/*x*/ 0, /*y*/ 0, screen_size.width, available),
frame.buffer,
);
view.render(
let mut completion_tip_area = view.render_with_turn_tip_space(
Rect::new(
/*x*/ 0,
/*y*/ 0,
transcript_width,
available.saturating_sub(u16::from(composer_gap.is_none())),
transcript_bottom,
),
frame.buffer,
&self.transcript_cells,
completion_tip,
);
if let Some(gap) = composer_gap.as_mut() {
gap.needs_separator = available > 1
Expand Down Expand Up @@ -180,6 +193,7 @@ impl App {
crate::bottom_pane::CommandPopupPlacement::Overlay
},
composer_gap.as_ref(),
working_tip,
);
footer_height_changed = !dashboard_visible
&& bottom
Expand All @@ -195,14 +209,24 @@ impl App {
// Resolve controls with the compact viewport first, then make room for
// their separator. Resizing must not preserve a stale return control.
ratatui::widgets::Clear.render(bottom_area, frame.buffer);
view.render(
Rect::new(/*x*/ 0, /*y*/ 0, transcript_width, bottom_area.y),
transcript_bottom = bottom_area.y;
completion_tip_area = view.render_with_turn_tip_space(
Rect::new(
/*x*/ 0,
/*y*/ 0,
transcript_width,
transcript_bottom,
),
frame.buffer,
&self.transcript_cells,
completion_tip,
);
footer_height_changed = false;
}
bottom.render(bottom_area, frame.buffer);
if let (Some(tip), Some(area)) = (completion_tip, completion_tip_area) {
tip.render(area, frame.buffer);
}
let follow_area = if let Some(gap) = composer_gap.as_ref() {
Some(Rect {
width: transcript_width,
Expand All @@ -228,6 +252,11 @@ impl App {
frame.set_cursor_position(position);
}
})?;
if let Some((surface, tip)) = &turn_tip
&& tip.rendered.get()
{
self.turn_tips.acknowledge(*surface);
}
if footer_height_changed {
tui.frame_requester().schedule_frame();
}
Expand Down Expand Up @@ -525,7 +554,7 @@ impl App {

#[cfg(test)]
#[path = "owned_transcript_tests.rs"]
mod tests;
pub(super) mod tests;

#[cfg(test)]
#[path = "empty_state_animation_tests.rs"]
Expand Down
7 changes: 4 additions & 3 deletions codex-rs/tui/src/app/owned_transcript_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crossterm::event::MouseEventKind::Up;
use pretty_assertions::assert_eq;
use ratatui::buffer::Buffer;

fn user_cell(message: &str) -> Arc<dyn HistoryCell> {
pub(in crate::app) fn user_cell(message: &str) -> Arc<dyn HistoryCell> {
Arc::new(UserHistoryCell {
spoken: false,
message: message.to_string(),
Expand All @@ -33,7 +33,7 @@ fn user_cell(message: &str) -> Arc<dyn HistoryCell> {
})
}

fn attach_thread(app: &mut App, thread_id: ThreadId) {
pub(in crate::app) fn attach_thread(app: &mut App, thread_id: ThreadId) {
app.chat_widget.handle_thread_session(ThreadSessionState {
windows_sandbox_host: crate::app::WindowsSandboxHost::Local,
thread_id,
Expand All @@ -59,7 +59,7 @@ fn attach_thread(app: &mut App, thread_id: ThreadId) {
});
}

pub(super) fn buffer_text(buffer: &Buffer) -> String {
pub(in crate::app) fn buffer_text(buffer: &Buffer) -> String {
buffer
.content()
.chunks(usize::from(buffer.area.width))
Expand Down Expand Up @@ -515,6 +515,7 @@ async fn owned_details_keep_the_composer_cursor_and_screen() -> Result<()> {
/*footer*/ None,
crate::bottom_pane::CommandPopupPlacement::Overlay,
Some(&crate::bottom_pane::ComposerGap::default()),
/*working_tip*/ None,
)
.cursor_pos(bottom_area)
.expect("composer cursor");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
source: tui/src/app/turn_tips_tests.rs
assertion_line: 337
expression: "crate::chatwidget::tests::helpers::normalize_snapshot_paths(screens.join(\"\\n\\n\"))"
---
working=true, response rows=1, 80x12


Response row 0

• Working (0s • esc to interrupt)
└ Tip: Try /help.


› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=true, response rows=1, 80x6
• Working (0s • esc to interrupt)

› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=false, response rows=1, 80x12
Show a response.

• Done.
└ Tip: Try /help.

[completion time]


› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=false, response rows=30, 80x12
Show a response.

• Done.
└ Tip: Try /help.

[completion time]


› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=false, response rows=1, 80x9
• Done.

[completion time]


› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=false, response rows=1, 80x10
Show a response.
• Done.

[completion time]


› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=false, response rows=1, 80x11
Show a response.
• Done.
└ Tip: Try /help.

[completion time]


› Ask Codex to do anything

gpt-test default · /tmp/project
? for shortcuts

working=false, response rows=1, 12x12
Response row
0

• Done.

[completion time]


› Ask Codex

gpt-test …
? for shor

working=false, response rows=1, 80x4

› Ask Codex to do anything

? for shortcuts
1 change: 1 addition & 0 deletions codex-rs/tui/src/app/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ See the Codex keymap documentation for supported actions and examples."
key_chord_matcher: KeyChordMatcher::default(),
transcript_cells: Vec::new(),
native_history: Default::default(),
turn_tips: Default::default(),
transcript_view: Default::default(),
last_rendered_history_tail: None,
last_thread_usage_status_cell: None,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/tui/src/app/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub(crate) async fn make_test_app() -> App {
file_search,
transcript_cells: Vec::new(),
native_history: Default::default(),
turn_tips: Default::default(),
transcript_view: Default::default(),
last_rendered_history_tail: None,
last_thread_usage_status_cell: None,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/tui/src/app/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6012,6 +6012,7 @@ async fn make_test_app() -> Box<App> {
file_search,
transcript_cells: Vec::new(),
native_history: Default::default(),
turn_tips: Default::default(),
transcript_view: Default::default(),
last_rendered_history_tail: None,
last_thread_usage_status_cell: None,
Expand Down Expand Up @@ -6127,6 +6128,7 @@ pub(super) async fn make_test_app_with_channels() -> (
file_search,
transcript_cells: Vec::new(),
native_history: Default::default(),
turn_tips: Default::default(),
transcript_view: Default::default(),
last_rendered_history_tail: None,
last_thread_usage_status_cell: None,
Expand Down
6 changes: 6 additions & 0 deletions codex-rs/tui/src/app/thread_routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2023,8 +2023,13 @@ impl App {
match event {
ThreadBufferedEvent::Notification(notification) => {
self.cache_collab_receiver_threads_for_notification(notification.as_ref());
let tip_ready = self.turn_tips.observe(&notification, Instant::now());
self.chat_widget
.handle_server_notification(*notification, /*replay_kind*/ None);
// History cells queued by completion must be applied before anchoring its tip.
if let Some(event) = tip_ready {
self.app_event_tx.send(event);
}
}
ThreadBufferedEvent::Request(request) => {
if self
Expand Down Expand Up @@ -2056,6 +2061,7 @@ impl App {
}

pub(super) fn handle_thread_event_replay(&mut self, event: ThreadBufferedEvent) {
self.turn_tips.dismiss();
match event {
ThreadBufferedEvent::Notification(notification) => self
.chat_widget
Expand Down
Loading
Loading