agent_ui: Remove history view

Bennet Bo Fenner created

Change summary

assets/keymaps/default-linux.json                    |   2 
assets/keymaps/default-macos.json                    |   2 
assets/keymaps/default-windows.json                  |   2 
crates/agent_ui/src/agent_panel.rs                   | 351 -----
crates/agent_ui/src/agent_ui.rs                      |   8 
crates/agent_ui/src/conversation_view.rs             | 178 ---
crates/agent_ui/src/conversation_view/thread_view.rs |  29 
crates/agent_ui/src/thread_history.rs                |  42 
crates/agent_ui/src/thread_history_view.rs           | 751 --------------
9 files changed, 51 insertions(+), 1,314 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -224,7 +224,6 @@
     "context": "AgentPanel",
     "bindings": {
       "ctrl-n": "agent::NewThread",
-      "ctrl-shift-h": "agent::OpenHistory",
       "ctrl-alt-c": "agent::OpenSettings",
       "ctrl-alt-p": "agent::ManageProfiles",
       "ctrl-alt-l": "agent::OpenRulesLibrary",
@@ -234,7 +233,6 @@
       "alt-tab": "agent::CycleFavoriteModels",
       // `alt-l` is provided as an alternative to `alt-tab` as the latter breaks on Linux under the `AgentPanel` context
       "alt-l": "agent::CycleFavoriteModels",
-      "shift-alt-j": "agent::ToggleNavigationMenu",
       "shift-alt-i": "agent::ToggleOptionsMenu",
       "ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
       "ctrl-shift-t": "agent::CycleStartThreadIn",

assets/keymaps/default-macos.json 🔗

@@ -264,7 +264,6 @@
     "use_key_equivalents": true,
     "bindings": {
       "cmd-n": "agent::NewThread",
-      "cmd-shift-h": "agent::OpenHistory",
       "cmd-alt-c": "agent::OpenSettings",
       "cmd-alt-l": "agent::OpenRulesLibrary",
       "cmd-alt-p": "agent::ManageProfiles",
@@ -272,7 +271,6 @@
       "shift-tab": "agent::CycleModeSelector",
       "cmd-alt-/": "agent::ToggleModelSelector",
       "alt-tab": "agent::CycleFavoriteModels",
-      "cmd-shift-j": "agent::ToggleNavigationMenu",
       "cmd-alt-m": "agent::ToggleOptionsMenu",
       "cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
       "cmd-shift-t": "agent::CycleStartThreadIn",

assets/keymaps/default-windows.json 🔗

@@ -225,7 +225,6 @@
     "use_key_equivalents": true,
     "bindings": {
       "ctrl-n": "agent::NewThread",
-      "ctrl-shift-h": "agent::OpenHistory",
       "shift-alt-c": "agent::OpenSettings",
       "shift-alt-l": "agent::OpenRulesLibrary",
       "shift-alt-p": "agent::ManageProfiles",
@@ -235,7 +234,6 @@
       // `alt-l` is provided as an alternative to `alt-tab` as the latter breaks on Windows under the `AgentPanel` context
       "alt-l": "agent::CycleFavoriteModels",
       "shift-alt-/": "agent::ToggleModelSelector",
-      "shift-alt-j": "agent::ToggleNavigationMenu",
       "shift-alt-i": "agent::ToggleOptionsMenu",
       "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
       "ctrl-shift-t": "agent::CycleStartThreadIn",

crates/agent_ui/src/agent_panel.rs 🔗

@@ -24,12 +24,13 @@ use zed_actions::agent::{
     ResolveConflictsWithAgent, ReviewBranchDiff,
 };
 
+use crate::agent_connection_store::AgentConnectionStore;
 use crate::thread_metadata_store::ThreadMetadataStore;
 use crate::{
     AddContextServer, AgentDiffPane, ConversationView, CopyThreadToClipboard, CycleStartThreadIn,
     Follow, InlineAssistant, LoadThreadFromClipboard, NewThread, NewWorktreeBranchTarget,
-    OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
-    StartThreadIn, ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
+    OpenActiveThreadAsMarkdown, OpenAgentDiff, ResetTrialEndUpsell, ResetTrialUpsell,
+    StartThreadIn, ToggleNewThreadMenu, ToggleOptionsMenu,
     agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
     conversation_view::{AcpThreadViewEvent, ThreadView},
     thread_branch_picker::ThreadBranchPicker,
@@ -41,12 +42,10 @@ use crate::{
     NewNativeAgentThreadFromSummary,
 };
 use crate::{DEFAULT_THREAD_TITLE, ui::AcpOnboardingModal};
-use crate::{ExpandMessageEditor, ThreadHistoryView};
-use crate::{ManageProfiles, ThreadHistoryViewEvent};
-use crate::{ThreadHistory, agent_connection_store::AgentConnectionStore};
+use crate::{ExpandMessageEditor, ManageProfiles};
 use agent_settings::{AgentSettings, WindowLayout};
 use ai_onboarding::AgentPanelOnboarding;
-use anyhow::{Context as _, Result, anyhow};
+use anyhow::{Result, anyhow};
 use client::UserStore;
 use cloud_api_types::Plan;
 use collections::HashMap;
@@ -56,8 +55,8 @@ use extension_host::ExtensionStore;
 use fs::Fs;
 use gpui::{
     Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem, Corner,
-    DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels,
-    Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
+    Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription,
+    Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
 };
 use language::LanguageRegistry;
 use language_model::LanguageModelRegistry;
@@ -76,7 +75,7 @@ use ui::{
     Button, ButtonLike, Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, PopoverMenu,
     PopoverMenuHandle, Tab, Tooltip, prelude::*, utils::WithRemSize,
 };
-use util::{ResultExt as _, debug_panic};
+use util::ResultExt as _;
 use workspace::{
     CollaboratorId, DraggedSelection, DraggedTab, PathList, SerializedPathList,
     ToggleWorkspaceSidebar, ToggleZoom, Workspace, WorkspaceId,
@@ -89,7 +88,6 @@ use zed_actions::{
 };
 
 const AGENT_PANEL_KEY: &str = "agent_panel";
-const RECENTLY_UPDATED_MENU_LIMIT: usize = 6;
 const LAST_USED_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
 
 #[derive(Serialize, Deserialize)]
@@ -191,12 +189,6 @@ pub fn init(cx: &mut App) {
                         panel.update(cx, |panel, cx| panel.expand_message_editor(window, cx));
                     }
                 })
-                .register_action(|workspace, _: &OpenHistory, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
-                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
-                    }
-                })
                 .register_action(|workspace, _: &OpenSettings, window, cx| {
                     if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
                         workspace.focus_panel::<AgentPanel>(window, cx);
@@ -247,14 +239,6 @@ pub fn init(cx: &mut App) {
                         AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
                     }
                 })
-                .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
-                        panel.update(cx, |panel, cx| {
-                            panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);
-                        });
-                    }
-                })
                 .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {
                     if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
                         workspace.focus_panel::<AgentPanel>(window, cx);
@@ -606,9 +590,6 @@ enum ActiveView {
     AgentThread {
         conversation_view: Entity<ConversationView>,
     },
-    History {
-        view: Entity<ThreadHistoryView>,
-    },
     Configuration,
 }
 
@@ -776,9 +757,7 @@ enum WorktreeCreationArgs {
 impl ActiveView {
     pub fn which_font_size_used(&self) -> WhichFontSize {
         match self {
-            ActiveView::Uninitialized
-            | ActiveView::AgentThread { .. }
-            | ActiveView::History { .. } => WhichFontSize::AgentFont,
+            ActiveView::Uninitialized | ActiveView::AgentThread { .. } => WhichFontSize::AgentFont,
             ActiveView::Configuration => WhichFontSize::None,
         }
     }
@@ -806,8 +785,6 @@ pub struct AgentPanel {
     start_thread_in_menu_handle: PopoverMenuHandle<ThreadWorktreePicker>,
     thread_branch_menu_handle: PopoverMenuHandle<ThreadBranchPicker>,
     agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
-    agent_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
-    agent_navigation_menu: Option<Entity<ContextMenu>>,
     _extension_subscription: Option<Subscription>,
     _project_subscription: Subscription,
     _git_store_subscription: Subscription,
@@ -990,7 +967,7 @@ impl AgentPanel {
     pub(crate) fn new(
         workspace: &Workspace,
         prompt_store: Option<Entity<PromptStore>>,
-        window: &mut Window,
+        _window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
         let fs = workspace.app_state().fs.clone();
@@ -1008,48 +985,6 @@ impl AgentPanel {
 
         let active_view = ActiveView::Uninitialized;
 
-        let weak_panel = cx.entity().downgrade();
-
-        window.defer(cx, move |window, cx| {
-            let panel = weak_panel.clone();
-            let agent_navigation_menu =
-                ContextMenu::build_persistent(window, cx, move |mut menu, window, cx| {
-                    if let Some(panel) = panel.upgrade() {
-                        if let Some(history) = panel
-                            .update(cx, |panel, cx| panel.history_for_selected_agent(window, cx))
-                        {
-                            menu = Self::populate_recently_updated_menu_section(
-                                menu, panel, history, cx,
-                            );
-                            menu = menu.action("View All", Box::new(OpenHistory));
-                        }
-                    }
-
-                    menu = menu
-                        .fixed_width(px(320.).into())
-                        .keep_open_on_confirm(false)
-                        .key_context("NavigationMenu");
-
-                    menu
-                });
-            weak_panel
-                .update(cx, |panel, cx| {
-                    cx.subscribe_in(
-                        &agent_navigation_menu,
-                        window,
-                        |_, menu, _: &DismissEvent, window, cx| {
-                            menu.update(cx, |menu, _| {
-                                menu.clear_selected();
-                            });
-                            cx.focus_self(window);
-                        },
-                    )
-                    .detach();
-                    panel.agent_navigation_menu = Some(agent_navigation_menu);
-                })
-                .ok();
-        });
-
         let weak_panel = cx.entity().downgrade();
         let onboarding = cx.new(|cx| {
             AgentPanelOnboarding::new(
@@ -1184,8 +1119,6 @@ impl AgentPanel {
             start_thread_in_menu_handle: PopoverMenuHandle::default(),
             thread_branch_menu_handle: PopoverMenuHandle::default(),
             agent_panel_menu_handle: PopoverMenuHandle::default(),
-            agent_navigation_menu_handle: PopoverMenuHandle::default(),
-            agent_navigation_menu: None,
             _extension_subscription: extension_subscription,
             _project_subscription,
             _git_store_subscription,
@@ -1348,40 +1281,28 @@ impl AgentPanel {
     ) {
         let session_id = action.from_session_id.clone();
 
-        let Some(history) = self
-            .connection_store
+        let Some(thread) = ThreadStore::global(cx)
             .read(cx)
-            .entry(&Agent::NativeAgent)
-            .and_then(|e| e.read(cx).history().cloned())
+            .entries()
+            .find(|t| t.id == session_id)
         else {
-            debug_panic!("Native agent is not registered");
+            log::error!("No session found for summarization with id {}", session_id);
             return;
         };
 
-        cx.spawn_in(window, async move |this, cx| {
-            this.update_in(cx, |this, window, cx| {
-                let thread = history
-                    .read(cx)
-                    .session_for_id(&session_id)
-                    .context("Session not found")?;
-
-                this.external_thread(
-                    Some(Agent::NativeAgent),
-                    None,
-                    None,
-                    None,
-                    Some(AgentInitialContent::ThreadSummary {
-                        session_id: thread.session_id,
-                        title: thread.title,
-                    }),
-                    true,
-                    window,
-                    cx,
-                );
-                anyhow::Ok(())
-            })
-        })
-        .detach_and_log_err(cx);
+        self.external_thread(
+            Some(Agent::NativeAgent),
+            None,
+            None,
+            None,
+            Some(AgentInitialContent::ThreadSummary {
+                session_id: thread.id,
+                title: Some(thread.title),
+            }),
+            true,
+            window,
+            cx,
+        );
     }
 
     fn external_thread(
@@ -1456,102 +1377,13 @@ impl AgentPanel {
         })
     }
 
-    fn has_history_for_selected_agent(&self, cx: &App) -> bool {
-        match &self.selected_agent {
-            Agent::NativeAgent => true,
-            Agent::Custom { .. } => self
-                .connection_store
-                .read(cx)
-                .entry(&self.selected_agent)
-                .map_or(false, |entry| entry.read(cx).history().is_some()),
-        }
-    }
-
-    fn history_for_selected_agent(
-        &self,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Option<Entity<ThreadHistoryView>> {
-        let agent = self.selected_agent.clone();
-        let history = self
-            .connection_store
-            .read(cx)
-            .entry(&agent)?
-            .read(cx)
-            .history()?
-            .clone();
-        Some(self.create_thread_history_view(agent, history, window, cx))
-    }
-
-    fn create_thread_history_view(
-        &self,
-        agent: Agent,
-        history: Entity<ThreadHistory>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Entity<ThreadHistoryView> {
-        let view = cx.new(|cx| ThreadHistoryView::new(history.clone(), window, cx));
-        cx.subscribe_in(
-            &view,
-            window,
-            move |this, _, event, window, cx| match event {
-                ThreadHistoryViewEvent::Open(thread) => {
-                    this.load_agent_thread(
-                        agent.clone(),
-                        thread.session_id.clone(),
-                        thread.work_dirs.clone(),
-                        thread.title.clone(),
-                        true,
-                        window,
-                        cx,
-                    );
-                }
-            },
-        )
-        .detach();
-        view
-    }
-
-    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let Some(view) = self.history_for_selected_agent(window, cx) else {
-            return;
-        };
-
-        if let ActiveView::History { view: active_view } = &self.active_view {
-            if active_view == &view {
-                if let Some(previous_view) = self.previous_view.take() {
-                    self.set_active_view(previous_view, true, window, cx);
-                }
-                return;
-            }
-        }
-
-        self.set_active_view(ActiveView::History { view }, true, window, cx);
-        cx.notify();
-    }
-
     pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
-        match self.active_view {
-            ActiveView::Configuration | ActiveView::History { .. } => {
-                if let Some(previous_view) = self.previous_view.take() {
-                    self.set_active_view(previous_view, true, window, cx);
-                }
-                cx.notify();
+        if let ActiveView::Configuration = self.active_view {
+            if let Some(previous_view) = self.previous_view.take() {
+                self.set_active_view(previous_view, true, window, cx);
             }
-            _ => {}
-        }
-    }
-
-    pub fn toggle_navigation_menu(
-        &mut self,
-        _: &ToggleNavigationMenu,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if !self.has_history_for_selected_agent(cx) {
-            return;
+            cx.notify();
         }
-        self.agent_navigation_menu_handle.toggle(window, cx);
     }
 
     pub fn toggle_options_menu(
@@ -2043,16 +1875,12 @@ impl AgentPanel {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        let was_in_agent_history = matches!(self.active_view, ActiveView::History { .. });
         let current_is_uninitialized = matches!(self.active_view, ActiveView::Uninitialized);
-        let current_is_history = matches!(self.active_view, ActiveView::History { .. });
-        let new_is_history = matches!(new_view, ActiveView::History { .. });
-
         let current_is_config = matches!(self.active_view, ActiveView::Configuration);
         let new_is_config = matches!(new_view, ActiveView::Configuration);
 
-        let current_is_overlay = current_is_history || current_is_config;
-        let new_is_overlay = new_is_history || new_is_config;
+        let current_is_overlay = current_is_config;
+        let new_is_overlay = new_is_config;
 
         if current_is_uninitialized || (current_is_overlay && !new_is_overlay) {
             self.active_view = new_view;
@@ -2101,78 +1929,12 @@ impl AgentPanel {
             }
         };
 
-        if let ActiveView::History { view } = &self.active_view {
-            if !was_in_agent_history {
-                view.update(cx, |view, cx| {
-                    view.history()
-                        .update(cx, |history, cx| history.refresh_full_history(cx))
-                });
-            }
-        }
-
         if focus {
             self.focus_handle(cx).focus(window, cx);
         }
         cx.emit(AgentPanelEvent::ActiveViewChanged);
     }
 
-    fn populate_recently_updated_menu_section(
-        mut menu: ContextMenu,
-        panel: Entity<Self>,
-        view: Entity<ThreadHistoryView>,
-        cx: &mut Context<ContextMenu>,
-    ) -> ContextMenu {
-        let entries = view
-            .read(cx)
-            .history()
-            .read(cx)
-            .sessions()
-            .iter()
-            .take(RECENTLY_UPDATED_MENU_LIMIT)
-            .cloned()
-            .collect::<Vec<_>>();
-
-        if entries.is_empty() {
-            return menu;
-        }
-
-        menu = menu.header("Recently Updated");
-
-        for entry in entries {
-            let title = entry
-                .title
-                .as_ref()
-                .filter(|title| !title.is_empty())
-                .cloned()
-                .unwrap_or_else(|| SharedString::new_static(DEFAULT_THREAD_TITLE));
-
-            menu = menu.entry(title, None, {
-                let panel = panel.downgrade();
-                let entry = entry.clone();
-                move |window, cx| {
-                    let entry = entry.clone();
-                    panel
-                        .update(cx, move |this, cx| {
-                            if let Some(agent) = this.selected_agent() {
-                                this.load_agent_thread(
-                                    agent,
-                                    entry.session_id.clone(),
-                                    entry.work_dirs.clone(),
-                                    entry.title.clone(),
-                                    true,
-                                    window,
-                                    cx,
-                                );
-                            }
-                        })
-                        .ok();
-                }
-            });
-        }
-
-        menu.separator()
-    }
-
     fn subscribe_to_active_thread_view(
         server_view: &Entity<ConversationView>,
         window: &mut Window,
@@ -3324,7 +3086,6 @@ impl Focusable for AgentPanel {
             ActiveView::AgentThread {
                 conversation_view, ..
             } => conversation_view.focus_handle(cx),
-            ActiveView::History { view } => view.read(cx).focus_handle(cx),
             ActiveView::Configuration => {
                 if let Some(configuration) = self.configuration.as_ref() {
                     configuration.focus_handle(cx)
@@ -3507,7 +3268,6 @@ impl AgentPanel {
                         .into_any_element()
                 }
             }
-            ActiveView::History { .. } => Label::new("History").truncate().into_any_element(),
             ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
             ActiveView::Uninitialized => Label::new("Agent").truncate().into_any_element(),
         };
@@ -3774,9 +3534,7 @@ impl AgentPanel {
             ActiveView::AgentThread { conversation_view } => {
                 conversation_view.read(cx).as_native_thread(cx)
             }
-            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
-                None
-            }
+            ActiveView::Uninitialized | ActiveView::Configuration => None,
         };
 
         let new_thread_menu_builder: Rc<
@@ -4005,10 +3763,7 @@ impl AgentPanel {
 
         let is_empty_state = !self.active_thread_has_messages(cx);
 
-        let is_in_history_or_config = matches!(
-            &self.active_view,
-            ActiveView::History { .. } | ActiveView::Configuration
-        );
+        let is_in_history_or_config = matches!(&self.active_view, ActiveView::Configuration);
 
         let is_full_screen = self.is_zoomed(window, cx);
         let full_screen_button = if is_full_screen {
@@ -4143,7 +3898,7 @@ impl AgentPanel {
                         .gap(DynamicSpacing::Base04.rems(cx))
                         .pl(DynamicSpacing::Base04.rems(cx))
                         .child(match &self.active_view {
-                            ActiveView::History { .. } | ActiveView::Configuration => {
+                            ActiveView::Configuration => {
                                 self.render_toolbar_back_button(cx).into_any_element()
                             }
                             _ => selected_agent.into_any_element(),
@@ -4238,7 +3993,7 @@ impl AgentPanel {
                     return false;
                 }
             }
-            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
+            ActiveView::Uninitialized | ActiveView::Configuration => {
                 return false;
             }
         }
@@ -4265,9 +4020,7 @@ impl AgentPanel {
         }
 
         match &self.active_view {
-            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
-                false
-            }
+            ActiveView::Uninitialized | ActiveView::Configuration => false,
             ActiveView::AgentThread { .. } => {
                 let existing_user = self
                     .new_user_onboarding_upsell_dismissed
@@ -4338,17 +4091,12 @@ impl AgentPanel {
             });
 
         match &self.active_view {
-            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
-                false
-            }
+            ActiveView::Uninitialized | ActiveView::Configuration => false,
             ActiveView::AgentThread {
                 conversation_view, ..
             } if conversation_view.read(cx).as_native_thread(cx).is_none() => false,
-            ActiveView::AgentThread { conversation_view } => {
-                let history_is_empty = conversation_view
-                    .read(cx)
-                    .history()
-                    .is_none_or(|h| h.read(cx).is_empty());
+            ActiveView::AgentThread { .. } => {
+                let history_is_empty = ThreadStore::global(cx).read(cx).is_empty();
                 history_is_empty || !has_configured_non_zed_providers
             }
         }
@@ -4471,7 +4219,7 @@ impl AgentPanel {
                     conversation_view.insert_dragged_files(paths, added_worktrees, window, cx);
                 });
             }
-            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
+            ActiveView::Uninitialized | ActiveView::Configuration => {}
         }
     }
 
@@ -4512,7 +4260,7 @@ impl AgentPanel {
         key_context.add("AgentPanel");
         match &self.active_view {
             ActiveView::AgentThread { .. } => key_context.add("acp_thread"),
-            ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
+            ActiveView::Uninitialized | ActiveView::Configuration => {}
         }
         key_context
     }
@@ -4537,16 +4285,12 @@ impl Render for AgentPanel {
             .on_action(cx.listener(|this, action: &NewThread, window, cx| {
                 this.new_thread(action, window, cx);
             }))
-            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
-                this.open_history(window, cx);
-            }))
             .on_action(cx.listener(|this, _: &OpenSettings, window, cx| {
                 this.open_configuration(window, cx);
             }))
             .on_action(cx.listener(Self::open_active_thread_as_markdown))
             .on_action(cx.listener(Self::deploy_rules_library))
             .on_action(cx.listener(Self::go_back))
-            .on_action(cx.listener(Self::toggle_navigation_menu))
             .on_action(cx.listener(Self::toggle_options_menu))
             .on_action(cx.listener(Self::increase_font_size))
             .on_action(cx.listener(Self::decrease_font_size))
@@ -4570,7 +4314,6 @@ impl Render for AgentPanel {
                 } => parent
                     .child(conversation_view.clone())
                     .child(self.render_drag_target(cx)),
-                ActiveView::History { view } => parent.child(view.clone()),
                 ActiveView::Configuration => parent.children(self.configuration.clone()),
             })
             .children(self.render_worktree_creation_status(cx))
@@ -4732,14 +4475,6 @@ impl AgentPanel {
         cx.notify();
     }
 
-    /// Opens the history view.
-    ///
-    /// This is a test-only helper that exposes the private `open_history()`
-    /// method for visual tests.
-    pub fn open_history_for_tests(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.open_history(window, cx);
-    }
-
     /// Opens the start_thread_in selector popover menu.
     ///
     /// This is a test-only helper for visual tests.

crates/agent_ui/src/agent_ui.rs 🔗

@@ -30,7 +30,6 @@ mod terminal_inline_assistant;
 pub mod test_support;
 mod thread_branch_picker;
 mod thread_history;
-mod thread_history_view;
 mod thread_import;
 pub mod thread_metadata_store;
 pub mod thread_worktree_archive;
@@ -75,7 +74,6 @@ pub(crate) use mode_selector::ModeSelector;
 pub(crate) use model_selector::ModelSelector;
 pub(crate) use model_selector_popover::ModelSelectorPopover;
 pub(crate) use thread_history::ThreadHistory;
-pub(crate) use thread_history_view::*;
 pub use thread_import::{AcpThreadImportOnboarding, ThreadImportModal};
 use zed_actions;
 
@@ -88,8 +86,6 @@ actions!(
         ToggleNewThreadMenu,
         /// Cycles through the options for where new threads start (current project or new worktree).
         CycleStartThreadIn,
-        /// Toggles the navigation menu for switching between threads and views.
-        ToggleNavigationMenu,
         /// Toggles the options menu for agent settings and preferences.
         ToggleOptionsMenu,
         /// Toggles the profile or mode selector for switching between agent profiles.
@@ -100,10 +96,6 @@ actions!(
         CycleFavoriteModels,
         /// Expands the message editor to full size.
         ExpandMessageEditor,
-        /// Removes all thread history.
-        RemoveHistory,
-        /// Opens the conversation history view.
-        OpenHistory,
         /// Adds a context server to the configuration.
         AddContextServer,
         /// Removes the currently selected thread.

crates/agent_ui/src/conversation_view.rs 🔗

@@ -2644,10 +2644,6 @@ impl ConversationView {
             Self::handle_auth_required(this, AuthRequired::new(), agent_id, connection, window, cx);
         })
     }
-
-    pub fn history(&self) -> Option<&Entity<ThreadHistory>> {
-        self.as_connected().and_then(|c| c.history.as_ref())
-    }
 }
 
 fn loading_contents_spinner(size: IconSize) -> AnyElement {
@@ -2797,9 +2793,7 @@ fn plan_label_markdown_style(
 
 #[cfg(test)]
 pub(crate) mod tests {
-    use acp_thread::{
-        AgentSessionList, AgentSessionListRequest, AgentSessionListResponse, StubAgentConnection,
-    };
+    use acp_thread::StubAgentConnection;
     use action_log::ActionLog;
     use agent::{AgentTool, EditFileTool, FetchTool, TerminalTool, ToolPermissionContext};
     use agent_client_protocol::SessionId;
@@ -2934,66 +2928,6 @@ pub(crate) mod tests {
         );
     }
 
-    #[gpui::test]
-    async fn test_recent_history_refreshes_when_history_cache_updated(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let session_a = AgentSessionInfo::new(SessionId::new("session-a"));
-        let session_b = AgentSessionInfo::new(SessionId::new("session-b"));
-
-        // Use a connection that provides a session list so ThreadHistory is created
-        let (conversation_view, history, cx) = setup_thread_view_with_history(
-            StubAgentServer::new(SessionHistoryConnection::new(vec![session_a.clone()])),
-            cx,
-        )
-        .await;
-
-        // Initially has session_a from the connection's session list
-        active_thread(&conversation_view, cx).read_with(cx, |view, _cx| {
-            assert_eq!(view.recent_history_entries.len(), 1);
-            assert_eq!(
-                view.recent_history_entries[0].session_id,
-                session_a.session_id
-            );
-        });
-
-        // Swap to a different session list
-        let list_b: Rc<dyn AgentSessionList> =
-            Rc::new(StubSessionList::new(vec![session_b.clone()]));
-        history.update(cx, |history, cx| {
-            history.set_session_list(list_b, cx);
-        });
-        cx.run_until_parked();
-
-        active_thread(&conversation_view, cx).read_with(cx, |view, _cx| {
-            assert_eq!(view.recent_history_entries.len(), 1);
-            assert_eq!(
-                view.recent_history_entries[0].session_id,
-                session_b.session_id
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_new_thread_creation_triggers_session_list_refresh(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let session = AgentSessionInfo::new(SessionId::new("history-session"));
-        let (conversation_view, _history, cx) = setup_thread_view_with_history(
-            StubAgentServer::new(SessionHistoryConnection::new(vec![session.clone()])),
-            cx,
-        )
-        .await;
-
-        active_thread(&conversation_view, cx).read_with(cx, |view, _cx| {
-            assert_eq!(view.recent_history_entries.len(), 1);
-            assert_eq!(
-                view.recent_history_entries[0].session_id,
-                session.session_id
-            );
-        });
-    }
-
     #[gpui::test]
     async fn test_resume_without_history_adds_notice(cx: &mut TestAppContext) {
         init_test(cx);
@@ -3852,19 +3786,6 @@ pub(crate) mod tests {
         (conversation_view, cx)
     }
 
-    async fn setup_thread_view_with_history(
-        agent: impl AgentServer + 'static,
-        cx: &mut TestAppContext,
-    ) -> (
-        Entity<ConversationView>,
-        Entity<ThreadHistory>,
-        &mut VisualTestContext,
-    ) {
-        let (conversation_view, history, cx) =
-            setup_conversation_view_with_history_and_initial_content(agent, None, cx).await;
-        (conversation_view, history.expect("Missing history"), cx)
-    }
-
     async fn setup_conversation_view_with_initial_content(
         agent: impl AgentServer + 'static,
         initial_content: AgentInitialContent,
@@ -4061,42 +3982,6 @@ pub(crate) mod tests {
         }
     }
 
-    #[derive(Clone)]
-    struct StubSessionList {
-        sessions: Vec<AgentSessionInfo>,
-    }
-
-    impl StubSessionList {
-        fn new(sessions: Vec<AgentSessionInfo>) -> Self {
-            Self { sessions }
-        }
-    }
-
-    impl AgentSessionList for StubSessionList {
-        fn list_sessions(
-            &self,
-            _request: AgentSessionListRequest,
-            _cx: &mut App,
-        ) -> Task<anyhow::Result<AgentSessionListResponse>> {
-            Task::ready(Ok(AgentSessionListResponse::new(self.sessions.clone())))
-        }
-
-        fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
-            self
-        }
-    }
-
-    #[derive(Clone)]
-    struct SessionHistoryConnection {
-        sessions: Vec<AgentSessionInfo>,
-    }
-
-    impl SessionHistoryConnection {
-        fn new(sessions: Vec<AgentSessionInfo>) -> Self {
-            Self { sessions }
-        }
-    }
-
     fn build_test_thread(
         connection: Rc<dyn AgentConnection>,
         project: Entity<Project>,
@@ -4125,67 +4010,6 @@ pub(crate) mod tests {
         })
     }
 
-    impl AgentConnection for SessionHistoryConnection {
-        fn agent_id(&self) -> AgentId {
-            AgentId::new("history-connection")
-        }
-
-        fn telemetry_id(&self) -> SharedString {
-            "history-connection".into()
-        }
-
-        fn new_session(
-            self: Rc<Self>,
-            project: Entity<Project>,
-            _work_dirs: PathList,
-            cx: &mut App,
-        ) -> Task<anyhow::Result<Entity<AcpThread>>> {
-            let thread = build_test_thread(
-                self,
-                project,
-                "SessionHistoryConnection",
-                SessionId::new("history-session"),
-                cx,
-            );
-            Task::ready(Ok(thread))
-        }
-
-        fn supports_load_session(&self) -> bool {
-            true
-        }
-
-        fn session_list(&self, _cx: &mut App) -> Option<Rc<dyn AgentSessionList>> {
-            Some(Rc::new(StubSessionList::new(self.sessions.clone())))
-        }
-
-        fn auth_methods(&self) -> &[acp::AuthMethod] {
-            &[]
-        }
-
-        fn authenticate(
-            &self,
-            _method_id: acp::AuthMethodId,
-            _cx: &mut App,
-        ) -> Task<anyhow::Result<()>> {
-            Task::ready(Ok(()))
-        }
-
-        fn prompt(
-            &self,
-            _id: Option<acp_thread::UserMessageId>,
-            _params: acp::PromptRequest,
-            _cx: &mut App,
-        ) -> Task<anyhow::Result<acp::PromptResponse>> {
-            Task::ready(Ok(acp::PromptResponse::new(acp::StopReason::EndTurn)))
-        }
-
-        fn cancel(&self, _session_id: &acp::SessionId, _cx: &mut App) {}
-
-        fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
-            self
-        }
-    }
-
     #[derive(Clone)]
     struct ResumeOnlyAgentConnection;
 

crates/agent_ui/src/conversation_view/thread_view.rs 🔗

@@ -326,13 +326,9 @@ pub struct ThreadView {
     pub add_context_menu_handle: PopoverMenuHandle<ContextMenu>,
     pub thinking_effort_menu_handle: PopoverMenuHandle<ContextMenu>,
     pub project: WeakEntity<Project>,
-    pub recent_history_entries: Vec<AgentSessionInfo>,
-    pub hovered_recent_history_item: Option<usize>,
     pub show_external_source_prompt_warning: bool,
     pub show_codex_windows_warning: bool,
     pub generating_indicator_in_list: bool,
-    pub history: Option<Entity<ThreadHistory>>,
-    pub _history_subscription: Option<Subscription>,
 }
 impl Focusable for ThreadView {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
@@ -387,12 +383,6 @@ impl ThreadView {
         let has_commands = !session_capabilities.read().available_commands().is_empty();
         let placeholder = placeholder_text(agent_display_name.as_ref(), has_commands);
 
-        let history_subscription = history.as_ref().map(|h| {
-            cx.observe(h, |this, history, cx| {
-                this.update_recent_history_from_cache(&history, cx);
-            })
-        });
-
         let mut should_auto_submit = false;
         let mut show_external_source_prompt_warning = false;
 
@@ -500,11 +490,6 @@ impl ThreadView {
             }));
         }));
 
-        let recent_history_entries = history
-            .as_ref()
-            .map(|h| h.read(cx).get_recent_sessions(3))
-            .unwrap_or_default();
-
         let mut this = Self {
             id,
             parent_id,
@@ -567,11 +552,7 @@ impl ThreadView {
             add_context_menu_handle: PopoverMenuHandle::default(),
             thinking_effort_menu_handle: PopoverMenuHandle::default(),
             project,
-            recent_history_entries,
-            hovered_recent_history_item: None,
             show_external_source_prompt_warning,
-            history,
-            _history_subscription: history_subscription,
             show_codex_windows_warning,
             generating_indicator_in_list: false,
         };
@@ -8276,16 +8257,6 @@ impl ThreadView {
             .into_any_element()
     }
 
-    fn update_recent_history_from_cache(
-        &mut self,
-        history: &Entity<ThreadHistory>,
-        cx: &mut Context<Self>,
-    ) {
-        self.recent_history_entries = history.read(cx).get_recent_sessions(3);
-        self.hovered_recent_history_item = None;
-        cx.notify();
-    }
-
     fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Callout {
         Callout::new()
             .icon(IconName::Warning)

crates/agent_ui/src/thread_history.rs 🔗

@@ -1,6 +1,6 @@
 use acp_thread::{AgentSessionInfo, AgentSessionList, AgentSessionListRequest, SessionListUpdate};
 use agent_client_protocol as acp;
-use gpui::{App, Task};
+use gpui::Task;
 use std::rc::Rc;
 use ui::prelude::*;
 
@@ -75,10 +75,6 @@ impl ThreadHistory {
         }));
     }
 
-    pub(crate) fn refresh_full_history(&mut self, cx: &mut Context<Self>) {
-        self.refresh_sessions(true, cx);
-    }
-
     fn apply_info_update(
         &mut self,
         session_id: acp::SessionId,
@@ -178,10 +174,6 @@ impl ThreadHistory {
         });
     }
 
-    pub(crate) fn is_empty(&self) -> bool {
-        self.sessions.is_empty()
-    }
-
     pub fn refresh(&mut self, _cx: &mut Context<Self>) {
         self.session_list.notify_refresh();
     }
@@ -196,26 +188,6 @@ impl ThreadHistory {
     pub(crate) fn sessions(&self) -> &[AgentSessionInfo] {
         &self.sessions
     }
-
-    pub(crate) fn get_recent_sessions(&self, limit: usize) -> Vec<AgentSessionInfo> {
-        self.sessions.iter().take(limit).cloned().collect()
-    }
-
-    pub fn supports_delete(&self) -> bool {
-        self.session_list.supports_delete()
-    }
-
-    pub(crate) fn delete_session(
-        &self,
-        session_id: &acp::SessionId,
-        cx: &mut App,
-    ) -> Task<anyhow::Result<()>> {
-        self.session_list.delete_session(session_id, cx)
-    }
-
-    pub(crate) fn delete_sessions(&self, cx: &mut App) -> Task<anyhow::Result<()>> {
-        self.session_list.delete_sessions(cx)
-    }
 }
 
 #[cfg(test)]
@@ -420,7 +392,7 @@ mod tests {
         cx.run_until_parked();
         session_list.clear_requested_cursors();
 
-        history.update(cx, |history, cx| history.refresh_full_history(cx));
+        history.update(cx, |history, cx| history.refresh_sessions(true, cx));
         cx.run_until_parked();
 
         history.update(cx, |history, _cx| {
@@ -454,7 +426,7 @@ mod tests {
         let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
         cx.run_until_parked();
 
-        history.update(cx, |history, cx| history.refresh_full_history(cx));
+        history.update(cx, |history, cx| history.refresh_sessions(true, cx));
         cx.run_until_parked();
         session_list.clear_requested_cursors();
 
@@ -485,11 +457,11 @@ mod tests {
         let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
         cx.run_until_parked();
 
-        history.update(cx, |history, cx| history.refresh_full_history(cx));
+        history.update(cx, |history, cx| history.refresh_sessions(true, cx));
         cx.run_until_parked();
         session_list.clear_requested_cursors();
 
-        history.update(cx, |history, cx| history.refresh_full_history(cx));
+        history.update(cx, |history, cx| history.refresh_sessions(true, cx));
         cx.run_until_parked();
 
         history.update(cx, |history, _cx| {
@@ -514,7 +486,7 @@ mod tests {
         let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
         cx.run_until_parked();
 
-        history.update(cx, |history, cx| history.refresh_full_history(cx));
+        history.update(cx, |history, cx| history.refresh_sessions(true, cx));
         cx.run_until_parked();
 
         session_list.clear_requested_cursors();
@@ -558,7 +530,7 @@ mod tests {
         cx.run_until_parked();
         session_list.clear_requested_cursors();
 
-        history.update(cx, |history, cx| history.refresh_full_history(cx));
+        history.update(cx, |history, cx| history.refresh_sessions(true, cx));
         cx.run_until_parked();
 
         history.update(cx, |history, _cx| {

crates/agent_ui/src/thread_history_view.rs 🔗

@@ -1,751 +0,0 @@
-use crate::thread_history::ThreadHistory;
-use crate::{DEFAULT_THREAD_TITLE, RemoveHistory, RemoveSelectedThread};
-use acp_thread::AgentSessionInfo;
-use chrono::{Datelike as _, Local, NaiveDate, TimeDelta, Utc};
-use editor::{Editor, EditorEvent};
-use fuzzy::StringMatchCandidate;
-use gpui::{
-    AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, ScrollStrategy, Task,
-    UniformListScrollHandle, Window, uniform_list,
-};
-use std::{fmt::Display, ops::Range};
-use text::Bias;
-use time::{OffsetDateTime, UtcOffset};
-use ui::{
-    HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip, WithScrollbar,
-    prelude::*,
-};
-
-pub(crate) fn thread_title(entry: &AgentSessionInfo) -> SharedString {
-    entry
-        .title
-        .clone()
-        .filter(|title| !title.is_empty())
-        .unwrap_or_else(|| DEFAULT_THREAD_TITLE.into())
-}
-
-pub struct ThreadHistoryView {
-    history: Entity<ThreadHistory>,
-    scroll_handle: UniformListScrollHandle,
-    selected_index: usize,
-    hovered_index: Option<usize>,
-    search_editor: Entity<Editor>,
-    search_query: SharedString,
-    visible_items: Vec<ListItemType>,
-    local_timezone: UtcOffset,
-    confirming_delete_history: bool,
-    _visible_items_task: Task<()>,
-    _subscriptions: Vec<gpui::Subscription>,
-}
-
-enum ListItemType {
-    BucketSeparator(TimeBucket),
-    Entry {
-        entry: AgentSessionInfo,
-        format: EntryTimeFormat,
-    },
-    SearchResult {
-        entry: AgentSessionInfo,
-        positions: Vec<usize>,
-    },
-}
-
-impl ListItemType {
-    fn history_entry(&self) -> Option<&AgentSessionInfo> {
-        match self {
-            ListItemType::Entry { entry, .. } => Some(entry),
-            ListItemType::SearchResult { entry, .. } => Some(entry),
-            _ => None,
-        }
-    }
-}
-
-pub enum ThreadHistoryViewEvent {
-    Open(AgentSessionInfo),
-}
-
-impl EventEmitter<ThreadHistoryViewEvent> for ThreadHistoryView {}
-
-impl ThreadHistoryView {
-    pub fn new(
-        history: Entity<ThreadHistory>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let search_editor = cx.new(|cx| {
-            let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Search threads...", window, cx);
-            editor
-        });
-
-        let search_editor_subscription =
-            cx.subscribe(&search_editor, |this, search_editor, event, cx| {
-                if let EditorEvent::BufferEdited = event {
-                    let query = search_editor.read(cx).text(cx);
-                    if this.search_query != query {
-                        this.search_query = query.into();
-                        this.update_visible_items(false, cx);
-                    }
-                }
-            });
-
-        let history_subscription = cx.observe(&history, |this, _, cx| {
-            this.update_visible_items(true, cx);
-        });
-
-        let scroll_handle = UniformListScrollHandle::default();
-
-        let mut this = Self {
-            history,
-            scroll_handle,
-            selected_index: 0,
-            hovered_index: None,
-            visible_items: Default::default(),
-            search_editor,
-            local_timezone: UtcOffset::from_whole_seconds(
-                chrono::Local::now().offset().local_minus_utc(),
-            )
-            .unwrap(),
-            search_query: SharedString::default(),
-            confirming_delete_history: false,
-            _subscriptions: vec![search_editor_subscription, history_subscription],
-            _visible_items_task: Task::ready(()),
-        };
-        this.update_visible_items(false, cx);
-        this
-    }
-
-    pub fn history(&self) -> &Entity<ThreadHistory> {
-        &self.history
-    }
-
-    fn update_visible_items(&mut self, preserve_selected_item: bool, cx: &mut Context<Self>) {
-        let entries = self.history.read(cx).sessions().to_vec();
-        let new_list_items = if self.search_query.is_empty() {
-            self.add_list_separators(entries, cx)
-        } else {
-            self.filter_search_results(entries, cx)
-        };
-        let selected_history_entry = if preserve_selected_item {
-            self.selected_history_entry().cloned()
-        } else {
-            None
-        };
-
-        self._visible_items_task = cx.spawn(async move |this, cx| {
-            let new_visible_items = new_list_items.await;
-            this.update(cx, |this, cx| {
-                let new_selected_index = if let Some(history_entry) = selected_history_entry {
-                    new_visible_items
-                        .iter()
-                        .position(|visible_entry| {
-                            visible_entry
-                                .history_entry()
-                                .is_some_and(|entry| entry.session_id == history_entry.session_id)
-                        })
-                        .unwrap_or(0)
-                } else {
-                    0
-                };
-
-                this.visible_items = new_visible_items;
-                this.set_selected_index(new_selected_index, Bias::Right, cx);
-                cx.notify();
-            })
-            .ok();
-        });
-    }
-
-    fn add_list_separators(
-        &self,
-        entries: Vec<AgentSessionInfo>,
-        cx: &App,
-    ) -> Task<Vec<ListItemType>> {
-        cx.background_spawn(async move {
-            let mut items = Vec::with_capacity(entries.len() + 1);
-            let mut bucket = None;
-            let today = Local::now().naive_local().date();
-
-            for entry in entries.into_iter() {
-                let entry_bucket = entry
-                    .updated_at
-                    .map(|timestamp| {
-                        let entry_date = timestamp.with_timezone(&Local).naive_local().date();
-                        TimeBucket::from_dates(today, entry_date)
-                    })
-                    .unwrap_or(TimeBucket::All);
-
-                if Some(entry_bucket) != bucket {
-                    bucket = Some(entry_bucket);
-                    items.push(ListItemType::BucketSeparator(entry_bucket));
-                }
-
-                items.push(ListItemType::Entry {
-                    entry,
-                    format: entry_bucket.into(),
-                });
-            }
-            items
-        })
-    }
-
-    fn filter_search_results(
-        &self,
-        entries: Vec<AgentSessionInfo>,
-        cx: &App,
-    ) -> Task<Vec<ListItemType>> {
-        let query = self.search_query.clone();
-        cx.background_spawn({
-            let executor = cx.background_executor().clone();
-            async move {
-                let mut candidates = Vec::with_capacity(entries.len());
-
-                for (idx, entry) in entries.iter().enumerate() {
-                    candidates.push(StringMatchCandidate::new(idx, &thread_title(entry)));
-                }
-
-                const MAX_MATCHES: usize = 100;
-
-                let matches = fuzzy::match_strings(
-                    &candidates,
-                    &query,
-                    false,
-                    true,
-                    MAX_MATCHES,
-                    &Default::default(),
-                    executor,
-                )
-                .await;
-
-                matches
-                    .into_iter()
-                    .map(|search_match| ListItemType::SearchResult {
-                        entry: entries[search_match.candidate_id].clone(),
-                        positions: search_match.positions,
-                    })
-                    .collect()
-            }
-        })
-    }
-
-    fn search_produced_no_matches(&self) -> bool {
-        self.visible_items.is_empty() && !self.search_query.is_empty()
-    }
-
-    fn selected_history_entry(&self) -> Option<&AgentSessionInfo> {
-        self.get_history_entry(self.selected_index)
-    }
-
-    fn get_history_entry(&self, visible_items_ix: usize) -> Option<&AgentSessionInfo> {
-        self.visible_items.get(visible_items_ix)?.history_entry()
-    }
-
-    fn set_selected_index(&mut self, mut index: usize, bias: Bias, cx: &mut Context<Self>) {
-        if self.visible_items.len() == 0 {
-            self.selected_index = 0;
-            return;
-        }
-        while matches!(
-            self.visible_items.get(index),
-            None | Some(ListItemType::BucketSeparator(..))
-        ) {
-            index = match bias {
-                Bias::Left => {
-                    if index == 0 {
-                        self.visible_items.len() - 1
-                    } else {
-                        index - 1
-                    }
-                }
-                Bias::Right => {
-                    if index >= self.visible_items.len() - 1 {
-                        0
-                    } else {
-                        index + 1
-                    }
-                }
-            };
-        }
-        self.selected_index = index;
-        self.scroll_handle
-            .scroll_to_item(index, ScrollStrategy::Top);
-        cx.notify()
-    }
-
-    fn select_previous(
-        &mut self,
-        _: &menu::SelectPrevious,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if self.selected_index == 0 {
-            self.set_selected_index(self.visible_items.len() - 1, Bias::Left, cx);
-        } else {
-            self.set_selected_index(self.selected_index - 1, Bias::Left, cx);
-        }
-    }
-
-    fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
-        if self.selected_index == self.visible_items.len() - 1 {
-            self.set_selected_index(0, Bias::Right, cx);
-        } else {
-            self.set_selected_index(self.selected_index + 1, Bias::Right, cx);
-        }
-    }
-
-    fn select_first(
-        &mut self,
-        _: &menu::SelectFirst,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.set_selected_index(0, Bias::Right, cx);
-    }
-
-    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
-        self.set_selected_index(self.visible_items.len() - 1, Bias::Left, cx);
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
-        self.confirm_entry(self.selected_index, cx);
-    }
-
-    fn confirm_entry(&mut self, ix: usize, cx: &mut Context<Self>) {
-        let Some(entry) = self.get_history_entry(ix) else {
-            return;
-        };
-        cx.emit(ThreadHistoryViewEvent::Open(entry.clone()));
-    }
-
-    fn remove_selected_thread(
-        &mut self,
-        _: &RemoveSelectedThread,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.remove_thread(self.selected_index, cx)
-    }
-
-    fn remove_thread(&mut self, visible_item_ix: usize, cx: &mut Context<Self>) {
-        let Some(entry) = self.get_history_entry(visible_item_ix) else {
-            return;
-        };
-        if !self.history.read(cx).supports_delete() {
-            return;
-        }
-        let session_id = entry.session_id.clone();
-        self.history.update(cx, |history, cx| {
-            history
-                .delete_session(&session_id, cx)
-                .detach_and_log_err(cx);
-        });
-    }
-
-    fn remove_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        if !self.history.read(cx).supports_delete() {
-            return;
-        }
-        self.history.update(cx, |history, cx| {
-            history.delete_sessions(cx).detach_and_log_err(cx);
-        });
-        self.confirming_delete_history = false;
-        cx.notify();
-    }
-
-    fn prompt_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        self.confirming_delete_history = true;
-        cx.notify();
-    }
-
-    fn cancel_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        self.confirming_delete_history = false;
-        cx.notify();
-    }
-
-    fn render_list_items(
-        &mut self,
-        range: Range<usize>,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Vec<AnyElement> {
-        self.visible_items
-            .get(range.clone())
-            .into_iter()
-            .flatten()
-            .enumerate()
-            .map(|(ix, item)| self.render_list_item(item, range.start + ix, cx))
-            .collect()
-    }
-
-    fn render_list_item(&self, item: &ListItemType, ix: usize, cx: &Context<Self>) -> AnyElement {
-        match item {
-            ListItemType::Entry { entry, format } => self
-                .render_history_entry(entry, *format, ix, Vec::default(), cx)
-                .into_any(),
-            ListItemType::SearchResult { entry, positions } => self.render_history_entry(
-                entry,
-                EntryTimeFormat::DateAndTime,
-                ix,
-                positions.clone(),
-                cx,
-            ),
-            ListItemType::BucketSeparator(bucket) => div()
-                .px(DynamicSpacing::Base06.rems(cx))
-                .pt_2()
-                .pb_1()
-                .child(
-                    Label::new(bucket.to_string())
-                        .size(LabelSize::XSmall)
-                        .color(Color::Muted),
-                )
-                .into_any_element(),
-        }
-    }
-
-    fn render_history_entry(
-        &self,
-        entry: &AgentSessionInfo,
-        format: EntryTimeFormat,
-        ix: usize,
-        highlight_positions: Vec<usize>,
-        cx: &Context<Self>,
-    ) -> AnyElement {
-        let selected = ix == self.selected_index;
-        let hovered = Some(ix) == self.hovered_index;
-        let entry_time = entry.updated_at;
-        let display_text = match (format, entry_time) {
-            (EntryTimeFormat::DateAndTime, Some(entry_time)) => {
-                let now = Utc::now();
-                let duration = now.signed_duration_since(entry_time);
-                let days = duration.num_days();
-
-                format!("{}d", days)
-            }
-            (EntryTimeFormat::TimeOnly, Some(entry_time)) => {
-                format.format_timestamp(entry_time.timestamp(), self.local_timezone)
-            }
-            (_, None) => "—".to_string(),
-        };
-
-        let title = thread_title(entry);
-        let full_date = entry_time
-            .map(|time| {
-                EntryTimeFormat::DateAndTime.format_timestamp(time.timestamp(), self.local_timezone)
-            })
-            .unwrap_or_else(|| "Unknown".to_string());
-
-        let supports_delete = self.history.read(cx).supports_delete();
-
-        h_flex()
-            .w_full()
-            .pb_1()
-            .child(
-                ListItem::new(ix)
-                    .rounded()
-                    .toggle_state(selected)
-                    .spacing(ListItemSpacing::Sparse)
-                    .start_slot(
-                        h_flex()
-                            .w_full()
-                            .gap_2()
-                            .justify_between()
-                            .child(
-                                HighlightedLabel::new(thread_title(entry), highlight_positions)
-                                    .size(LabelSize::Small)
-                                    .truncate(),
-                            )
-                            .child(
-                                Label::new(display_text)
-                                    .color(Color::Muted)
-                                    .size(LabelSize::XSmall),
-                            ),
-                    )
-                    .tooltip(move |_, cx| {
-                        Tooltip::with_meta(title.clone(), None, full_date.clone(), cx)
-                    })
-                    .on_hover(cx.listener(move |this, is_hovered, _window, cx| {
-                        if *is_hovered {
-                            this.hovered_index = Some(ix);
-                        } else if this.hovered_index == Some(ix) {
-                            this.hovered_index = None;
-                        }
-
-                        cx.notify();
-                    }))
-                    .end_slot::<IconButton>(if hovered && supports_delete {
-                        Some(
-                            IconButton::new("delete", IconName::Trash)
-                                .shape(IconButtonShape::Square)
-                                .icon_size(IconSize::XSmall)
-                                .icon_color(Color::Muted)
-                                .tooltip(move |_window, cx| {
-                                    Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
-                                })
-                                .on_click(cx.listener(move |this, _, _, cx| {
-                                    this.remove_thread(ix, cx);
-                                    cx.stop_propagation()
-                                })),
-                        )
-                    } else {
-                        None
-                    })
-                    .on_click(cx.listener(move |this, _, _, cx| this.confirm_entry(ix, cx))),
-            )
-            .into_any_element()
-    }
-}
-
-impl Focusable for ThreadHistoryView {
-    fn focus_handle(&self, cx: &App) -> FocusHandle {
-        self.search_editor.focus_handle(cx)
-    }
-}
-
-impl Render for ThreadHistoryView {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let has_no_history = self.history.read(cx).is_empty();
-        let supports_delete = self.history.read(cx).supports_delete();
-
-        v_flex()
-            .key_context("ThreadHistory")
-            .size_full()
-            .bg(cx.theme().colors().panel_background)
-            .on_action(cx.listener(Self::select_previous))
-            .on_action(cx.listener(Self::select_next))
-            .on_action(cx.listener(Self::select_first))
-            .on_action(cx.listener(Self::select_last))
-            .on_action(cx.listener(Self::confirm))
-            .on_action(cx.listener(Self::remove_selected_thread))
-            .on_action(cx.listener(|this, _: &RemoveHistory, window, cx| {
-                this.remove_history(window, cx);
-            }))
-            .child(
-                h_flex()
-                    .h(Tab::container_height(cx))
-                    .w_full()
-                    .py_1()
-                    .px_2()
-                    .gap_2()
-                    .justify_between()
-                    .border_b_1()
-                    .border_color(cx.theme().colors().border)
-                    .child(
-                        Icon::new(IconName::MagnifyingGlass)
-                            .color(Color::Muted)
-                            .size(IconSize::Small),
-                    )
-                    .child(self.search_editor.clone()),
-            )
-            .child({
-                let view = v_flex()
-                    .id("list-container")
-                    .relative()
-                    .overflow_hidden()
-                    .flex_grow();
-
-                if has_no_history {
-                    view.justify_center().items_center().child(
-                        Label::new("You don't have any past threads yet.")
-                            .size(LabelSize::Small)
-                            .color(Color::Muted),
-                    )
-                } else if self.search_produced_no_matches() {
-                    view.justify_center()
-                        .items_center()
-                        .child(Label::new("No threads match your search.").size(LabelSize::Small))
-                } else {
-                    view.child(
-                        uniform_list(
-                            "thread-history",
-                            self.visible_items.len(),
-                            cx.processor(|this, range: Range<usize>, window, cx| {
-                                this.render_list_items(range, window, cx)
-                            }),
-                        )
-                        .p_1()
-                        .pr_4()
-                        .track_scroll(&self.scroll_handle)
-                        .flex_grow(),
-                    )
-                    .vertical_scrollbar_for(&self.scroll_handle, window, cx)
-                }
-            })
-            .when(!has_no_history && supports_delete, |this| {
-                this.child(
-                    h_flex()
-                        .p_2()
-                        .border_t_1()
-                        .border_color(cx.theme().colors().border_variant)
-                        .when(!self.confirming_delete_history, |this| {
-                            this.child(
-                                Button::new("delete_history", "Delete All History")
-                                    .full_width()
-                                    .style(ButtonStyle::Outlined)
-                                    .label_size(LabelSize::Small)
-                                    .on_click(cx.listener(|this, _, window, cx| {
-                                        this.prompt_delete_history(window, cx);
-                                    })),
-                            )
-                        })
-                        .when(self.confirming_delete_history, |this| {
-                            this.w_full()
-                                .gap_2()
-                                .flex_wrap()
-                                .justify_between()
-                                .child(
-                                    h_flex()
-                                        .flex_wrap()
-                                        .gap_1()
-                                        .child(
-                                            Label::new("Delete all threads?")
-                                                .size(LabelSize::Small),
-                                        )
-                                        .child(
-                                            Label::new("You won't be able to recover them later.")
-                                                .size(LabelSize::Small)
-                                                .color(Color::Muted),
-                                        ),
-                                )
-                                .child(
-                                    h_flex()
-                                        .gap_1()
-                                        .child(
-                                            Button::new("cancel_delete", "Cancel")
-                                                .label_size(LabelSize::Small)
-                                                .on_click(cx.listener(|this, _, window, cx| {
-                                                    this.cancel_delete_history(window, cx);
-                                                })),
-                                        )
-                                        .child(
-                                            Button::new("confirm_delete", "Delete")
-                                                .style(ButtonStyle::Tinted(ui::TintColor::Error))
-                                                .color(Color::Error)
-                                                .label_size(LabelSize::Small)
-                                                .on_click(cx.listener(|_, _, window, cx| {
-                                                    window.dispatch_action(
-                                                        Box::new(RemoveHistory),
-                                                        cx,
-                                                    );
-                                                })),
-                                        ),
-                                )
-                        }),
-                )
-            })
-    }
-}
-
-#[derive(Clone, Copy)]
-pub enum EntryTimeFormat {
-    DateAndTime,
-    TimeOnly,
-}
-
-impl EntryTimeFormat {
-    fn format_timestamp(&self, timestamp: i64, timezone: UtcOffset) -> String {
-        let timestamp = OffsetDateTime::from_unix_timestamp(timestamp).unwrap();
-
-        match self {
-            EntryTimeFormat::DateAndTime => time_format::format_localized_timestamp(
-                timestamp,
-                OffsetDateTime::now_utc(),
-                timezone,
-                time_format::TimestampFormat::EnhancedAbsolute,
-            ),
-            EntryTimeFormat::TimeOnly => time_format::format_time(timestamp.to_offset(timezone)),
-        }
-    }
-}
-
-impl From<TimeBucket> for EntryTimeFormat {
-    fn from(bucket: TimeBucket) -> Self {
-        match bucket {
-            TimeBucket::Today => EntryTimeFormat::TimeOnly,
-            TimeBucket::Yesterday => EntryTimeFormat::TimeOnly,
-            TimeBucket::ThisWeek => EntryTimeFormat::DateAndTime,
-            TimeBucket::PastWeek => EntryTimeFormat::DateAndTime,
-            TimeBucket::All => EntryTimeFormat::DateAndTime,
-        }
-    }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-enum TimeBucket {
-    Today,
-    Yesterday,
-    ThisWeek,
-    PastWeek,
-    All,
-}
-
-impl TimeBucket {
-    fn from_dates(reference: NaiveDate, date: NaiveDate) -> Self {
-        if date == reference {
-            return TimeBucket::Today;
-        }
-
-        if date == reference - TimeDelta::days(1) {
-            return TimeBucket::Yesterday;
-        }
-
-        let week = date.iso_week();
-
-        if reference.iso_week() == week {
-            return TimeBucket::ThisWeek;
-        }
-
-        let last_week = (reference - TimeDelta::days(7)).iso_week();
-
-        if week == last_week {
-            return TimeBucket::PastWeek;
-        }
-
-        TimeBucket::All
-    }
-}
-
-impl Display for TimeBucket {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            TimeBucket::Today => write!(f, "Today"),
-            TimeBucket::Yesterday => write!(f, "Yesterday"),
-            TimeBucket::ThisWeek => write!(f, "This Week"),
-            TimeBucket::PastWeek => write!(f, "Past Week"),
-            TimeBucket::All => write!(f, "All"),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use chrono::NaiveDate;
-
-    #[test]
-    fn test_time_bucket_from_dates() {
-        let today = NaiveDate::from_ymd_opt(2025, 1, 15).unwrap();
-
-        assert_eq!(TimeBucket::from_dates(today, today), TimeBucket::Today);
-
-        let yesterday = NaiveDate::from_ymd_opt(2025, 1, 14).unwrap();
-        assert_eq!(
-            TimeBucket::from_dates(today, yesterday),
-            TimeBucket::Yesterday
-        );
-
-        let this_week = NaiveDate::from_ymd_opt(2025, 1, 13).unwrap();
-        assert_eq!(
-            TimeBucket::from_dates(today, this_week),
-            TimeBucket::ThisWeek
-        );
-
-        let past_week = NaiveDate::from_ymd_opt(2025, 1, 7).unwrap();
-        assert_eq!(
-            TimeBucket::from_dates(today, past_week),
-            TimeBucket::PastWeek
-        );
-
-        let old = NaiveDate::from_ymd_opt(2024, 12, 1).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, old), TimeBucket::All);
-    }
-}