From 4f5fff704c16adbb5f6f69d90b6fe86d4b5be1ee Mon Sep 17 00:00:00 2001 From: Kevin Ernst Date: Mon, 1 Jun 2026 09:46:49 +0200 Subject: [PATCH] sidebar: Include archived threads in the thread switcher dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The thread switcher (the per-project dropdown reached via ToggleThreadSwitcher) only sourced entries from the sidebar's filtered list, which excludes archived threads — so archived/closed agent threads weren't reachable from it. The switcher now also pulls archived metadata from ThreadMetadataStore::archived_entries, scoped to currently-open projects (so the entry has a valid Workspace to switch to), de-duped against the active entries already sourced from the sidebar list. --- crates/sidebar/src/sidebar.rs | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index bef411b5498d25..f80bd2b3f14ad0 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -5306,6 +5306,7 @@ impl Sidebar { fn mru_entries_for_switcher(&self, cx: &App) -> Vec { let mut current_header_label: Option = None; let mut current_header_key: Option = None; + let mut seen_thread_ids: HashSet = HashSet::new(); let mut entries: Vec = self .contents .entries @@ -5334,6 +5335,7 @@ impl Sidebar { }) } }?; + seen_thread_ids.insert(thread.metadata.thread_id); let notified = self.contents.is_thread_notified(&thread.metadata.thread_id); let timestamp: SharedString = format_history_entry_timestamp(Self::thread_display_time(&thread.metadata)) @@ -5387,6 +5389,68 @@ impl Sidebar { }) .collect(); + // Also include archived threads whose project is currently open, so the + // switcher can reach archived/closed agent threads, not just active + // ones. Without this they're invisible to the dropdown even though + // they're still real history. + if let Some(multi_workspace) = self.multi_workspace.upgrade() { + let open_workspaces: Vec> = + multi_workspace.read(cx).workspaces().cloned().collect(); + let workspace_for_paths = |folder_paths: &PathList| -> Option> { + open_workspaces + .iter() + .find(|ws| workspace_path_list(ws, cx) == *folder_paths) + .cloned() + }; + for metadata in ThreadMetadataStore::global(cx).read(cx).archived_entries() { + if !seen_thread_ids.insert(metadata.thread_id) { + continue; + } + let Some(workspace) = workspace_for_paths(metadata.folder_paths()) else { + continue; + }; + let project_name = workspace + .read(cx) + .project() + .read(cx) + .visible_worktrees(cx) + .next() + .map(|wt| SharedString::from(wt.read(cx).root_name_str().to_string())); + let timestamp: SharedString = + format_history_entry_timestamp(Self::thread_display_time(metadata)).into(); + // Inline icon resolution: the full `resolve_agent_icon` closure + // in `rebuild_contents` also surfaces custom-agent SVG icons via + // the agent_server_store, but that's only worth the plumbing if + // the archived-switcher rows visibly need it. Stick to a + // built-in icon by Agent kind. + let icon = match Agent::from(metadata.agent_id.clone()) { + Agent::NativeAgent => IconName::ZedAgent, + Agent::Custom { .. } => IconName::Terminal, + _ => IconName::ZedAgent, + }; + let icon_from_external_svg: Option = None; + let worktrees = worktree_info_from_thread_paths( + &metadata.worktree_paths, + &HashMap::new(), + ); + entries.push(ThreadSwitcherEntry::Thread(ThreadSwitcherThreadEntry { + title: metadata.display_title(), + icon, + icon_from_external_svg, + status: AgentThreadStatus::Completed, + metadata: metadata.clone(), + workspace, + project_name, + worktrees, + diff_stats: DiffStats::default(), + is_draft: false, + is_title_generating: false, + notified: false, + timestamp, + })); + } + } + entries.sort_by(|a, b| self.switcher_entry_cmp(a, b)); entries