mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Avoid redundant reads and screen clears when switching side chats (#60675)
## Why A live parent thread continues receiving lifecycle notifications while its side chat has focus, so returning to it does not need another liveness read. ## What changed - Skip the parent liveness read when returning from a side chat while the parent is still attached and online. Keep the read when reattachment is needed. - Stop scheduling a deferred screen clear when handling a transcript reset that preserves the screen. ## Testing Add regression coverage for parent `thread/read` requests with and without reattachment, and assert that side-chat navigation leaves no deferred screen clear. GitOrigin-RevId: a07f161ec86b0ae51250c5a0156402d328c42bdf
This commit is contained in:
@@ -1059,17 +1059,6 @@ impl App {
|
||||
AppEvent::ResetTranscriptForThreadSwitchPreservingScreen => {
|
||||
self.reset_transcript_state_after_clear();
|
||||
tui.clear_pending_history_lines();
|
||||
let switched_tab = self.conversation_frame.chat_strip.is_some()
|
||||
&& self.active_side_parent_thread_id().is_some()
|
||||
&& self.conversation_frame.sidebar_input.is_some_and(|painted| {
|
||||
painted.selected_chat != self.current_displayed_thread_id()
|
||||
});
|
||||
if self.conversation_frame.chooser_area().is_none()
|
||||
&& self.chooser_focus().is_none()
|
||||
&& !switched_tab
|
||||
{
|
||||
tui.defer_thread_switch_clear();
|
||||
}
|
||||
self.pending_thread_switch_resets -= 1;
|
||||
}
|
||||
AppEvent::CommitRealtimeTranscriptHistory => {
|
||||
|
||||
@@ -638,10 +638,16 @@ impl App {
|
||||
self.store_active_thread_receiver().await;
|
||||
}
|
||||
|
||||
// A tracked side thread stays loaded until it is explicitly discarded and already has a
|
||||
// replay channel, so another liveness read cannot add anything before selection.
|
||||
if !(self.side_threads.contains_key(&thread_id)
|
||||
&& self.thread_event_channels.contains_key(&thread_id)
|
||||
let known_side = self.side_threads.contains_key(&thread_id)
|
||||
&& self.thread_event_channels.contains_key(&thread_id);
|
||||
// The live parent already receives lifecycle notifications while its child has focus.
|
||||
let live_parent = self.side_retention_enabled(tui)
|
||||
&& !self.reconnect.offline
|
||||
&& self.primary_thread_id == Some(thread_id)
|
||||
&& self.active_side_parent_thread_id() == Some(thread_id)
|
||||
&& !self.should_attach_live_thread_for_selection(thread_id);
|
||||
if !(known_side
|
||||
|| live_parent
|
||||
|| self
|
||||
.refresh_agent_picker_thread_liveness(app_server, thread_id)
|
||||
.await)
|
||||
|
||||
@@ -4554,6 +4554,7 @@ async fn handle_start_side_seeds_navigation_before_thread_started() -> Result<()
|
||||
|
||||
keys!(toggle);
|
||||
assert_eq!(app.current_displayed_thread_id(), Some(side_thread_id));
|
||||
assert!(!tui.has_deferred_thread_switch_clear());
|
||||
let wide = side_frame(&mut app, &mut tui, size)?;
|
||||
assert!(
|
||||
wide.contains(parent_row) && wide.contains(child_row),
|
||||
@@ -4616,6 +4617,7 @@ async fn handle_start_side_seeds_navigation_before_thread_started() -> Result<()
|
||||
};
|
||||
// Park an interaction on the parent so both hide shortcuts must clear it on return.
|
||||
keys!(prefix, main);
|
||||
assert!(!tui.has_deferred_thread_switch_clear());
|
||||
app.transcript_view.begin_search();
|
||||
app.select_agent_thread(&mut tui, &mut app_server, side_thread_id)
|
||||
.await?;
|
||||
|
||||
@@ -1104,6 +1104,54 @@ async fn complete_managed_worktree_creation(
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returning_from_side_chat_reads_parent_only_when_reattaching() -> Result<()> {
|
||||
let (mut app, _home) = make_history_test_app().await?;
|
||||
enable_side_retention(&mut app)?;
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
tui.set_owned_screen(/*owned*/ true)?;
|
||||
let (mut server, requests, proxy) = start_recording_app_server(
|
||||
&app.config,
|
||||
/*blocked_thread_list*/ None,
|
||||
/*failed_thread_name*/ None,
|
||||
)
|
||||
.await?;
|
||||
let started = server.start_thread(&app.config).await?;
|
||||
let parent = started.session.thread_id;
|
||||
app.enqueue_primary_thread_session(started.session, started.turns)
|
||||
.await?;
|
||||
let side = server.start_thread(&app.config).await?;
|
||||
let child = side.session.thread_id;
|
||||
app.side_threads.insert(child, SideThreadState::new(parent));
|
||||
app.ensure_thread_channel(child)
|
||||
.store
|
||||
.lock()
|
||||
.await
|
||||
.set_session(side.session, side.turns);
|
||||
|
||||
for needs_reattach in [false, true] {
|
||||
app.select_agent_thread(&mut tui, &mut server, child)
|
||||
.await?;
|
||||
assert_eq!(app.current_displayed_thread_id(), Some(child));
|
||||
if needs_reattach {
|
||||
app.ensure_thread_channel(parent).mark_replay_only();
|
||||
}
|
||||
app.select_agent_thread(&mut tui, &mut server, parent)
|
||||
.await?;
|
||||
assert_eq!(app.current_displayed_thread_id(), Some(parent));
|
||||
let reads = recorded_params(&requests, "thread/read");
|
||||
assert_eq!(
|
||||
reads
|
||||
.iter()
|
||||
.any(|params| params["threadId"] == parent.to_string()),
|
||||
needs_reattach
|
||||
);
|
||||
}
|
||||
server.shutdown().await?;
|
||||
proxy.await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn same_thread_retry_keeps_subscription_and_restores_draft() -> Result<()> {
|
||||
let (mut app, codex_home) = make_history_test_app().await?;
|
||||
|
||||
Reference in New Issue
Block a user