agent_ui: Improve the new thread worktree UX (#53941)

Danilo Leal and Nathan Sobo created

Closes https://github.com/zed-industries/zed/issues/53262

- Remove the ability to pick a branch from the agent panel; delegate
this to the title bar picker
- Make the worktree creation earger, just as you selected whether you
want to create it from main or current branch
- Remove flicker when creating a new worktree and switching to a
previously existing one
- Improve some UI stuff: how we display that a worktree is
creating/loading, the branch and worktree icons, etc.
- Fixed a bug where worktrees in a detached HEAD state wouldn't show up
in the worktree pickers

A big part of the diff of this PR is the removal of everything involved
with the `StartThreadIn` enum/the set up involved in only creating the
worktree by the time of the first prompt send.

Release Notes:

- Agent: Improved and simplified the UX of creating threads in Git
worktrees.
- Git: Fixed a bug where worktrees in a detached HEAD state wouldn't
show up in the worktree picker.

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

assets/icons/git_branch.svg                          |    7 
assets/icons/git_branch_alt.svg                      |    7 
assets/icons/git_branch_plus.svg                     |    4 
assets/icons/git_worktree.svg                        |   10 
assets/icons/inception.svg                           |   16 
assets/icons/list_filter.svg                         |    6 
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                   |  739 +++------
crates/agent_ui/src/agent_ui.rs                      |   39 
crates/agent_ui/src/conversation_view/thread_view.rs |   45 
crates/agent_ui/src/thread_branch_picker.rs          |  837 ----------
crates/agent_ui/src/thread_worktree_archive.rs       |    1 
crates/agent_ui/src/thread_worktree_picker.rs        | 1112 +++++++++----
crates/agent_ui/src/ui/hold_for_default.rs           |    1 
crates/collab/tests/integration/git_tests.rs         |    3 
crates/component_preview/src/component_preview.rs    |    2 
crates/fs/src/fake_git_repo.rs                       |    2 
crates/git/src/repository.rs                         |   42 
crates/git_ui/src/branch_picker.rs                   |    2 
crates/git_ui/src/git_panel.rs                       |   24 
crates/git_ui/src/worktree_picker.rs                 |  105 
crates/icons/src/icons.rs                            |    1 
crates/notifications/src/status_toast.rs             |    2 
crates/project/src/git_store.rs                      |    8 
crates/proto/proto/git.proto                         |    1 
crates/recent_projects/src/recent_projects.rs        |    2 
crates/remote_server/src/remote_editing_tests.rs     |    1 
crates/sidebar/src/sidebar.rs                        |   10 
crates/sidebar/src/sidebar_tests.rs                  |   36 
crates/workspace/src/multi_workspace.rs              |   22 
crates/workspace/src/multi_workspace_tests.rs        |    6 
crates/workspace/src/persistence.rs                  |   11 
crates/workspace/src/workspace.rs                    |    4 
crates/worktree/tests/integration/main.rs            |    2 
crates/zed/src/visual_test_runner.rs                 |  639 --------
37 files changed, 1,294 insertions(+), 2,461 deletions(-)

Detailed changes

assets/icons/git_branch.svg πŸ”—

@@ -1 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5 3v7M11.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4.5 13a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM11 6a5 5 0 0 1-5 5"/></svg>
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.5 13C5.32843 13 6 12.3284 6 11.5C6 10.6716 5.32843 10 4.5 10C3.67157 10 3 10.6716 3 11.5C3 12.3284 3.67157 13 4.5 13Z" stroke="#C6CAD0" stroke-width="1.2"/>
+<path d="M11.5 6C12.3284 6 13 5.3284 13 4.5C13 3.6716 12.3284 3 11.5 3C10.6716 3 10 3.6716 10 4.5C10 5.3284 10.6716 6 11.5 6Z" stroke="#C6CAD0" stroke-width="1.2"/>
+<path d="M4.5 10L4.5 3" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M10 4.44133C8.54131 4.44133 7.14236 5.02697 6.11091 6.06943C5.07946 7.11188 4.5 8.52575 4.5 10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

assets/icons/git_branch_alt.svg πŸ”—

@@ -1,7 +0,0 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.5 14C5.32843 14 6 13.3284 6 12.5C6 11.6716 5.32843 11 4.5 11C3.67157 11 3 11.6716 3 12.5C3 13.3284 3.67157 14 4.5 14Z" stroke="black" stroke-width="1.2"/>
-<path d="M4.5 11V5.5" stroke="black" stroke-width="1.2"/>
-<path d="M4.5 10C4.5 10 4.875 8 6.5 8C7.29195 8 9.00787 8 9.87553 8C10.773 8 11.5 7.32843 11.5 6.5V5.5" stroke="black" stroke-width="1.2"/>
-<path d="M4.5 6C5.32843 6 6 5.32843 6 4.5C6 3.67157 5.32843 3 4.5 3C3.67157 3 3 3.67157 3 4.5C3 5.32843 3.67157 6 4.5 6Z" stroke="black" stroke-width="1.2"/>
-<path d="M11.5 6C12.3284 6 13 5.32843 13 4.5C13 3.67157 12.3284 3 11.5 3C10.6716 3 10 3.67157 10 4.5C10 5.32843 10.6716 6 11.5 6Z" stroke="black" stroke-width="1.2"/>
-</svg>

assets/icons/git_branch_plus.svg πŸ”—

@@ -1,8 +1,8 @@
 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
 <path d="M4 2V10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12 6C12.5304 6 13.0391 5.78929 13.4142 5.41421C13.7893 5.03914 14 4.53043 14 4C14 3.46957 13.7893 2.96086 13.4142 2.58579C13.0391 2.21071 12.5304 2 12 2C11.4696 2 10.9609 2.21071 10.5858 2.58579C10.2107 2.96086 10 3.46957 10 4C10 4.53043 10.2107 5.03914 10.5858 5.41421C10.9609 5.78929 11.4696 6 12 6Z" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4 14C4.53043 14 5.03914 13.7893 5.41421 13.4142C5.78929 13.0391 6 12.5304 6 12C6 11.4696 5.78929 10.9609 5.41421 10.5858C5.03914 10.2107 4.53043 10 4 10C3.46957 10 2.96086 10.2107 2.58579 10.5858C2.21071 10.9609 2 11.4696 2 12C2 12.5304 2.21071 13.0391 2.58579 13.4142C2.96086 13.7893 3.46957 14 4 14Z" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
 <path d="M10 4C8.4087 4 6.88258 4.63214 5.75736 5.75736C4.63214 6.88258 4 8.4087 4 10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
 <path d="M12 10V14" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
 <path d="M14 12H10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4 13C4.82843 13 5.5 12.3284 5.5 11.5C5.5 10.6716 4.82843 10 4 10C3.17157 10 2.5 10.6716 2.5 11.5C2.5 12.3284 3.17157 13 4 13Z" stroke="#C6CAD0" stroke-width="1.2"/>
+<path d="M11.5 5.5C12.3284 5.5 13 4.8284 13 4C13 3.1716 12.3284 2.5 11.5 2.5C10.6716 2.5 10 3.1716 10 4C10 4.8284 10.6716 5.5 11.5 5.5Z" stroke="#C6CAD0" stroke-width="1.2"/>
 </svg>

assets/icons/git_worktree.svg πŸ”—

@@ -1,7 +1,7 @@
 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.99567 13.0812C8.93101 13.0812 9.68925 12.3229 9.68925 11.3876C9.68925 10.4522 8.93101 9.694 7.99567 9.694C7.06033 9.694 6.30209 10.4522 6.30209 11.3876C6.30209 12.3229 7.06033 13.0812 7.99567 13.0812Z" stroke="#A9AFBC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.61023 6.30643C5.54557 6.30643 6.30381 5.54819 6.30381 4.61286C6.30381 3.67752 5.54557 2.91928 4.61023 2.91928C3.6749 2.91928 2.91666 3.67752 2.91666 4.61286C2.91666 5.54819 3.6749 6.30643 4.61023 6.30643Z" stroke="#A9AFBC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.3915 6.30643C12.3268 6.30643 13.0851 5.54819 13.0851 4.61286C13.0851 3.67752 12.3268 2.91928 11.3915 2.91928C10.4561 2.91928 9.69791 3.67752 9.69791 4.61286C9.69791 5.54819 10.4561 6.30643 11.3915 6.30643Z" stroke="#A9AFBC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.3889 6.306V7.43505C11.3889 7.77377 11.1631 7.99958 10.8244 7.99958H5.17912C4.8404 7.99958 4.61459 7.77377 4.61459 7.43505V6.306" stroke="#A9AFBC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 8V9.69358" stroke="#A9AFBC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.81506 7.834L11.3571 3.29195" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.53091 9.5509L11.3556 12.3756" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.28955 7.834H6.80326" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.7743 6.31279V2.87418H8.33571" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.33417 12.7932L11.7728 12.7932L11.7728 9.35463" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
 </svg>

assets/icons/inception.svg πŸ”—

@@ -1,11 +1,9 @@
-<svg width="28" height="28" viewBox="0 0 28 28" fill="none" id="svg1378540956_510">
-<g clip-path="url(#svg1378540956_510_clip0_1_1506)" transform="translate(4, 4) scale(0.857)">
-<path d="M17.0547 0.372066H8.52652L-0.00165176 8.90024V17.4284H8.52652V8.90024H17.0547V0.372066Z" fill="#1A1C20"></path>
-<path d="M10.1992 27.6279H18.7274L27.2556 19.0998V10.5716H18.7274V19.0998H10.1992V27.6279Z" fill="#1A1C20"></path>
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<mask id="mask0_4326_1680" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="1" y="1" width="14" height="14">
+<path d="M14.6738 1.32619H1.32619V14.6738H14.6738V1.32619Z" fill="white"/>
+</mask>
+<g mask="url(#mask0_4326_1680)">
+<path d="M9.6781 1.32619H5.50173L1.32536 5.50256V9.67892H5.50173V5.50256H9.6781V1.32619Z" fill="#C6CAD0"/>
+<path d="M6.32088 14.6738H10.4973L14.6736 10.4974V6.32104H10.4973V10.4974H6.32088V14.6738Z" fill="#C6CAD0"/>
 </g>
-<defs>
-<clipPath id="svg1378540956_510_clip0_1_1506">
-<rect width="27.2559" height="27.2559" fill="white" transform="translate(0 0.37207)"></rect>
-</clipPath>
-</defs>
 </svg>

assets/icons/list_filter.svg πŸ”—

@@ -1 +1,5 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-filter-icon lucide-list-filter"><path d="M3 6h18"/><path d="M7 12h10"/><path d="M10 18h4"/></svg>
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2 4H14" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.66666 8H11.3333" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.66666 12H9.33332" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

assets/keymaps/default-linux.json πŸ”—

@@ -237,7 +237,7 @@
       "shift-alt-j": "agent::ToggleNavigationMenu",
       "shift-alt-i": "agent::ToggleOptionsMenu",
       "ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
-      "ctrl-shift-t": "agent::CycleStartThreadIn",
+      "ctrl-shift-t": "agent::ToggleWorktreeSelector",
       "shift-alt-escape": "agent::ExpandMessageEditor",
       "ctrl->": "agent::AddSelectionToThread",
       "ctrl-shift-e": "project_panel::ToggleFocus",

assets/keymaps/default-macos.json πŸ”—

@@ -275,7 +275,7 @@
       "cmd-shift-j": "agent::ToggleNavigationMenu",
       "cmd-alt-m": "agent::ToggleOptionsMenu",
       "cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
-      "cmd-shift-t": "agent::CycleStartThreadIn",
+      "cmd-shift-t": "agent::ToggleWorktreeSelector",
       "shift-alt-escape": "agent::ExpandMessageEditor",
       "cmd->": "agent::AddSelectionToThread",
       "cmd-shift-e": "project_panel::ToggleFocus",

assets/keymaps/default-windows.json πŸ”—

@@ -238,7 +238,7 @@
       "shift-alt-j": "agent::ToggleNavigationMenu",
       "shift-alt-i": "agent::ToggleOptionsMenu",
       "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
-      "ctrl-shift-t": "agent::CycleStartThreadIn",
+      "ctrl-shift-t": "agent::ToggleWorktreeSelector",
       "shift-alt-escape": "agent::ExpandMessageEditor",
       "ctrl-shift-.": "agent::AddSelectionToThread",
       "ctrl-shift-e": "project_panel::ToggleFocus",

crates/agent_ui/src/agent_panel.rs πŸ”—

@@ -32,13 +32,13 @@ use zed_actions::{
 use crate::DEFAULT_THREAD_TITLE;
 use crate::thread_metadata_store::{ThreadId, ThreadMetadata, ThreadMetadataStore};
 use crate::{
-    AddContextServer, AgentDiffPane, ConversationView, CopyThreadToClipboard, CycleStartThreadIn,
+    AddContextServer, AgentDiffPane, ConversationView, CopyThreadToClipboard, CreateWorktree,
     Follow, InlineAssistant, LoadThreadFromClipboard, NewThread, NewWorktreeBranchTarget,
     OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
-    StartThreadIn, ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
+    SwitchWorktree, ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
+    ToggleWorktreeSelector,
     agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
     conversation_view::{AcpThreadViewEvent, ThreadView},
-    thread_branch_picker::ThreadBranchPicker,
     thread_worktree_picker::ThreadWorktreePicker,
     ui::EndTrialUpsell,
 };
@@ -67,7 +67,6 @@ use gpui::{
 };
 use language::LanguageRegistry;
 use language_model::LanguageModelRegistry;
-use project::git_store::{GitStoreEvent, RepositoryEvent};
 use project::project_settings::ProjectSettings;
 use project::{Project, ProjectPath, Worktree, WorktreePaths, linked_worktree_short_name};
 use prompt_store::{PromptStore, UserPromptId};
@@ -80,13 +79,13 @@ use terminal::terminal_settings::TerminalSettings;
 use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
 use theme_settings::ThemeSettings;
 use ui::{
-    Button, ButtonLike, Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, PopoverMenu,
-    PopoverMenuHandle, Tab, Tooltip, prelude::*, utils::WithRemSize,
+    Button, Callout, ContextMenu, ContextMenuEntry, PopoverMenu, PopoverMenuHandle, Tab, Tooltip,
+    prelude::*, utils::WithRemSize,
 };
 use util::{ResultExt as _, debug_panic};
 use workspace::{
-    CollaboratorId, DraggedSelection, DraggedTab, PathList, SerializedPathList,
-    ToggleWorkspaceSidebar, ToggleZoom, Workspace, WorkspaceId,
+    CollaboratorId, DockStructure, DraggedSelection, DraggedTab, OpenMode, PathList,
+    SerializedPathList, ToggleWorkspaceSidebar, ToggleZoom, Workspace, WorkspaceId,
     dock::{DockPosition, Panel, PanelEvent},
 };
 
@@ -170,8 +169,6 @@ struct SerializedAgentPanel {
     selected_agent: Option<Agent>,
     #[serde(default)]
     last_active_thread: Option<SerializedActiveThread>,
-    #[serde(default)]
-    start_thread_in: Option<StartThreadIn>,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -279,6 +276,14 @@ pub fn init(cx: &mut App) {
                         });
                     }
                 })
+                .register_action(|workspace, _: &ToggleWorktreeSelector, window, cx| {
+                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                        workspace.focus_panel::<AgentPanel>(window, cx);
+                        panel.update(cx, |panel, cx| {
+                            panel.toggle_worktree_selector(&ToggleWorktreeSelector, window, cx);
+                        });
+                    }
+                })
                 .register_action(|_workspace, _: &ResetOnboarding, window, cx| {
                     window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
                     window.refresh();
@@ -417,20 +422,6 @@ pub fn init(cx: &mut App) {
                         });
                     },
                 )
-                .register_action(|workspace, action: &StartThreadIn, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        panel.update(cx, |panel, cx| {
-                            panel.set_start_thread_in(action, window, cx);
-                        });
-                    }
-                })
-                .register_action(|workspace, _: &CycleStartThreadIn, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        panel.update(cx, |panel, cx| {
-                            panel.cycle_start_thread_in(window, cx);
-                        });
-                    }
-                })
                 .register_action(
                     |workspace: &mut Workspace, _: &AddSelectionToThread, window, cx| {
                         let active_editor = workspace
@@ -493,6 +484,28 @@ pub fn init(cx: &mut App) {
                             });
                         });
                     },
+                )
+                .register_action(
+                    |workspace: &mut Workspace, action: &CreateWorktree, window, cx| {
+                        let previous_state =
+                            AgentPanel::capture_workspace_state(workspace, window, cx);
+                        if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                            panel.update(cx, |panel, cx| {
+                                panel.create_worktree(action, previous_state, window, cx);
+                            });
+                        }
+                    },
+                )
+                .register_action(
+                    |workspace: &mut Workspace, action: &SwitchWorktree, window, cx| {
+                        let previous_state =
+                            AgentPanel::capture_workspace_state(workspace, window, cx);
+                        if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                            panel.update(cx, |panel, cx| {
+                                panel.switch_to_worktree(action, previous_state, window, cx);
+                            });
+                        }
+                    },
                 );
         },
     )
@@ -642,124 +655,10 @@ enum WhichFontSize {
     None,
 }
 
-struct StartThreadInLabel {
-    prefix: Option<SharedString>,
-    label: SharedString,
-    suffix: Option<SharedString>,
-}
-
-impl StartThreadIn {
-    fn trigger_label(&self, project: &Project, cx: &App) -> StartThreadInLabel {
-        match self {
-            Self::LocalProject => {
-                let suffix = project.active_repository(cx).and_then(|repo| {
-                    let repo = repo.read(cx);
-                    let work_dir = &repo.original_repo_abs_path;
-                    let visible_paths: Vec<_> = project
-                        .visible_worktrees(cx)
-                        .map(|wt| wt.read(cx).abs_path().to_path_buf())
-                        .collect();
-
-                    for linked in repo.linked_worktrees() {
-                        if visible_paths.contains(&linked.path) {
-                            return Some(SharedString::from(format!(
-                                "({})",
-                                linked.display_name()
-                            )));
-                        }
-                    }
-
-                    if let Some(name) = linked_worktree_short_name(
-                        repo.original_repo_abs_path.as_ref(),
-                        repo.work_directory_abs_path.as_ref(),
-                    ) {
-                        if visible_paths
-                            .iter()
-                            .any(|p| p.as_path() == repo.work_directory_abs_path.as_ref())
-                        {
-                            return Some(SharedString::from(format!("({})", name)));
-                        }
-                    }
-
-                    if visible_paths
-                        .iter()
-                        .any(|p| p.as_path() == work_dir.as_ref())
-                    {
-                        return Some("(main)".into());
-                    }
-
-                    None
-                });
-
-                StartThreadInLabel {
-                    prefix: None,
-                    label: "Current Worktree".into(),
-                    suffix,
-                }
-            }
-            Self::NewWorktree {
-                worktree_name: Some(worktree_name),
-                ..
-            } => StartThreadInLabel {
-                prefix: Some("New:".into()),
-                label: worktree_name.clone().into(),
-                suffix: None,
-            },
-            Self::NewWorktree { .. } => StartThreadInLabel {
-                prefix: None,
-                label: "New Git Worktree".into(),
-                suffix: None,
-            },
-            Self::LinkedWorktree { display_name, .. } => StartThreadInLabel {
-                prefix: Some("From:".into()),
-                label: display_name.clone().into(),
-                suffix: None,
-            },
-        }
-    }
-
-    fn branch_trigger_label(&self, project: &Project, cx: &App) -> Option<StartThreadInLabel> {
-        match self {
-            Self::NewWorktree { branch_target, .. } => {
-                let label: SharedString = match branch_target {
-                    NewWorktreeBranchTarget::CurrentBranch => {
-                        if project.repositories(cx).len() > 1 {
-                            "current branches".into()
-                        } else {
-                            project
-                                .active_repository(cx)
-                                .and_then(|repo| {
-                                    repo.read(cx)
-                                        .branch
-                                        .as_ref()
-                                        .map(|branch| SharedString::from(branch.name().to_string()))
-                                })
-                                .unwrap_or_else(|| "HEAD".into())
-                        }
-                    }
-                    NewWorktreeBranchTarget::ExistingBranch { name } => name.clone().into(),
-                    NewWorktreeBranchTarget::CreateBranch {
-                        from_ref: Some(from_ref),
-                        ..
-                    } => from_ref.clone().into(),
-                    NewWorktreeBranchTarget::CreateBranch { name, .. } => name.clone().into(),
-                };
-
-                Some(StartThreadInLabel {
-                    prefix: None,
-                    label,
-                    suffix: None,
-                })
-            }
-            _ => None,
-        }
-    }
-}
-
 #[derive(Clone, Debug)]
-#[allow(dead_code)]
 pub enum WorktreeCreationStatus {
-    Creating,
+    Creating(SharedString),
+    Loading(SharedString),
     Error(SharedString),
 }
 
@@ -771,9 +670,46 @@ enum WorktreeCreationArgs {
     },
     Linked {
         worktree_path: PathBuf,
+        display_name: String,
     },
 }
 
+struct PreviousWorkspaceState {
+    dock_structure: DockStructure,
+    open_file_paths: Vec<PathBuf>,
+    active_file_path: Option<PathBuf>,
+}
+
+#[cfg(test)]
+impl PreviousWorkspaceState {
+    /// An empty state with all docks hidden and no open files.
+    fn empty() -> Self {
+        use workspace::DockData;
+
+        Self {
+            dock_structure: DockStructure {
+                left: DockData {
+                    visible: false,
+                    active_panel: None,
+                    zoom: false,
+                },
+                right: DockData {
+                    visible: false,
+                    active_panel: None,
+                    zoom: false,
+                },
+                bottom: DockData {
+                    visible: false,
+                    active_panel: None,
+                    zoom: false,
+                },
+            },
+            open_file_paths: Vec::new(),
+            active_file_path: None,
+        }
+    }
+}
+
 impl BaseView {
     pub fn which_font_size_used(&self) -> WhichFontSize {
         WhichFontSize::AgentFont
@@ -809,13 +745,11 @@ pub struct AgentPanel {
     retained_threads: HashMap<ThreadId, Entity<ConversationView>>,
     new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
     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,
     zoomed: bool,
     pending_serialization: Option<Task<Result<()>>>,
     new_user_onboarding: Entity<AgentPanelOnboarding>,
@@ -823,7 +757,6 @@ pub struct AgentPanel {
     agent_layout_onboarding: Entity<ai_onboarding::AgentLayoutOnboarding>,
     agent_layout_onboarding_dismissed: AtomicBool,
     selected_agent: Agent,
-    start_thread_in: StartThreadIn,
     pending_thread_loads: usize,
     worktree_creation_status: Option<(EntityId, WorktreeCreationStatus)>,
     _thread_view_subscription: Option<Subscription>,
@@ -840,7 +773,6 @@ impl AgentPanel {
         };
 
         let selected_agent = self.selected_agent.clone();
-        let start_thread_in = Some(self.start_thread_in.clone());
 
         let last_active_thread = self.active_agent_thread(cx).map(|thread| {
             let thread = thread.read(cx);
@@ -861,7 +793,6 @@ impl AgentPanel {
                 SerializedAgentPanel {
                     selected_agent: Some(selected_agent),
                     last_active_thread,
-                    start_thread_in,
                 },
                 kvp,
             )
@@ -929,8 +860,7 @@ impl AgentPanel {
             };
 
             let panel = workspace.update_in(cx, |workspace, window, cx| {
-                let panel =
-                    cx.new(|cx| Self::new(workspace, prompt_store, window, cx));
+                let panel = cx.new(|cx| Self::new(workspace, prompt_store, window, cx));
 
                 panel.update(cx, |panel, cx| {
                     let is_via_collab = panel.project.read(cx).is_via_collab();
@@ -939,8 +869,8 @@ impl AgentPanel {
                     // Collab workspaces only support NativeAgent, so inheriting a
                     // custom agent would cause set_active β†’ new_agent_thread_inner
                     // to bypass the collab guard in external_thread.
-                    let global_fallback = global_last_used_agent
-                        .filter(|agent| !is_via_collab || agent.is_native());
+                    let global_fallback =
+                        global_last_used_agent.filter(|agent| !is_via_collab || agent.is_native());
 
                     if let Some(serialized_panel) = &serialized_panel {
                         if let Some(selected_agent) = serialized_panel.selected_agent.clone() {
@@ -948,26 +878,6 @@ impl AgentPanel {
                         } else if let Some(agent) = global_fallback {
                             panel.selected_agent = agent;
                         }
-                        if let Some(ref start_thread_in) = serialized_panel.start_thread_in {
-                            let is_valid = match &start_thread_in {
-                                StartThreadIn::LocalProject => true,
-                                StartThreadIn::NewWorktree { .. } => {
-                                    let project = panel.project.read(cx);
-                                    agent_v2_enabled(cx) && !project.is_via_collab()
-                                }
-                                StartThreadIn::LinkedWorktree { path, .. } => {
-                                    agent_v2_enabled(cx) && path.exists()
-                                }
-                            };
-                            if is_valid {
-                                panel.start_thread_in = start_thread_in.clone();
-                            } else {
-                                log::info!(
-                                    "deserialized start_thread_in {:?} is no longer valid, falling back to LocalProject",
-                                    start_thread_in,
-                                );
-                            }
-                        }
                     } else if let Some(agent) = global_fallback {
                         panel.selected_agent = agent;
                     }
@@ -981,7 +891,10 @@ impl AgentPanel {
                         panel.load_agent_thread(
                             agent,
                             thread_info.session_id.clone().into(),
-                            thread_info.work_dirs.as_ref().map(|dirs| PathList::deserialize(dirs)),
+                            thread_info
+                                .work_dirs
+                                .as_ref()
+                                .map(|dirs| PathList::deserialize(dirs)),
                             thread_info.title.as_ref().map(|t| t.clone().into()),
                             false,
                             window,
@@ -1152,27 +1065,6 @@ impl AgentPanel {
                 }
                 _ => {}
             });
-        let git_store = project.read(cx).git_store().clone();
-        let _git_store_subscription = cx.subscribe(&git_store, |this, _, event, cx| {
-            let should_sync = matches!(
-                event,
-                GitStoreEvent::ActiveRepositoryChanged(_)
-                    | GitStoreEvent::RepositoryAdded
-                    | GitStoreEvent::RepositoryRemoved(_)
-                    | GitStoreEvent::RepositoryUpdated(
-                        _,
-                        RepositoryEvent::HeadChanged
-                            | RepositoryEvent::BranchListChanged
-                            | RepositoryEvent::GitWorktreeListChanged,
-                        _,
-                    )
-            );
-
-            if should_sync {
-                this.sync_start_thread_in_with_git_state(cx);
-            }
-        });
-
         let mut panel = Self {
             workspace_id,
             base_view,
@@ -1191,20 +1083,17 @@ impl AgentPanel {
             retained_threads: HashMap::default(),
             new_thread_menu_handle: PopoverMenuHandle::default(),
             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,
             zoomed: false,
             pending_serialization: None,
             new_user_onboarding: onboarding,
             agent_layout_onboarding,
             thread_store,
             selected_agent: Agent::default(),
-            start_thread_in: StartThreadIn::default(),
             pending_thread_loads: 0,
             worktree_creation_status: None,
             _thread_view_subscription: None,
@@ -1715,6 +1604,15 @@ impl AgentPanel {
         self.new_thread_menu_handle.toggle(window, cx);
     }
 
+    pub fn toggle_worktree_selector(
+        &mut self,
+        _: &ToggleWorktreeSelector,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.start_thread_in_menu_handle.toggle(window, cx);
+    }
+
     pub fn increase_font_size(
         &mut self,
         action: &IncreaseBufferFontSize,
@@ -2391,10 +2289,7 @@ impl AgentPanel {
             cx.subscribe_in(
                 &tv,
                 window,
-                |this, view, event: &AcpThreadViewEvent, window, cx| match event {
-                    AcpThreadViewEvent::FirstSendRequested { content } => {
-                        this.handle_first_send_requested(view.clone(), content.clone(), window, cx);
-                    }
+                |this, _view, event: &AcpThreadViewEvent, _window, cx| match event {
                     AcpThreadViewEvent::MessageSentOrQueued => {
                         let Some(thread_id) = this.active_thread_id(cx) else {
                             return;
@@ -2407,223 +2302,6 @@ impl AgentPanel {
         })
     }
 
-    pub fn start_thread_in(&self) -> &StartThreadIn {
-        &self.start_thread_in
-    }
-
-    fn set_start_thread_in(
-        &mut self,
-        action: &StartThreadIn,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let new_target = match action {
-            StartThreadIn::LocalProject => StartThreadIn::LocalProject,
-            StartThreadIn::NewWorktree { .. } => {
-                if !agent_v2_enabled(cx) {
-                    return;
-                }
-                if !self.project_has_git_repository(cx) {
-                    log::error!(
-                        "set_start_thread_in: cannot use worktree mode without a git repository"
-                    );
-                    return;
-                }
-                if self.project.read(cx).is_via_collab() {
-                    log::error!(
-                        "set_start_thread_in: cannot use worktree mode in a collab project"
-                    );
-                    return;
-                }
-                action.clone()
-            }
-            StartThreadIn::LinkedWorktree { .. } => {
-                if !agent_v2_enabled(cx) {
-                    return;
-                }
-                if !self.project_has_git_repository(cx) {
-                    log::error!(
-                        "set_start_thread_in: cannot use LinkedWorktree without a git repository"
-                    );
-                    return;
-                }
-                if self.project.read(cx).is_via_collab() {
-                    log::error!(
-                        "set_start_thread_in: cannot use LinkedWorktree in a collab project"
-                    );
-                    return;
-                }
-                action.clone()
-            }
-        };
-        self.start_thread_in = new_target;
-        if let Some(thread) = self.active_thread_view(cx) {
-            thread.update(cx, |thread, cx| thread.focus_handle(cx).focus(window, cx));
-        }
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn cycle_start_thread_in(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        if !agent_v2_enabled(cx) {
-            return;
-        }
-
-        let next = match &self.start_thread_in {
-            StartThreadIn::LocalProject => StartThreadIn::NewWorktree {
-                worktree_name: None,
-                branch_target: NewWorktreeBranchTarget::default(),
-            },
-            StartThreadIn::NewWorktree { .. } | StartThreadIn::LinkedWorktree { .. } => {
-                StartThreadIn::LocalProject
-            }
-        };
-        self.set_start_thread_in(&next, window, cx);
-    }
-
-    fn reset_start_thread_in_to_default(&mut self, cx: &mut Context<Self>) {
-        use settings::{NewThreadLocation, Settings};
-        if !agent_v2_enabled(cx) {
-            if self.start_thread_in != StartThreadIn::LocalProject {
-                self.start_thread_in = StartThreadIn::LocalProject;
-                self.serialize(cx);
-                cx.notify();
-            }
-            return;
-        }
-
-        let default = AgentSettings::get_global(cx).new_thread_location;
-        let start_thread_in = match default {
-            NewThreadLocation::LocalProject => StartThreadIn::LocalProject,
-            NewThreadLocation::NewWorktree => {
-                if self.project_has_git_repository(cx) {
-                    StartThreadIn::NewWorktree {
-                        worktree_name: None,
-                        branch_target: NewWorktreeBranchTarget::default(),
-                    }
-                } else {
-                    StartThreadIn::LocalProject
-                }
-            }
-        };
-        if self.start_thread_in != start_thread_in {
-            self.start_thread_in = start_thread_in;
-            self.serialize(cx);
-            cx.notify();
-        }
-    }
-
-    fn sync_start_thread_in_with_git_state(&mut self, cx: &mut Context<Self>) {
-        if !agent_v2_enabled(cx) {
-            if self.start_thread_in != StartThreadIn::LocalProject {
-                self.start_thread_in = StartThreadIn::LocalProject;
-                self.serialize(cx);
-                cx.notify();
-            }
-            return;
-        }
-
-        if matches!(self.start_thread_in, StartThreadIn::LocalProject) {
-            return;
-        }
-
-        let visible_worktree_paths: Vec<_> = self
-            .project
-            .read(cx)
-            .visible_worktrees(cx)
-            .map(|worktree| worktree.read(cx).abs_path().to_path_buf())
-            .collect();
-        let repositories = self.project.read(cx).repositories(cx);
-        let linked_worktrees = if repositories.len() > 1 {
-            Vec::new()
-        } else {
-            repositories
-                .values()
-                .flat_map(|repo| repo.read(cx).linked_worktrees().iter().cloned())
-                .filter(|worktree| !visible_worktree_paths.contains(&worktree.path))
-                .collect::<Vec<_>>()
-        };
-
-        let updated_start_thread_in = match &self.start_thread_in {
-            StartThreadIn::NewWorktree {
-                worktree_name: Some(worktree_name),
-                branch_target,
-            } => {
-                let normalized_worktree_name = worktree_name.replace(' ', "-");
-                linked_worktrees
-                    .iter()
-                    .find(|worktree| {
-                        worktree.display_name() == normalized_worktree_name
-                            && self.linked_worktree_matches_branch_target(
-                                worktree,
-                                branch_target,
-                                cx,
-                            )
-                    })
-                    .map(|worktree| StartThreadIn::LinkedWorktree {
-                        path: worktree.path.clone(),
-                        display_name: worktree.display_name().to_string(),
-                    })
-            }
-            StartThreadIn::LinkedWorktree { path, .. } => linked_worktrees
-                .iter()
-                .find(|worktree| worktree.path == *path)
-                .map(|worktree| StartThreadIn::LinkedWorktree {
-                    path: worktree.path.clone(),
-                    display_name: worktree.display_name().to_string(),
-                })
-                .or(Some(StartThreadIn::LocalProject)),
-            _ => None,
-        };
-
-        if let Some(updated_start_thread_in) = updated_start_thread_in {
-            if self.start_thread_in != updated_start_thread_in {
-                self.start_thread_in = updated_start_thread_in;
-                self.serialize(cx);
-            }
-            cx.notify();
-        }
-    }
-
-    fn linked_worktree_matches_branch_target(
-        &self,
-        worktree: &git::repository::Worktree,
-        branch_target: &NewWorktreeBranchTarget,
-        cx: &App,
-    ) -> bool {
-        let active_repository = self.project.read(cx).active_repository(cx);
-        let current_branch_name = active_repository.as_ref().and_then(|repo| {
-            repo.read(cx)
-                .branch
-                .as_ref()
-                .map(|branch| branch.name().to_string())
-        });
-        let existing_branch_names = active_repository
-            .as_ref()
-            .map(|repo| {
-                repo.read(cx)
-                    .branch_list
-                    .iter()
-                    .map(|branch| branch.name().to_string())
-                    .collect::<HashSet<_>>()
-            })
-            .unwrap_or_default();
-
-        match branch_target {
-            NewWorktreeBranchTarget::CurrentBranch => {
-                current_branch_name.as_deref() == worktree.branch_name()
-            }
-            NewWorktreeBranchTarget::ExistingBranch { name } => {
-                existing_branch_names.contains(name)
-                    && worktree.branch_name() == Some(name.as_str())
-            }
-            NewWorktreeBranchTarget::CreateBranch { name, .. } => {
-                !existing_branch_names.contains(name)
-                    && worktree.branch_name() == Some(name.as_str())
-            }
-        }
-    }
-
     pub(crate) fn selected_agent(&self) -> Option<Agent> {
         Some(self.selected_agent.clone())
     }
@@ -2672,7 +2350,6 @@ impl AgentPanel {
     }
 
     pub fn new_agent_thread(&mut self, agent: Agent, window: &mut Window, cx: &mut Context<Self>) {
-        self.reset_start_thread_in_to_default(cx);
         self.new_agent_thread_inner(agent, true, window, cx);
     }
 
@@ -2912,49 +2589,6 @@ impl AgentPanel {
         self.active_conversation_view().is_some() && !self.active_thread_has_messages(cx)
     }
 
-    fn handle_first_send_requested(
-        &mut self,
-        thread_view: Entity<ThreadView>,
-        content: Vec<acp::ContentBlock>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match &self.start_thread_in {
-            StartThreadIn::NewWorktree {
-                worktree_name,
-                branch_target,
-            } => {
-                self.handle_worktree_requested(
-                    content,
-                    WorktreeCreationArgs::New {
-                        worktree_name: worktree_name.clone(),
-                        branch_target: branch_target.clone(),
-                    },
-                    window,
-                    cx,
-                );
-            }
-            StartThreadIn::LinkedWorktree { path, .. } => {
-                self.handle_worktree_requested(
-                    content,
-                    WorktreeCreationArgs::Linked {
-                        worktree_path: path.clone(),
-                    },
-                    window,
-                    cx,
-                );
-            }
-            StartThreadIn::LocalProject => {
-                cx.defer_in(window, move |_this, window, cx| {
-                    thread_view.update(cx, |thread_view, cx| {
-                        let editor = thread_view.message_editor.clone();
-                        thread_view.send_impl(editor, window, cx);
-                    });
-                });
-            }
-        }
-    }
-
     // TODO: The mapping from workspace root paths to git repositories needs a
     // unified approach across the codebase: this method, `sidebar::is_root_repo`,
     // thread persistence (which PathList is saved to the database), and thread
@@ -3288,42 +2922,163 @@ impl AgentPanel {
         }
     }
 
-    fn set_worktree_creation_error(
+    fn capture_workspace_state(
+        workspace: &Workspace,
+        window: &Window,
+        cx: &App,
+    ) -> PreviousWorkspaceState {
+        let dock_structure = workspace.capture_dock_state(window, cx);
+        let open_file_paths = workspace.open_item_abs_paths(cx);
+        let active_file_path = workspace
+            .active_item(cx)
+            .and_then(|item| item.project_path(cx))
+            .and_then(|pp| workspace.project().read(cx).absolute_path(&pp, cx));
+
+        PreviousWorkspaceState {
+            dock_structure,
+            open_file_paths,
+            active_file_path,
+        }
+    }
+
+    fn create_worktree(
         &mut self,
-        message: SharedString,
+        action: &CreateWorktree,
+        previous_workspace_state: PreviousWorkspaceState,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        if let Some((_, status)) = &mut self.worktree_creation_status {
-            *status = WorktreeCreationStatus::Error(message);
+        if !self.project_has_git_repository(cx) {
+            log::error!("create_worktree: no git repository in the project");
+            return;
         }
-        if matches!(self.base_view, BaseView::Uninitialized) {
-            let selected_agent = self.selected_agent.clone();
-            self.new_agent_thread(selected_agent, window, cx);
+        if self.project.read(cx).is_via_collab() {
+            log::error!("create_worktree: not supported in collab projects");
+            return;
         }
-        cx.notify();
+        if matches!(
+            self.worktree_creation_status,
+            Some((
+                _,
+                WorktreeCreationStatus::Creating(_) | WorktreeCreationStatus::Loading(_)
+            ))
+        ) {
+            return;
+        }
+
+        let content = self.take_active_initial_content(cx);
+        let content_blocks = match content {
+            Some(AgentInitialContent::ContentBlock { blocks, .. }) => blocks,
+            _ => Vec::new(),
+        };
+
+        self.handle_worktree_requested(
+            content_blocks,
+            WorktreeCreationArgs::New {
+                worktree_name: action.worktree_name.clone(),
+                branch_target: action.branch_target.clone(),
+            },
+            previous_workspace_state,
+            window,
+            cx,
+        );
     }
 
-    fn handle_worktree_requested(
+    fn switch_to_worktree(
         &mut self,
-        content: Vec<acp::ContentBlock>,
-        args: WorktreeCreationArgs,
+        action: &SwitchWorktree,
+        previous_workspace_state: PreviousWorkspaceState,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
+        if !self.project_has_git_repository(cx) {
+            log::error!("switch_to_worktree: no git repository in the project");
+            return;
+        }
+        if self.project.read(cx).is_via_collab() {
+            log::error!("switch_to_worktree: not supported in collab projects");
+            return;
+        }
         if matches!(
             self.worktree_creation_status,
-            Some((_, WorktreeCreationStatus::Creating))
+            Some((
+                _,
+                WorktreeCreationStatus::Creating(_) | WorktreeCreationStatus::Loading(_)
+            ))
         ) {
             return;
         }
 
-        let conversation_view_id = self
+        let content = self.take_active_initial_content(cx);
+        let content_blocks = match content {
+            Some(AgentInitialContent::ContentBlock { blocks, .. }) => blocks,
+            _ => Vec::new(),
+        };
+
+        self.handle_worktree_requested(
+            content_blocks,
+            WorktreeCreationArgs::Linked {
+                worktree_path: action.path.clone(),
+                display_name: action.display_name.clone(),
+            },
+            previous_workspace_state,
+            window,
+            cx,
+        );
+    }
+
+    fn set_worktree_creation_error(
+        &mut self,
+        message: SharedString,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some((_, status)) = &mut self.worktree_creation_status {
+            *status = WorktreeCreationStatus::Error(message);
+        }
+        if matches!(self.base_view, BaseView::Uninitialized) {
+            let selected_agent = self.selected_agent.clone();
+            self.new_agent_thread(selected_agent, window, cx);
+        }
+        cx.notify();
+    }
+
+    fn handle_worktree_requested(
+        &mut self,
+        content: Vec<acp::ContentBlock>,
+        args: WorktreeCreationArgs,
+        previous_workspace_state: PreviousWorkspaceState,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if matches!(
+            self.worktree_creation_status,
+            Some((
+                _,
+                WorktreeCreationStatus::Creating(_) | WorktreeCreationStatus::Loading(_)
+            ))
+        ) {
+            return;
+        }
+
+        let conversation_view_id = self
             .active_conversation_view()
             .map(|v| v.entity_id())
             .unwrap_or_else(|| EntityId::from(0u64));
-        self.worktree_creation_status =
-            Some((conversation_view_id, WorktreeCreationStatus::Creating));
+        let display_name: SharedString = match &args {
+            WorktreeCreationArgs::New {
+                worktree_name: Some(name),
+                ..
+            } => name.clone().into(),
+            WorktreeCreationArgs::New { .. } => "worktree".into(),
+            WorktreeCreationArgs::Linked { display_name, .. } => display_name.clone().into(),
+        };
+        let status = if matches!(args, WorktreeCreationArgs::Linked { .. }) {
+            WorktreeCreationStatus::Loading(display_name)
+        } else {
+            WorktreeCreationStatus::Creating(display_name)
+        };
+        self.worktree_creation_status = Some((conversation_view_id, status));
         cx.notify();
 
         let (git_repos, non_git_paths) = self.classify_worktrees(cx);
@@ -3357,16 +3112,6 @@ impl AgentPanel {
                 (None, None)
             };
 
-        let active_file_path = self.workspace.upgrade().and_then(|workspace| {
-            let workspace = workspace.read(cx);
-            let active_item = workspace.active_item(cx)?;
-            let project_path = active_item.project_path(cx)?;
-            workspace
-                .project()
-                .read(cx)
-                .absolute_path(&project_path, cx)
-        });
-
         let remote_connection_options = self.project.read(cx).remote_connection_options(cx);
 
         if remote_connection_options.is_some() {
@@ -3392,6 +3137,11 @@ impl AgentPanel {
 
         let selected_agent = self.selected_agent();
 
+        let git_repo_work_dirs: Vec<PathBuf> = git_repos
+            .iter()
+            .map(|repo| repo.read(cx).work_directory_abs_path.to_path_buf())
+            .collect();
+
         let task = cx.spawn_in(window, async move |this, cx| {
             let (all_paths, path_remapping, has_non_git) = match args {
                 WorktreeCreationArgs::New {
@@ -3500,11 +3250,15 @@ impl AgentPanel {
                     all_paths.extend(non_git_paths.iter().cloned());
                     (all_paths, path_remapping, has_non_git)
                 }
-                WorktreeCreationArgs::Linked { worktree_path } => {
+                WorktreeCreationArgs::Linked { worktree_path, .. } => {
+                    let path_remapping: Vec<(PathBuf, PathBuf)> = git_repo_work_dirs
+                        .iter()
+                        .map(|work_dir| (work_dir.clone(), worktree_path.clone()))
+                        .collect();
                     let mut all_paths = vec![worktree_path];
                     let has_non_git = !non_git_paths.is_empty();
                     all_paths.extend(non_git_paths.iter().cloned());
-                    (all_paths, Vec::new(), has_non_git)
+                    (all_paths, path_remapping, has_non_git)
                 }
             };
 
@@ -3524,7 +3278,7 @@ impl AgentPanel {
                 this,
                 all_paths,
                 window_handle,
-                active_file_path,
+                previous_workspace_state,
                 path_remapping,
                 non_git_paths,
                 has_non_git,
@@ -3557,7 +3311,7 @@ impl AgentPanel {
         this: WeakEntity<Self>,
         all_paths: Vec<PathBuf>,
         window_handle: Option<gpui::WindowHandle<workspace::MultiWorkspace>>,
-        active_file_path: Option<PathBuf>,
+        previous_workspace_state: PreviousWorkspaceState,
         path_remapping: Vec<(PathBuf, PathBuf)>,
         non_git_paths: Vec<PathBuf>,
         has_non_git: bool,
@@ -3575,6 +3329,15 @@ impl AgentPanel {
                 let active_workspace = multi_workspace.workspace().clone();
                 let modal_workspace = active_workspace.clone();
 
+                let dock_structure = previous_workspace_state.dock_structure;
+                let init = Box::new(
+                    move |workspace: &mut Workspace,
+                          window: &mut Window,
+                          cx: &mut Context<Workspace>| {
+                        workspace.set_dock_structure(dock_structure, window, cx);
+                    },
+                );
+
                 let task = multi_workspace.find_or_create_workspace(
                     path_list,
                     remote_connection_options,

crates/agent_ui/src/agent_ui.rs πŸ”—

@@ -27,7 +27,6 @@ mod terminal_codegen;
 mod terminal_inline_assistant;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test_support;
-mod thread_branch_picker;
 mod thread_history;
 mod thread_history_view;
 mod thread_import;
@@ -90,8 +89,8 @@ actions!(
     [
         /// Toggles the menu to create new agent threads.
         ToggleNewThreadMenu,
-        /// Cycles through the options for where new threads start (current project or new worktree).
-        CycleStartThreadIn,
+        /// Toggles the worktree selector popover for choosing which worktree to use.
+        ToggleWorktreeSelector,
         /// Toggles the navigation menu for switching between threads and views.
         ToggleNavigationMenu,
         /// Toggles the options menu for agent settings and preferences.
@@ -340,23 +339,25 @@ pub enum NewWorktreeBranchTarget {
     },
 }
 
-/// Sets where new threads will run.
-#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Action)]
+/// Creates a new git worktree and switches the workspace to it.
+/// Dispatched by the unified worktree picker when the user selects a "Create new worktree" entry.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Action)]
 #[action(namespace = agent)]
-#[serde(rename_all = "snake_case", tag = "kind")]
-pub enum StartThreadIn {
-    #[default]
-    LocalProject,
-    NewWorktree {
-        /// When this is None, Zed will randomly generate a worktree name
-        /// otherwise, the provided name will be used.
-        #[serde(default)]
-        worktree_name: Option<String>,
-        #[serde(default)]
-        branch_target: NewWorktreeBranchTarget,
-    },
-    /// A linked worktree that already exists on disk.
-    LinkedWorktree { path: PathBuf, display_name: String },
+#[serde(deny_unknown_fields)]
+pub struct CreateWorktree {
+    /// When this is None, Zed will randomly generate a worktree name.
+    pub worktree_name: Option<String>,
+    pub branch_target: NewWorktreeBranchTarget,
+}
+
+/// Switches the workspace to an existing linked worktree.
+/// Dispatched by the unified worktree picker when the user selects an existing worktree.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Action)]
+#[action(namespace = agent)]
+#[serde(deny_unknown_fields)]
+pub struct SwitchWorktree {
+    pub path: PathBuf,
+    pub display_name: String,
 }
 
 /// Content to initialize new external agent with.

crates/agent_ui/src/conversation_view/thread_view.rs πŸ”—

@@ -9,7 +9,6 @@ use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCo
 use editor::actions::OpenExcerpts;
 use feature_flags::AcpBetaFeatureFlag;
 
-use crate::StartThreadIn;
 use crate::message_editor::SharedSessionCapabilities;
 
 use gpui::{Corner, List};
@@ -207,7 +206,6 @@ impl RenderOnce for GeneratingSpinnerElement {
 }
 
 pub enum AcpThreadViewEvent {
-    FirstSendRequested { content: Vec<acp::ContentBlock> },
     MessageSentOrQueued,
 }
 
@@ -909,49 +907,6 @@ impl ThreadView {
 
         let message_editor = self.message_editor.clone();
 
-        // Intercept the first send so the agent panel can capture the full
-        // content blocks β€” needed for "Start thread in New Worktree",
-        // which must create a workspace before sending the message there.
-        let intercept_first_send = self.thread.read(cx).entries().is_empty()
-            && !message_editor.read(cx).is_empty(cx)
-            && self
-                .workspace
-                .upgrade()
-                .and_then(|workspace| workspace.read(cx).panel::<AgentPanel>(cx))
-                .is_some_and(|panel| {
-                    !matches!(
-                        panel.read(cx).start_thread_in(),
-                        StartThreadIn::LocalProject
-                    )
-                });
-
-        if intercept_first_send {
-            cx.emit(AcpThreadViewEvent::MessageSentOrQueued);
-            let content_task = self.resolve_message_contents(&message_editor, cx);
-
-            cx.spawn(async move |this, cx| match content_task.await {
-                Ok((content, _tracked_buffers)) => {
-                    if content.is_empty() {
-                        return;
-                    }
-
-                    this.update(cx, |_, cx| {
-                        cx.emit(AcpThreadViewEvent::FirstSendRequested { content });
-                    })
-                    .ok();
-                }
-                Err(error) => {
-                    this.update(cx, |this, cx| {
-                        this.handle_thread_error(error, cx);
-                    })
-                    .ok();
-                }
-            })
-            .detach();
-
-            return;
-        }
-
         let is_editor_empty = message_editor.read(cx).is_empty(cx);
         let is_generating = thread.read(cx).status() != ThreadStatus::Idle;
 

crates/agent_ui/src/thread_branch_picker.rs πŸ”—

@@ -1,837 +0,0 @@
-use std::rc::Rc;
-
-use collections::{HashMap, HashSet};
-use std::path::PathBuf;
-use std::sync::Arc;
-
-use fuzzy::StringMatchCandidate;
-use git::repository::{Branch as GitBranch, Worktree as GitWorktree};
-use gpui::{
-    AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
-    IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, Window, rems,
-};
-use picker::{Picker, PickerDelegate, PickerEditorPosition};
-use project::Project;
-use project::git_store::RepositoryEvent;
-use ui::{
-    Divider, DocumentationAside, HighlightedLabel, Icon, IconName, Label, LabelCommon, ListItem,
-    ListItemSpacing, prelude::*,
-};
-use util::ResultExt as _;
-
-use crate::{NewWorktreeBranchTarget, StartThreadIn};
-
-pub(crate) struct ThreadBranchPicker {
-    picker: Entity<Picker<ThreadBranchPickerDelegate>>,
-    focus_handle: FocusHandle,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl ThreadBranchPicker {
-    pub fn new(
-        project: Entity<Project>,
-        current_target: &StartThreadIn,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let project_worktree_paths: HashSet<PathBuf> = project
-            .read(cx)
-            .visible_worktrees(cx)
-            .map(|worktree| worktree.read(cx).abs_path().to_path_buf())
-            .collect();
-
-        let has_multiple_repositories = project.read(cx).repositories(cx).len() > 1;
-        let current_branch_name = project
-            .read(cx)
-            .active_repository(cx)
-            .and_then(|repo| {
-                repo.read(cx)
-                    .branch
-                    .as_ref()
-                    .map(|branch| branch.name().to_string())
-            })
-            .unwrap_or_else(|| "HEAD".to_string());
-
-        let repository = if has_multiple_repositories {
-            None
-        } else {
-            project.read(cx).active_repository(cx)
-        };
-
-        let (all_branches, occupied_branches) = repository
-            .as_ref()
-            .map(|repo| {
-                let snapshot = repo.read(cx);
-                let branches = process_branches(&snapshot.branch_list);
-                let occupied =
-                    compute_occupied_branches(&snapshot.linked_worktrees, &project_worktree_paths);
-                (branches, occupied)
-            })
-            .unwrap_or_default();
-
-        let default_branch_request = repository
-            .clone()
-            .map(|repo| repo.update(cx, |repo, _| repo.default_branch(false)));
-
-        let (worktree_name, branch_target) = match current_target {
-            StartThreadIn::NewWorktree {
-                worktree_name,
-                branch_target,
-            } => (worktree_name.clone(), branch_target.clone()),
-            _ => (None, NewWorktreeBranchTarget::default()),
-        };
-
-        let delegate = ThreadBranchPickerDelegate {
-            matches: vec![ThreadBranchEntry::CurrentBranch],
-            all_branches,
-            occupied_branches,
-            selected_index: 0,
-            worktree_name,
-            branch_target,
-            project_worktree_paths,
-            current_branch_name,
-            default_branch_name: None,
-            has_multiple_repositories,
-        };
-
-        let picker = cx.new(|cx| {
-            Picker::list(delegate, window, cx)
-                .list_measure_all()
-                .modal(false)
-                .max_height(Some(rems(20.).into()))
-        });
-
-        let focus_handle = picker.focus_handle(cx);
-
-        let mut subscriptions = Vec::new();
-
-        if let Some(repo) = &repository {
-            subscriptions.push(cx.subscribe_in(
-                repo,
-                window,
-                |this, repo, event: &RepositoryEvent, window, cx| match event {
-                    RepositoryEvent::BranchListChanged => {
-                        let all_branches = process_branches(&repo.read(cx).branch_list);
-                        this.picker.update(cx, |picker, cx| {
-                            picker.delegate.all_branches = all_branches;
-                            picker.refresh(window, cx);
-                        });
-                    }
-                    RepositoryEvent::GitWorktreeListChanged => {
-                        let project_worktree_paths =
-                            this.picker.read(cx).delegate.project_worktree_paths.clone();
-                        let occupied = compute_occupied_branches(
-                            &repo.read(cx).linked_worktrees,
-                            &project_worktree_paths,
-                        );
-                        this.picker.update(cx, |picker, cx| {
-                            picker.delegate.occupied_branches = occupied;
-                            picker.refresh(window, cx);
-                        });
-                    }
-                    _ => {}
-                },
-            ));
-        }
-
-        // Fetch default branch asynchronously since it requires a git operation
-        if let Some(default_branch_request) = default_branch_request {
-            let picker_handle = picker.downgrade();
-            cx.spawn_in(window, async move |_this, cx| {
-                let default_branch = default_branch_request
-                    .await
-                    .ok()
-                    .and_then(Result::ok)
-                    .flatten();
-
-                picker_handle.update_in(cx, |picker, window, cx| {
-                    picker.delegate.default_branch_name =
-                        default_branch.map(|branch| branch.to_string());
-                    picker.refresh(window, cx);
-                })?;
-
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-        }
-
-        subscriptions.push(cx.subscribe(&picker, |_, _, _, cx| {
-            cx.emit(DismissEvent);
-        }));
-
-        Self {
-            picker,
-            focus_handle,
-            _subscriptions: subscriptions,
-        }
-    }
-}
-
-impl Focusable for ThreadBranchPicker {
-    fn focus_handle(&self, _cx: &App) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl EventEmitter<DismissEvent> for ThreadBranchPicker {}
-
-impl Render for ThreadBranchPicker {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        v_flex()
-            .w(rems(22.))
-            .elevation_3(cx)
-            .child(self.picker.clone())
-            .on_mouse_down_out(cx.listener(|_, _, _, cx| {
-                cx.emit(DismissEvent);
-            }))
-    }
-}
-
-#[derive(Clone)]
-enum ThreadBranchEntry {
-    CurrentBranch,
-    DefaultBranch,
-    Separator,
-    ExistingBranch {
-        branch: GitBranch,
-        positions: Vec<usize>,
-    },
-    CreateNamed {
-        name: String,
-    },
-}
-
-pub(crate) struct ThreadBranchPickerDelegate {
-    matches: Vec<ThreadBranchEntry>,
-    all_branches: Vec<GitBranch>,
-    occupied_branches: HashMap<String, String>,
-    selected_index: usize,
-    worktree_name: Option<String>,
-    branch_target: NewWorktreeBranchTarget,
-    project_worktree_paths: HashSet<PathBuf>,
-    current_branch_name: String,
-    default_branch_name: Option<String>,
-    has_multiple_repositories: bool,
-}
-
-fn process_branches(branches: &Arc<[GitBranch]>) -> Vec<GitBranch> {
-    let remote_upstreams: HashSet<_> = branches
-        .iter()
-        .filter_map(|branch| {
-            branch
-                .upstream
-                .as_ref()
-                .filter(|upstream| upstream.is_remote())
-                .map(|upstream| upstream.ref_name.clone())
-        })
-        .collect();
-
-    let mut result: Vec<GitBranch> = branches
-        .iter()
-        .filter(|branch| !remote_upstreams.contains(&branch.ref_name))
-        .cloned()
-        .collect();
-
-    result.sort_by_key(|branch| {
-        (
-            branch.is_remote(),
-            !branch.is_head,
-            branch
-                .most_recent_commit
-                .as_ref()
-                .map(|commit| 0 - commit.commit_timestamp),
-        )
-    });
-
-    result
-}
-
-fn compute_occupied_branches(
-    worktrees: &[GitWorktree],
-    project_worktree_paths: &HashSet<PathBuf>,
-) -> HashMap<String, String> {
-    let mut occupied_branches = HashMap::default();
-    for worktree in worktrees {
-        let Some(branch_name) = worktree.branch_name().map(ToOwned::to_owned) else {
-            continue;
-        };
-
-        let reason = if project_worktree_paths.contains(&worktree.path) {
-            format!(
-                "This branch is already checked out in the current project worktree at {}.",
-                worktree.path.display()
-            )
-        } else {
-            format!(
-                "This branch is already checked out in a linked worktree at {}.",
-                worktree.path.display()
-            )
-        };
-
-        occupied_branches.insert(branch_name, reason);
-    }
-    occupied_branches
-}
-
-impl ThreadBranchPickerDelegate {
-    fn new_worktree_action(&self, branch_target: NewWorktreeBranchTarget) -> StartThreadIn {
-        StartThreadIn::NewWorktree {
-            worktree_name: self.worktree_name.clone(),
-            branch_target,
-        }
-    }
-
-    fn selected_entry_name(&self) -> Option<&str> {
-        match &self.branch_target {
-            NewWorktreeBranchTarget::CurrentBranch => None,
-            NewWorktreeBranchTarget::ExistingBranch { name } => Some(name),
-            NewWorktreeBranchTarget::CreateBranch {
-                from_ref: Some(from_ref),
-                ..
-            } => Some(from_ref),
-            NewWorktreeBranchTarget::CreateBranch { name, .. } => Some(name),
-        }
-    }
-
-    fn prefer_create_entry(&self) -> bool {
-        matches!(
-            &self.branch_target,
-            NewWorktreeBranchTarget::CreateBranch { from_ref: None, .. }
-        )
-    }
-
-    fn fixed_matches(&self) -> Vec<ThreadBranchEntry> {
-        let mut matches = vec![ThreadBranchEntry::CurrentBranch];
-        if !self.has_multiple_repositories
-            && self
-                .default_branch_name
-                .as_ref()
-                .is_some_and(|default_branch_name| default_branch_name != &self.current_branch_name)
-        {
-            matches.push(ThreadBranchEntry::DefaultBranch);
-        }
-        matches
-    }
-
-    fn is_branch_occupied(&self, branch_name: &str) -> bool {
-        self.occupied_branches.contains_key(branch_name)
-    }
-
-    fn branch_aside_text(&self, branch_name: &str, is_remote: bool) -> Option<SharedString> {
-        if self.is_branch_occupied(branch_name) {
-            Some(
-                "This branch is already checked out in another worktree. \
-                 The new worktree will start in detached HEAD state."
-                    .into(),
-            )
-        } else if is_remote {
-            Some("A new local branch will be created from this remote branch.".into())
-        } else {
-            None
-        }
-    }
-
-    fn entry_branch_name(&self, entry: &ThreadBranchEntry) -> Option<SharedString> {
-        match entry {
-            ThreadBranchEntry::CurrentBranch => {
-                Some(SharedString::from(self.current_branch_name.clone()))
-            }
-            ThreadBranchEntry::DefaultBranch => {
-                self.default_branch_name.clone().map(SharedString::from)
-            }
-            ThreadBranchEntry::ExistingBranch { branch, .. } => {
-                Some(SharedString::from(branch.name().to_string()))
-            }
-            _ => None,
-        }
-    }
-
-    fn entry_aside_text(&self, entry: &ThreadBranchEntry) -> Option<SharedString> {
-        match entry {
-            ThreadBranchEntry::CurrentBranch => Some(SharedString::from(
-                "A new branch will be created from the current branch.",
-            )),
-            ThreadBranchEntry::DefaultBranch => {
-                let default_branch_name = self
-                    .default_branch_name
-                    .as_ref()
-                    .filter(|name| *name != &self.current_branch_name)?;
-                self.branch_aside_text(default_branch_name, false)
-            }
-            ThreadBranchEntry::ExistingBranch { branch, .. } => {
-                self.branch_aside_text(branch.name(), branch.is_remote())
-            }
-            _ => None,
-        }
-    }
-
-    fn sync_selected_index(&mut self) {
-        let selected_entry_name = self.selected_entry_name().map(ToOwned::to_owned);
-        let prefer_create = self.prefer_create_entry();
-
-        if prefer_create {
-            if let Some(ref selected_entry_name) = selected_entry_name {
-                if let Some(index) = self.matches.iter().position(|entry| {
-                    matches!(
-                        entry,
-                        ThreadBranchEntry::CreateNamed { name } if name == selected_entry_name
-                    )
-                }) {
-                    self.selected_index = index;
-                    return;
-                }
-            }
-        } else if let Some(ref selected_entry_name) = selected_entry_name {
-            if selected_entry_name == &self.current_branch_name {
-                if let Some(index) = self
-                    .matches
-                    .iter()
-                    .position(|entry| matches!(entry, ThreadBranchEntry::CurrentBranch))
-                {
-                    self.selected_index = index;
-                    return;
-                }
-            }
-
-            if self
-                .default_branch_name
-                .as_ref()
-                .is_some_and(|default_branch_name| default_branch_name == selected_entry_name)
-            {
-                if let Some(index) = self
-                    .matches
-                    .iter()
-                    .position(|entry| matches!(entry, ThreadBranchEntry::DefaultBranch))
-                {
-                    self.selected_index = index;
-                    return;
-                }
-            }
-
-            if let Some(index) = self.matches.iter().position(|entry| {
-                matches!(
-                    entry,
-                    ThreadBranchEntry::ExistingBranch { branch, .. }
-                        if branch.name() == selected_entry_name.as_str()
-                )
-            }) {
-                self.selected_index = index;
-                return;
-            }
-        }
-
-        if self.matches.len() > 1
-            && self
-                .matches
-                .iter()
-                .skip(1)
-                .all(|entry| matches!(entry, ThreadBranchEntry::CreateNamed { .. }))
-        {
-            self.selected_index = 1;
-            return;
-        }
-
-        self.selected_index = 0;
-    }
-}
-
-impl PickerDelegate for ThreadBranchPickerDelegate {
-    type ListItem = AnyElement;
-
-    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
-        "Search branches…".into()
-    }
-
-    fn editor_position(&self) -> PickerEditorPosition {
-        PickerEditorPosition::Start
-    }
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(
-        &mut self,
-        ix: usize,
-        _window: &mut Window,
-        _cx: &mut Context<Picker<Self>>,
-    ) {
-        self.selected_index = ix;
-    }
-
-    fn can_select(&self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>) -> bool {
-        !matches!(self.matches.get(ix), Some(ThreadBranchEntry::Separator))
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        window: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Task<()> {
-        if self.has_multiple_repositories {
-            let mut matches = self.fixed_matches();
-
-            if query.is_empty() {
-                if let Some(name) = self.selected_entry_name().map(ToOwned::to_owned) {
-                    if self.prefer_create_entry() {
-                        matches.push(ThreadBranchEntry::Separator);
-                        matches.push(ThreadBranchEntry::CreateNamed { name });
-                    }
-                }
-            } else {
-                matches.push(ThreadBranchEntry::Separator);
-                matches.push(ThreadBranchEntry::CreateNamed {
-                    name: query.replace(' ', "-"),
-                });
-            }
-
-            self.matches = matches;
-            self.sync_selected_index();
-            return Task::ready(());
-        }
-
-        let all_branches = self.all_branches.clone();
-
-        if query.is_empty() {
-            let mut matches = self.fixed_matches();
-            let filtered_branches: Vec<_> = all_branches
-                .into_iter()
-                .filter(|branch| {
-                    branch.name() != self.current_branch_name
-                        && self
-                            .default_branch_name
-                            .as_ref()
-                            .is_none_or(|default_branch_name| branch.name() != default_branch_name)
-                })
-                .collect();
-
-            if !filtered_branches.is_empty() {
-                matches.push(ThreadBranchEntry::Separator);
-            }
-            for branch in filtered_branches {
-                matches.push(ThreadBranchEntry::ExistingBranch {
-                    branch,
-                    positions: Vec::new(),
-                });
-            }
-
-            if let Some(selected_entry_name) = self.selected_entry_name().map(ToOwned::to_owned) {
-                let has_existing = matches.iter().any(|entry| {
-                    matches!(
-                        entry,
-                        ThreadBranchEntry::ExistingBranch { branch, .. }
-                            if branch.name() == selected_entry_name
-                    )
-                });
-                if self.prefer_create_entry() && !has_existing {
-                    matches.push(ThreadBranchEntry::CreateNamed {
-                        name: selected_entry_name,
-                    });
-                }
-            }
-
-            self.matches = matches;
-            self.sync_selected_index();
-            return Task::ready(());
-        }
-
-        let candidates: Vec<_> = all_branches
-            .iter()
-            .enumerate()
-            .map(|(ix, branch)| StringMatchCandidate::new(ix, branch.name()))
-            .collect();
-        let executor = cx.background_executor().clone();
-        let query_clone = query.clone();
-        let normalized_query = query.replace(' ', "-");
-
-        let task = cx.background_executor().spawn(async move {
-            fuzzy::match_strings(
-                &candidates,
-                &query_clone,
-                true,
-                true,
-                10000,
-                &Default::default(),
-                executor,
-            )
-            .await
-        });
-
-        let all_branches_clone = all_branches;
-        cx.spawn_in(window, async move |picker, cx| {
-            let fuzzy_matches = task.await;
-
-            picker
-                .update_in(cx, |picker, _window, cx| {
-                    let mut matches = picker.delegate.fixed_matches();
-                    let mut has_dynamic_entries = false;
-
-                    for candidate in &fuzzy_matches {
-                        let branch = all_branches_clone[candidate.candidate_id].clone();
-                        if branch.name() == picker.delegate.current_branch_name
-                            || picker.delegate.default_branch_name.as_ref().is_some_and(
-                                |default_branch_name| branch.name() == default_branch_name,
-                            )
-                        {
-                            continue;
-                        }
-                        if !has_dynamic_entries {
-                            matches.push(ThreadBranchEntry::Separator);
-                            has_dynamic_entries = true;
-                        }
-                        matches.push(ThreadBranchEntry::ExistingBranch {
-                            branch,
-                            positions: candidate.positions.clone(),
-                        });
-                    }
-
-                    if fuzzy_matches.is_empty() {
-                        if !has_dynamic_entries {
-                            matches.push(ThreadBranchEntry::Separator);
-                        }
-                        matches.push(ThreadBranchEntry::CreateNamed {
-                            name: normalized_query.clone(),
-                        });
-                    }
-
-                    picker.delegate.matches = matches;
-                    if let Some(index) =
-                        picker.delegate.matches.iter().position(|entry| {
-                            matches!(entry, ThreadBranchEntry::ExistingBranch { .. })
-                        })
-                    {
-                        picker.delegate.selected_index = index;
-                    } else if !fuzzy_matches.is_empty() {
-                        picker.delegate.selected_index = 0;
-                    } else if let Some(index) =
-                        picker.delegate.matches.iter().position(|entry| {
-                            matches!(entry, ThreadBranchEntry::CreateNamed { .. })
-                        })
-                    {
-                        picker.delegate.selected_index = index;
-                    } else {
-                        picker.delegate.sync_selected_index();
-                    }
-                    cx.notify();
-                })
-                .log_err();
-        })
-    }
-
-    fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
-        let Some(entry) = self.matches.get(self.selected_index) else {
-            return;
-        };
-
-        match entry {
-            ThreadBranchEntry::Separator => return,
-            ThreadBranchEntry::CurrentBranch => {
-                window.dispatch_action(
-                    Box::new(self.new_worktree_action(NewWorktreeBranchTarget::CurrentBranch)),
-                    cx,
-                );
-            }
-            ThreadBranchEntry::DefaultBranch => {
-                let Some(default_branch_name) = self.default_branch_name.clone() else {
-                    return;
-                };
-                window.dispatch_action(
-                    Box::new(
-                        self.new_worktree_action(NewWorktreeBranchTarget::ExistingBranch {
-                            name: default_branch_name,
-                        }),
-                    ),
-                    cx,
-                );
-            }
-            ThreadBranchEntry::ExistingBranch { branch, .. } => {
-                let branch_target = if branch.is_remote() {
-                    let branch_name = branch
-                        .ref_name
-                        .as_ref()
-                        .strip_prefix("refs/remotes/")
-                        .and_then(|stripped| stripped.split_once('/').map(|(_, name)| name))
-                        .unwrap_or(branch.name())
-                        .to_string();
-                    NewWorktreeBranchTarget::CreateBranch {
-                        name: branch_name,
-                        from_ref: Some(branch.name().to_string()),
-                    }
-                } else {
-                    NewWorktreeBranchTarget::ExistingBranch {
-                        name: branch.name().to_string(),
-                    }
-                };
-                window.dispatch_action(Box::new(self.new_worktree_action(branch_target)), cx);
-            }
-            ThreadBranchEntry::CreateNamed { name } => {
-                window.dispatch_action(
-                    Box::new(
-                        self.new_worktree_action(NewWorktreeBranchTarget::CreateBranch {
-                            name: name.clone(),
-                            from_ref: None,
-                        }),
-                    ),
-                    cx,
-                );
-            }
-        }
-
-        cx.emit(DismissEvent);
-    }
-
-    fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        _window: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let entry = self.matches.get(ix)?;
-
-        match entry {
-            ThreadBranchEntry::Separator => Some(
-                div()
-                    .py(DynamicSpacing::Base04.rems(cx))
-                    .child(Divider::horizontal())
-                    .into_any_element(),
-            ),
-            ThreadBranchEntry::CurrentBranch => {
-                let branch_name = if self.has_multiple_repositories {
-                    SharedString::from("current branches")
-                } else {
-                    SharedString::from(self.current_branch_name.clone())
-                };
-
-                Some(
-                    ListItem::new("current-branch")
-                        .inset(true)
-                        .spacing(ListItemSpacing::Sparse)
-                        .toggle_state(selected)
-                        .child(Label::new(branch_name))
-                        .into_any_element(),
-                )
-            }
-            ThreadBranchEntry::DefaultBranch => {
-                let default_branch_name = self
-                    .default_branch_name
-                    .as_ref()
-                    .filter(|name| *name != &self.current_branch_name)?;
-
-                let is_occupied = self.is_branch_occupied(default_branch_name);
-
-                let item = ListItem::new("default-branch")
-                    .inset(true)
-                    .spacing(ListItemSpacing::Sparse)
-                    .toggle_state(selected)
-                    .child(Label::new(default_branch_name.clone()));
-
-                Some(
-                    if is_occupied {
-                        item.start_slot(Icon::new(IconName::GitBranchPlus).color(Color::Muted))
-                    } else {
-                        item
-                    }
-                    .into_any_element(),
-                )
-            }
-            ThreadBranchEntry::ExistingBranch {
-                branch, positions, ..
-            } => {
-                let branch_name = branch.name().to_string();
-                let needs_new_branch = self.is_branch_occupied(&branch_name) || branch.is_remote();
-
-                Some(
-                    ListItem::new(SharedString::from(format!("branch-{ix}")))
-                        .inset(true)
-                        .spacing(ListItemSpacing::Sparse)
-                        .toggle_state(selected)
-                        .child(
-                            h_flex()
-                                .min_w_0()
-                                .gap_1()
-                                .child(
-                                    HighlightedLabel::new(branch_name, positions.clone())
-                                        .truncate(),
-                                )
-                                .when(needs_new_branch, |item| {
-                                    item.child(
-                                        Icon::new(IconName::GitBranchPlus)
-                                            .size(IconSize::Small)
-                                            .color(Color::Muted),
-                                    )
-                                }),
-                        )
-                        .into_any_element(),
-                )
-            }
-            ThreadBranchEntry::CreateNamed { name } => Some(
-                ListItem::new("create-named-branch")
-                    .inset(true)
-                    .spacing(ListItemSpacing::Sparse)
-                    .toggle_state(selected)
-                    .child(Label::new(format!("Create Branch: \"{name}\"…")))
-                    .into_any_element(),
-            ),
-        }
-    }
-
-    fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
-        None
-    }
-
-    fn documentation_aside(
-        &self,
-        _window: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Option<DocumentationAside> {
-        let entry = self.matches.get(self.selected_index)?;
-        let branch_name = self.entry_branch_name(entry);
-        let aside_text = self.entry_aside_text(entry);
-
-        if branch_name.is_none() && aside_text.is_none() {
-            return None;
-        }
-
-        let side = crate::ui::documentation_aside_side(cx);
-
-        Some(DocumentationAside::new(
-            side,
-            Rc::new(move |cx| {
-                v_flex()
-                    .gap_1()
-                    .when_some(branch_name.clone(), |this, name| {
-                        this.child(Label::new(name))
-                    })
-                    .when_some(aside_text.clone(), |this, text| {
-                        this.child(
-                            div()
-                                .when(branch_name.is_some(), |this| {
-                                    this.pt_1()
-                                        .border_t_1()
-                                        .border_color(cx.theme().colors().border_variant)
-                                })
-                                .child(Label::new(text).color(Color::Muted)),
-                        )
-                    })
-                    .into_any_element()
-            }),
-        ))
-    }
-
-    fn documentation_aside_index(&self) -> Option<usize> {
-        let entry = self.matches.get(self.selected_index)?;
-        if self.entry_branch_name(entry).is_some() || self.entry_aside_text(entry).is_some() {
-            Some(self.selected_index)
-        } else {
-            None
-        }
-    }
-}

crates/agent_ui/src/thread_worktree_picker.rs πŸ”—

@@ -1,109 +1,71 @@
 use std::path::PathBuf;
-use std::rc::Rc;
 use std::sync::Arc;
 
-use agent_settings::AgentSettings;
-use fs::Fs;
+use collections::HashSet;
 use fuzzy::StringMatchCandidate;
 use git::repository::Worktree as GitWorktree;
 use gpui::{
     AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
-    IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, rems,
+    IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, Window, rems,
 };
 use picker::{Picker, PickerDelegate, PickerEditorPosition};
-use project::{Project, git_store::RepositoryId};
-use settings::{NewThreadLocation, Settings, update_settings_file};
-use ui::{
-    Divider, DocumentationAside, HighlightedLabel, Label, LabelCommon, ListItem, ListItemSpacing,
-    Tooltip, prelude::*,
-};
+use project::Project;
+use project::git_store::RepositoryEvent;
+use ui::{Divider, HighlightedLabel, ListItem, ListItemSpacing, Tooltip, prelude::*};
 use util::ResultExt as _;
 use util::paths::PathExt;
 
-use crate::ui::HoldForDefault;
-use crate::{NewWorktreeBranchTarget, StartThreadIn};
+use crate::{CreateWorktree, NewWorktreeBranchTarget, SwitchWorktree};
 
 pub(crate) struct ThreadWorktreePicker {
     picker: Entity<Picker<ThreadWorktreePickerDelegate>>,
     focus_handle: FocusHandle,
-    _subscription: gpui::Subscription,
+    _subscriptions: Vec<Subscription>,
 }
 
 impl ThreadWorktreePicker {
-    pub fn new(
-        project: Entity<Project>,
-        current_target: &StartThreadIn,
-        fs: Arc<dyn Fs>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let project_worktree_paths: Vec<PathBuf> = project
+    pub fn new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
+        let project_worktree_paths: HashSet<PathBuf> = project
             .read(cx)
             .visible_worktrees(cx)
             .map(|wt| wt.read(cx).abs_path().to_path_buf())
             .collect();
 
-        let preserved_branch_target = match current_target {
-            StartThreadIn::NewWorktree { branch_target, .. } => branch_target.clone(),
-            _ => NewWorktreeBranchTarget::default(),
-        };
-
-        let all_worktrees: Vec<_> = project
-            .read(cx)
-            .repositories(cx)
-            .iter()
-            .map(|(repo_id, repo)| (*repo_id, repo.read(cx).linked_worktrees.clone()))
-            .collect();
+        let has_multiple_repositories = project.read(cx).repositories(cx).len() > 1;
 
-        let has_multiple_repositories = all_worktrees.len() > 1;
+        let current_branch_name = project.read(cx).active_repository(cx).and_then(|repo| {
+            repo.read(cx)
+                .branch
+                .as_ref()
+                .map(|branch| branch.name().to_string())
+        });
 
-        let linked_worktrees: Vec<_> = if has_multiple_repositories {
-            Vec::new()
+        let repository = if has_multiple_repositories {
+            None
         } else {
-            all_worktrees
-                .iter()
-                .flat_map(|(_, worktrees)| worktrees.iter())
-                .filter(|worktree| {
-                    !project_worktree_paths
-                        .iter()
-                        .any(|project_path| project_path == &worktree.path)
-                })
-                .cloned()
-                .collect()
+            project.read(cx).active_repository(cx)
         };
 
-        let mut initial_matches = vec![
-            ThreadWorktreeEntry::CurrentWorktree,
-            ThreadWorktreeEntry::NewWorktree,
-        ];
+        // Fetch worktrees from the git backend (includes main + all linked)
+        let all_worktrees_request = repository
+            .clone()
+            .map(|repo| repo.update(cx, |repo, _| repo.worktrees()));
 
-        if !linked_worktrees.is_empty() {
-            initial_matches.push(ThreadWorktreeEntry::Separator);
-            for worktree in &linked_worktrees {
-                initial_matches.push(ThreadWorktreeEntry::LinkedWorktree {
-                    worktree: worktree.clone(),
-                    positions: Vec::new(),
-                });
-            }
-        }
+        let default_branch_request = repository
+            .clone()
+            .map(|repo| repo.update(cx, |repo, _| repo.default_branch(false)));
 
-        let selected_index = match current_target {
-            StartThreadIn::LocalProject => 0,
-            StartThreadIn::NewWorktree { .. } => 1,
-            StartThreadIn::LinkedWorktree { path, .. } => initial_matches
-                .iter()
-                .position(|entry| matches!(entry, ThreadWorktreeEntry::LinkedWorktree { worktree, .. } if worktree.path == *path))
-                .unwrap_or(0),
-        };
+        let initial_matches = vec![ThreadWorktreeEntry::CreateFromCurrentBranch];
 
         let delegate = ThreadWorktreePickerDelegate {
             matches: initial_matches,
-            all_worktrees,
+            all_worktrees: Vec::new(),
             project_worktree_paths,
-            selected_index,
+            selected_index: 0,
             project,
-            preserved_branch_target,
-            fs,
+            current_branch_name,
+            default_branch_name: None,
+            has_multiple_repositories,
         };
 
         let picker = cx.new(|cx| {
@@ -113,14 +75,82 @@ impl ThreadWorktreePicker {
                 .max_height(Some(rems(20.).into()))
         });
 
-        let subscription = cx.subscribe(&picker, |_, _, _, cx| {
+        let mut subscriptions = Vec::new();
+
+        // Fetch worktrees and default branch asynchronously
+        {
+            let picker_handle = picker.downgrade();
+            cx.spawn_in(window, async move |_this, cx| {
+                let all_worktrees: Vec<_> = match all_worktrees_request {
+                    Some(req) => match req.await {
+                        Ok(Ok(worktrees)) => {
+                            worktrees.into_iter().filter(|wt| !wt.is_bare).collect()
+                        }
+                        Ok(Err(err)) => {
+                            log::warn!("ThreadWorktreePicker: git worktree list failed: {err}");
+                            return anyhow::Ok(());
+                        }
+                        Err(_) => {
+                            log::warn!("ThreadWorktreePicker: worktree request was cancelled");
+                            return anyhow::Ok(());
+                        }
+                    },
+                    None => Vec::new(),
+                };
+
+                let default_branch = match default_branch_request {
+                    Some(req) => req.await.ok().and_then(Result::ok).flatten(),
+                    None => None,
+                };
+
+                picker_handle.update_in(cx, |picker, window, cx| {
+                    picker.delegate.all_worktrees = all_worktrees;
+                    picker.delegate.default_branch_name =
+                        default_branch.map(|branch| branch.to_string());
+                    picker.refresh(window, cx);
+                })?;
+
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
+        }
+
+        // Subscribe to repository events to live-update the worktree list
+        if let Some(repo) = &repository {
+            let picker_entity = picker.downgrade();
+            subscriptions.push(cx.subscribe_in(
+                repo,
+                window,
+                move |_this, repo, event: &RepositoryEvent, window, cx| {
+                    if matches!(event, RepositoryEvent::GitWorktreeListChanged) {
+                        let worktrees_request = repo.update(cx, |repo, _| repo.worktrees());
+                        let picker = picker_entity.clone();
+                        cx.spawn_in(window, async move |_, cx| {
+                            let all_worktrees: Vec<_> = worktrees_request
+                                .await??
+                                .into_iter()
+                                .filter(|wt| !wt.is_bare)
+                                .collect();
+                            picker.update_in(cx, |picker, window, cx| {
+                                picker.delegate.all_worktrees = all_worktrees;
+                                picker.refresh(window, cx);
+                            })?;
+                            anyhow::Ok(())
+                        })
+                        .detach_and_log_err(cx);
+                    }
+                },
+            ));
+        }
+
+        subscriptions.push(cx.subscribe(&picker, |_, _, _, cx| {
             cx.emit(DismissEvent);
-        });
+        }));
 
         Self {
             focus_handle: picker.focus_handle(cx),
             picker,
-            _subscription: subscription,
+            _subscriptions: subscriptions,
         }
     }
 }
@@ -136,7 +166,7 @@ impl EventEmitter<DismissEvent> for ThreadWorktreePicker {}
 impl Render for ThreadWorktreePicker {
     fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         v_flex()
-            .w(rems(20.))
+            .w(rems(34.))
             .elevation_3(cx)
             .child(self.picker.clone())
             .on_mouse_down_out(cx.listener(|_, _, _, cx| {
@@ -147,34 +177,62 @@ impl Render for ThreadWorktreePicker {
 
 #[derive(Clone)]
 enum ThreadWorktreeEntry {
-    CurrentWorktree,
-    NewWorktree,
+    CreateFromCurrentBranch,
+    CreateFromDefaultBranch {
+        default_branch_name: String,
+    },
     Separator,
-    LinkedWorktree {
+    Worktree {
         worktree: GitWorktree,
         positions: Vec<usize>,
     },
     CreateNamed {
         name: String,
+        /// When Some, create from this branch name (e.g. "main"). When None, create from current branch.
+        from_branch: Option<String>,
         disabled_reason: Option<String>,
     },
 }
 
 pub(crate) struct ThreadWorktreePickerDelegate {
     matches: Vec<ThreadWorktreeEntry>,
-    all_worktrees: Vec<(RepositoryId, Arc<[GitWorktree]>)>,
-    project_worktree_paths: Vec<PathBuf>,
+    all_worktrees: Vec<GitWorktree>,
+    project_worktree_paths: HashSet<PathBuf>,
     selected_index: usize,
-    preserved_branch_target: NewWorktreeBranchTarget,
     project: Entity<Project>,
-    fs: Arc<dyn Fs>,
+    current_branch_name: Option<String>,
+    default_branch_name: Option<String>,
+    has_multiple_repositories: bool,
 }
 
 impl ThreadWorktreePickerDelegate {
-    fn new_worktree_action(&self, worktree_name: Option<String>) -> StartThreadIn {
-        StartThreadIn::NewWorktree {
-            worktree_name,
-            branch_target: self.preserved_branch_target.clone(),
+    fn build_fixed_entries(&self) -> Vec<ThreadWorktreeEntry> {
+        let mut entries = Vec::new();
+
+        entries.push(ThreadWorktreeEntry::CreateFromCurrentBranch);
+
+        if !self.has_multiple_repositories {
+            if let Some(ref default_branch) = self.default_branch_name {
+                let is_different = self
+                    .current_branch_name
+                    .as_ref()
+                    .is_none_or(|current| current != default_branch);
+                if is_different {
+                    entries.push(ThreadWorktreeEntry::CreateFromDefaultBranch {
+                        default_branch_name: default_branch.clone(),
+                    });
+                }
+            }
+        }
+
+        entries
+    }
+
+    fn all_repo_worktrees(&self) -> &[GitWorktree] {
+        if self.has_multiple_repositories {
+            &[]
+        } else {
+            &self.all_worktrees
         }
     }
 
@@ -183,10 +241,11 @@ impl ThreadWorktreePickerDelegate {
             return;
         }
 
+        // When filtering, prefer selecting the first worktree match
         if let Some(index) = self
             .matches
             .iter()
-            .position(|entry| matches!(entry, ThreadWorktreeEntry::LinkedWorktree { .. }))
+            .position(|entry| matches!(entry, ThreadWorktreeEntry::Worktree { .. }))
         {
             self.selected_index = index;
         } else if let Some(index) = self
@@ -205,7 +264,7 @@ impl PickerDelegate for ThreadWorktreePickerDelegate {
     type ListItem = AnyElement;
 
     fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
-        "Search or create worktrees…".into()
+        "Select a worktree for this thread…".into()
     }
 
     fn editor_position(&self) -> PickerEditorPosition {
@@ -239,31 +298,18 @@ impl PickerDelegate for ThreadWorktreePickerDelegate {
         window: &mut Window,
         cx: &mut Context<Picker<Self>>,
     ) -> Task<()> {
-        let has_multiple_repositories = self.all_worktrees.len() > 1;
-
-        let linked_worktrees: Vec<_> = if has_multiple_repositories {
-            Vec::new()
-        } else {
-            self.all_worktrees
-                .iter()
-                .flat_map(|(_, worktrees)| worktrees.iter())
-                .filter(|worktree| {
-                    !self
-                        .project_worktree_paths
-                        .iter()
-                        .any(|project_path| project_path == &worktree.path)
-                })
-                .cloned()
-                .collect()
-        };
+        let repo_worktrees = self.all_repo_worktrees().to_vec();
 
         let normalized_query = query.replace(' ', "-");
-        let has_named_worktree = self.all_worktrees.iter().any(|(_, worktrees)| {
-            worktrees
-                .iter()
-                .any(|worktree| worktree.display_name() == normalized_query)
+        let main_worktree_path = self
+            .all_worktrees
+            .iter()
+            .find(|wt| wt.is_main)
+            .map(|wt| wt.path.clone());
+        let has_named_worktree = self.all_worktrees.iter().any(|worktree| {
+            worktree.directory_name(main_worktree_path.as_deref()) == normalized_query
         });
-        let create_named_disabled_reason = if has_multiple_repositories {
+        let create_named_disabled_reason: Option<String> = if self.has_multiple_repositories {
             Some("Cannot create a named worktree in a project with multiple repositories".into())
         } else if has_named_worktree {
             Some("A worktree with this name already exists".into())
@@ -271,147 +317,196 @@ impl PickerDelegate for ThreadWorktreePickerDelegate {
             None
         };
 
-        let mut matches = vec![
-            ThreadWorktreeEntry::CurrentWorktree,
-            ThreadWorktreeEntry::NewWorktree,
-        ];
+        let show_default_branch_create = !self.has_multiple_repositories
+            && self.default_branch_name.as_ref().is_some_and(|default| {
+                self.current_branch_name
+                    .as_ref()
+                    .is_none_or(|current| current != default)
+            });
+        let default_branch_name = self.default_branch_name.clone();
 
         if query.is_empty() {
-            if !linked_worktrees.is_empty() {
-                matches.push(ThreadWorktreeEntry::Separator);
-            }
-            for worktree in &linked_worktrees {
-                matches.push(ThreadWorktreeEntry::LinkedWorktree {
-                    worktree: worktree.clone(),
-                    positions: Vec::new(),
+            let mut matches = self.build_fixed_entries();
+
+            if !repo_worktrees.is_empty() {
+                let main_worktree_path = repo_worktrees
+                    .iter()
+                    .find(|wt| wt.is_main)
+                    .map(|wt| wt.path.clone());
+
+                let mut sorted = repo_worktrees;
+                let project_paths = &self.project_worktree_paths;
+
+                sorted.sort_by(|a, b| {
+                    let a_is_current = project_paths.contains(&a.path);
+                    let b_is_current = project_paths.contains(&b.path);
+                    b_is_current.cmp(&a_is_current).then_with(|| {
+                        a.directory_name(main_worktree_path.as_deref())
+                            .cmp(&b.directory_name(main_worktree_path.as_deref()))
+                    })
                 });
+
+                matches.push(ThreadWorktreeEntry::Separator);
+                for worktree in sorted {
+                    matches.push(ThreadWorktreeEntry::Worktree {
+                        worktree,
+                        positions: Vec::new(),
+                    });
+                }
             }
-        } else if linked_worktrees.is_empty() {
-            matches.push(ThreadWorktreeEntry::Separator);
-            matches.push(ThreadWorktreeEntry::CreateNamed {
-                name: normalized_query,
-                disabled_reason: create_named_disabled_reason,
-            });
-        } else {
-            let candidates: Vec<_> = linked_worktrees
-                .iter()
-                .enumerate()
-                .map(|(ix, worktree)| StringMatchCandidate::new(ix, worktree.display_name()))
-                .collect();
-
-            let executor = cx.background_executor().clone();
-            let query_clone = query.clone();
-
-            let task = cx.background_executor().spawn(async move {
-                fuzzy::match_strings(
-                    &candidates,
-                    &query_clone,
-                    true,
-                    true,
-                    10000,
-                    &Default::default(),
-                    executor,
-                )
-                .await
-            });
 
-            let linked_worktrees_clone = linked_worktrees;
-            return cx.spawn_in(window, async move |picker, cx| {
-                let fuzzy_matches = task.await;
+            self.matches = matches;
+            self.sync_selected_index(false);
+            return Task::ready(());
+        }
+
+        // When the user is typing, fuzzy-match worktree names using display_name
+        // For the main worktree, also match against "main"
+        let main_worktree_path = repo_worktrees
+            .iter()
+            .find(|wt| wt.is_main)
+            .map(|wt| wt.path.clone());
+        let candidates: Vec<_> = repo_worktrees
+            .iter()
+            .enumerate()
+            .map(|(ix, worktree)| {
+                StringMatchCandidate::new(
+                    ix,
+                    &worktree.directory_name(main_worktree_path.as_deref()),
+                )
+            })
+            .collect();
 
-                picker
-                    .update_in(cx, |picker, _window, cx| {
-                        let mut new_matches = vec![
-                            ThreadWorktreeEntry::CurrentWorktree,
-                            ThreadWorktreeEntry::NewWorktree,
-                        ];
+        let executor = cx.background_executor().clone();
 
-                        let has_extra_entries = !fuzzy_matches.is_empty();
+        let task = cx.background_executor().spawn(async move {
+            fuzzy::match_strings(
+                &candidates,
+                &query,
+                true,
+                true,
+                10000,
+                &Default::default(),
+                executor,
+            )
+            .await
+        });
 
-                        if has_extra_entries {
-                            new_matches.push(ThreadWorktreeEntry::Separator);
-                        }
+        let repo_worktrees_clone = repo_worktrees;
+        cx.spawn_in(window, async move |picker, cx| {
+            let fuzzy_matches = task.await;
 
-                        for candidate in &fuzzy_matches {
-                            new_matches.push(ThreadWorktreeEntry::LinkedWorktree {
-                                worktree: linked_worktrees_clone[candidate.candidate_id].clone(),
-                                positions: candidate.positions.clone(),
-                            });
-                        }
+            picker
+                .update_in(cx, |picker, _window, cx| {
+                    let mut new_matches: Vec<ThreadWorktreeEntry> = Vec::new();
 
-                        let has_exact_match = linked_worktrees_clone
-                            .iter()
-                            .any(|worktree| worktree.display_name() == query);
+                    for candidate in &fuzzy_matches {
+                        new_matches.push(ThreadWorktreeEntry::Worktree {
+                            worktree: repo_worktrees_clone[candidate.candidate_id].clone(),
+                            positions: candidate.positions.clone(),
+                        });
+                    }
 
-                        if !has_exact_match {
-                            if !has_extra_entries {
-                                new_matches.push(ThreadWorktreeEntry::Separator);
-                            }
+                    if !new_matches.is_empty() {
+                        new_matches.push(ThreadWorktreeEntry::Separator);
+                    }
+                    new_matches.push(ThreadWorktreeEntry::CreateNamed {
+                        name: normalized_query.clone(),
+                        from_branch: None,
+                        disabled_reason: create_named_disabled_reason.clone(),
+                    });
+                    if show_default_branch_create {
+                        if let Some(ref default_branch) = default_branch_name {
                             new_matches.push(ThreadWorktreeEntry::CreateNamed {
                                 name: normalized_query.clone(),
+                                from_branch: Some(default_branch.clone()),
                                 disabled_reason: create_named_disabled_reason.clone(),
                             });
                         }
+                    }
 
-                        picker.delegate.matches = new_matches;
-                        picker.delegate.sync_selected_index(true);
-
-                        cx.notify();
-                    })
-                    .log_err();
-            });
-        }
-
-        self.matches = matches;
-        self.sync_selected_index(!query.is_empty());
+                    picker.delegate.matches = new_matches;
+                    picker.delegate.sync_selected_index(true);
 
-        Task::ready(())
+                    cx.notify();
+                })
+                .log_err();
+        })
     }
 
-    fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
+    fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
         let Some(entry) = self.matches.get(self.selected_index) else {
             return;
         };
 
         match entry {
             ThreadWorktreeEntry::Separator => return,
-            ThreadWorktreeEntry::CurrentWorktree => {
-                if secondary {
-                    update_settings_file(self.fs.clone(), cx, |settings, _| {
-                        settings
-                            .agent
-                            .get_or_insert_default()
-                            .set_new_thread_location(NewThreadLocation::LocalProject);
-                    });
-                }
-                window.dispatch_action(Box::new(StartThreadIn::LocalProject), cx);
-            }
-            ThreadWorktreeEntry::NewWorktree => {
-                if secondary {
-                    update_settings_file(self.fs.clone(), cx, |settings, _| {
-                        settings
-                            .agent
-                            .get_or_insert_default()
-                            .set_new_thread_location(NewThreadLocation::NewWorktree);
-                    });
-                }
-                window.dispatch_action(Box::new(self.new_worktree_action(None)), cx);
+
+            ThreadWorktreeEntry::CreateFromCurrentBranch => {
+                window.dispatch_action(
+                    Box::new(CreateWorktree {
+                        worktree_name: None,
+                        branch_target: NewWorktreeBranchTarget::CurrentBranch,
+                    }),
+                    cx,
+                );
             }
-            ThreadWorktreeEntry::LinkedWorktree { worktree, .. } => {
+
+            ThreadWorktreeEntry::CreateFromDefaultBranch {
+                default_branch_name,
+            } => {
                 window.dispatch_action(
-                    Box::new(StartThreadIn::LinkedWorktree {
-                        path: worktree.path.clone(),
-                        display_name: worktree.display_name().to_string(),
+                    Box::new(CreateWorktree {
+                        worktree_name: None,
+                        branch_target: NewWorktreeBranchTarget::ExistingBranch {
+                            name: default_branch_name.clone(),
+                        },
                     }),
                     cx,
                 );
             }
+
+            ThreadWorktreeEntry::Worktree { worktree, .. } => {
+                let is_current = self.project_worktree_paths.contains(&worktree.path);
+
+                if is_current {
+                    // Already in this worktree β€” just dismiss
+                } else {
+                    let main_worktree_path = self
+                        .all_worktrees
+                        .iter()
+                        .find(|wt| wt.is_main)
+                        .map(|wt| wt.path.as_path());
+                    window.dispatch_action(
+                        Box::new(SwitchWorktree {
+                            path: worktree.path.clone(),
+                            display_name: worktree.directory_name(main_worktree_path),
+                        }),
+                        cx,
+                    );
+                }
+            }
+
             ThreadWorktreeEntry::CreateNamed {
                 name,
+                from_branch,
                 disabled_reason: None,
             } => {
-                window.dispatch_action(Box::new(self.new_worktree_action(Some(name.clone()))), cx);
+                let branch_target = match from_branch {
+                    Some(branch) => NewWorktreeBranchTarget::ExistingBranch {
+                        name: branch.clone(),
+                    },
+                    None => NewWorktreeBranchTarget::CurrentBranch,
+                };
+                window.dispatch_action(
+                    Box::new(CreateWorktree {
+                        worktree_name: Some(name.clone()),
+                        branch_target,
+                    }),
+                    cx,
+                );
             }
+
             ThreadWorktreeEntry::CreateNamed {
                 disabled_reason: Some(_),
                 ..
@@ -434,8 +529,43 @@ impl PickerDelegate for ThreadWorktreePickerDelegate {
     ) -> Option<Self::ListItem> {
         let entry = self.matches.get(ix)?;
         let project = self.project.read(cx);
-        let is_new_worktree_disabled =
-            project.repositories(cx).is_empty() || project.is_via_collab();
+        let is_create_disabled = project.repositories(cx).is_empty() || project.is_via_collab();
+
+        let no_git_reason: SharedString = "Requires a Git repository in the project".into();
+
+        let create_new_list_item = |id: SharedString,
+                                    label: SharedString,
+                                    disabled_tooltip: Option<SharedString>,
+                                    selected: bool| {
+            let is_disabled = disabled_tooltip.is_some();
+            ListItem::new(id)
+                .inset(true)
+                .spacing(ListItemSpacing::Sparse)
+                .toggle_state(selected)
+                .child(
+                    h_flex()
+                        .w_full()
+                        .gap_2p5()
+                        .child(
+                            Icon::new(IconName::Plus)
+                                .map(|this| {
+                                    if is_disabled {
+                                        this.color(Color::Disabled)
+                                    } else {
+                                        this.color(Color::Muted)
+                                    }
+                                })
+                                .size(IconSize::Small),
+                        )
+                        .child(
+                            Label::new(label).when(is_disabled, |this| this.color(Color::Disabled)),
+                        ),
+                )
+                .when_some(disabled_tooltip, |this, reason| {
+                    this.tooltip(Tooltip::text(reason))
+                })
+                .into_any_element()
+        };
 
         match entry {
             ThreadWorktreeEntry::Separator => Some(
@@ -444,178 +574,464 @@ impl PickerDelegate for ThreadWorktreePickerDelegate {
                     .child(Divider::horizontal())
                     .into_any_element(),
             ),
-            ThreadWorktreeEntry::CurrentWorktree => {
-                let path_label = project.active_repository(cx).map(|repo| {
-                    let path = repo.read(cx).work_directory_abs_path.clone();
-                    path.compact().to_string_lossy().to_string()
-                });
 
-                Some(
-                    ListItem::new("current-worktree")
-                        .inset(true)
-                        .spacing(ListItemSpacing::Sparse)
-                        .toggle_state(selected)
-                        .child(
-                            v_flex()
-                                .min_w_0()
-                                .overflow_hidden()
-                                .child(Label::new("Current Worktree"))
-                                .when_some(path_label, |this, path| {
-                                    this.child(
-                                        Label::new(path)
-                                            .size(LabelSize::Small)
-                                            .color(Color::Muted)
-                                            .truncate_start(),
-                                    )
-                                }),
-                        )
-                        .into_any_element(),
-                )
+            ThreadWorktreeEntry::CreateFromCurrentBranch => {
+                let branch_label = if self.has_multiple_repositories {
+                    "current branches".to_string()
+                } else {
+                    self.current_branch_name
+                        .clone()
+                        .unwrap_or_else(|| "HEAD".to_string())
+                };
+
+                let label = format!("Create new worktree based on {branch_label}");
+
+                let disabled_tooltip = is_create_disabled.then(|| no_git_reason.clone());
+
+                let item = create_new_list_item(
+                    "create-from-current".to_string().into(),
+                    label.into(),
+                    disabled_tooltip,
+                    selected,
+                );
+
+                Some(item.into_any_element())
             }
-            ThreadWorktreeEntry::NewWorktree => {
-                let item = ListItem::new("new-worktree")
-                    .inset(true)
-                    .spacing(ListItemSpacing::Sparse)
-                    .toggle_state(selected)
-                    .disabled(is_new_worktree_disabled)
-                    .child(
-                        v_flex()
-                            .min_w_0()
-                            .overflow_hidden()
-                            .child(
-                                Label::new("New Git Worktree")
-                                    .when(is_new_worktree_disabled, |this| {
-                                        this.color(Color::Disabled)
-                                    }),
-                            )
-                            .child(
-                                Label::new("Get a fresh new worktree")
-                                    .size(LabelSize::Small)
-                                    .color(Color::Muted),
-                            ),
-                    );
 
-                Some(
-                    if is_new_worktree_disabled {
-                        item.tooltip(Tooltip::text("Requires a Git repository in the project"))
-                    } else {
-                        item
-                    }
-                    .into_any_element(),
-                )
+            ThreadWorktreeEntry::CreateFromDefaultBranch {
+                default_branch_name,
+            } => {
+                let label = format!("Create new worktree based on {default_branch_name}");
+
+                let disabled_tooltip = is_create_disabled.then(|| no_git_reason.clone());
+
+                let item = create_new_list_item(
+                    "create-from-main".to_string().into(),
+                    label.into(),
+                    disabled_tooltip,
+                    selected,
+                );
+
+                Some(item.into_any_element())
             }
-            ThreadWorktreeEntry::LinkedWorktree {
+
+            ThreadWorktreeEntry::Worktree {
                 worktree,
                 positions,
             } => {
-                let display_name = worktree.display_name();
-                let first_line = display_name.lines().next().unwrap_or(display_name);
+                let main_worktree_path = self
+                    .all_worktrees
+                    .iter()
+                    .find(|wt| wt.is_main)
+                    .map(|wt| wt.path.as_path());
+                let display_name = worktree.directory_name(main_worktree_path);
+                let first_line = display_name.lines().next().unwrap_or(&display_name);
                 let positions: Vec<_> = positions
                     .iter()
                     .copied()
                     .filter(|&pos| pos < first_line.len())
                     .collect();
-                let path = worktree.path.compact();
+                let path = worktree.path.compact().to_string_lossy().to_string();
+                let sha = worktree.sha.chars().take(7).collect::<String>();
+
+                let is_current = self.project_worktree_paths.contains(&worktree.path);
+
+                let entry_icon = if is_current {
+                    IconName::Check
+                } else {
+                    IconName::GitWorktree
+                };
 
                 Some(
-                    ListItem::new(SharedString::from(format!("linked-worktree-{ix}")))
+                    ListItem::new(SharedString::from(format!("worktree-{ix}")))
                         .inset(true)
                         .spacing(ListItemSpacing::Sparse)
                         .toggle_state(selected)
                         .child(
-                            v_flex()
-                                .min_w_0()
-                                .overflow_hidden()
+                            h_flex()
+                                .w_full()
+                                .gap_2p5()
                                 .child(
-                                    HighlightedLabel::new(first_line.to_owned(), positions)
-                                        .truncate(),
+                                    Icon::new(entry_icon)
+                                        .color(if is_current {
+                                            Color::Accent
+                                        } else {
+                                            Color::Muted
+                                        })
+                                        .size(IconSize::Small),
                                 )
                                 .child(
-                                    Label::new(path.to_string_lossy().to_string())
-                                        .size(LabelSize::Small)
-                                        .color(Color::Muted)
-                                        .truncate_start(),
+                                    v_flex()
+                                        .w_full()
+                                        .min_w_0()
+                                        .child(
+                                            HighlightedLabel::new(first_line.to_owned(), positions)
+                                                .truncate(),
+                                        )
+                                        .child(
+                                            h_flex()
+                                                .w_full()
+                                                .min_w_0()
+                                                .gap_1p5()
+                                                .when_some(
+                                                    worktree.branch_name().map(|b| b.to_string()),
+                                                    |this, branch| {
+                                                        this.child(
+                                                            Label::new(branch)
+                                                                .size(LabelSize::Small)
+                                                                .color(Color::Muted),
+                                                        )
+                                                        .child(
+                                                            Label::new("\u{2022}")
+                                                                .alpha(0.5)
+                                                                .color(Color::Muted)
+                                                                .size(LabelSize::Small),
+                                                        )
+                                                    },
+                                                )
+                                                .when(!sha.is_empty(), |this| {
+                                                    this.child(
+                                                        Label::new(sha)
+                                                            .size(LabelSize::Small)
+                                                            .color(Color::Muted),
+                                                    )
+                                                    .child(
+                                                        Label::new("\u{2022}")
+                                                            .alpha(0.5)
+                                                            .color(Color::Muted)
+                                                            .size(LabelSize::Small),
+                                                    )
+                                                })
+                                                .child(
+                                                    Label::new(path)
+                                                        .truncate_start()
+                                                        .color(Color::Muted)
+                                                        .size(LabelSize::Small)
+                                                        .flex_1(),
+                                                ),
+                                        ),
                                 ),
                         )
                         .into_any_element(),
                 )
             }
+
             ThreadWorktreeEntry::CreateNamed {
                 name,
+                from_branch,
                 disabled_reason,
             } => {
-                let is_disabled = disabled_reason.is_some();
-                let item = ListItem::new("create-named-worktree")
-                    .inset(true)
-                    .spacing(ListItemSpacing::Sparse)
-                    .toggle_state(selected)
-                    .disabled(is_disabled)
-                    .child(Label::new(format!("Create Worktree: \"{name}\"…")).color(
-                        if is_disabled {
-                            Color::Disabled
-                        } else {
-                            Color::Default
-                        },
-                    ));
+                let branch_label = from_branch
+                    .as_deref()
+                    .unwrap_or(self.current_branch_name.as_deref().unwrap_or("HEAD"));
+                let label = format!("Create \"{name}\" based on {branch_label}");
+                let element_id = match from_branch {
+                    Some(branch) => format!("create-named-from-{branch}"),
+                    None => "create-named-from-current".to_string(),
+                };
 
-                Some(
-                    if let Some(reason) = disabled_reason.clone() {
-                        item.tooltip(Tooltip::text(reason))
-                    } else {
-                        item
-                    }
-                    .into_any_element(),
-                )
+                let item = create_new_list_item(
+                    element_id.into(),
+                    label.into(),
+                    disabled_reason.clone().map(SharedString::from),
+                    selected,
+                );
+
+                Some(item.into_any_element())
             }
         }
     }
+}
 
-    fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
-        None
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use fs::FakeFs;
+    use gpui::TestAppContext;
+    use project::Project;
+    use settings::SettingsStore;
+
+    fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
+            editor::init(cx);
+            release_channel::init("0.0.0".parse().unwrap(), cx);
+            crate::agent_panel::init(cx);
+        });
     }
 
-    fn documentation_aside(
-        &self,
-        _window: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Option<DocumentationAside> {
-        let entry = self.matches.get(self.selected_index)?;
-        let is_default = match entry {
-            ThreadWorktreeEntry::CurrentWorktree => {
-                let new_thread_location = AgentSettings::get_global(cx).new_thread_location;
-                Some(new_thread_location == NewThreadLocation::LocalProject)
-            }
-            ThreadWorktreeEntry::NewWorktree => {
-                let project = self.project.read(cx);
-                let is_disabled = project.repositories(cx).is_empty() || project.is_via_collab();
-                if is_disabled {
-                    None
-                } else {
-                    let new_thread_location = AgentSettings::get_global(cx).new_thread_location;
-                    Some(new_thread_location == NewThreadLocation::NewWorktree)
-                }
-            }
-            _ => None,
-        }?;
-
-        let side = crate::ui::documentation_aside_side(cx);
-
-        Some(DocumentationAside::new(
-            side,
-            Rc::new(move |_| {
-                HoldForDefault::new(is_default)
-                    .more_content(false)
-                    .into_any_element()
-            }),
-        ))
+    fn make_worktree(path: &str, branch: &str, is_main: bool) -> GitWorktree {
+        GitWorktree {
+            path: PathBuf::from(path),
+            ref_name: Some(format!("refs/heads/{branch}").into()),
+            sha: "abc1234".into(),
+            is_main,
+            is_bare: false,
+        }
     }
 
-    fn documentation_aside_index(&self) -> Option<usize> {
-        match self.matches.get(self.selected_index) {
-            Some(ThreadWorktreeEntry::CurrentWorktree | ThreadWorktreeEntry::NewWorktree) => {
-                Some(self.selected_index)
-            }
-            _ => None,
+    fn build_delegate(
+        project: Entity<Project>,
+        all_worktrees: Vec<GitWorktree>,
+        project_worktree_paths: HashSet<PathBuf>,
+        current_branch_name: Option<String>,
+        default_branch_name: Option<String>,
+        has_multiple_repositories: bool,
+    ) -> ThreadWorktreePickerDelegate {
+        ThreadWorktreePickerDelegate {
+            matches: vec![ThreadWorktreeEntry::CreateFromCurrentBranch],
+            all_worktrees,
+            project_worktree_paths,
+            selected_index: 0,
+            project,
+            current_branch_name,
+            default_branch_name,
+            has_multiple_repositories,
         }
     }
+
+    fn entry_names(delegate: &ThreadWorktreePickerDelegate) -> Vec<String> {
+        delegate
+            .matches
+            .iter()
+            .map(|entry| match entry {
+                ThreadWorktreeEntry::CreateFromCurrentBranch => {
+                    "CreateFromCurrentBranch".to_string()
+                }
+                ThreadWorktreeEntry::CreateFromDefaultBranch {
+                    default_branch_name,
+                } => format!("CreateFromDefaultBranch({default_branch_name})"),
+                ThreadWorktreeEntry::Separator => "---".to_string(),
+                ThreadWorktreeEntry::Worktree { worktree, .. } => {
+                    format!("Worktree({})", worktree.path.display())
+                }
+                ThreadWorktreeEntry::CreateNamed {
+                    name,
+                    from_branch,
+                    disabled_reason,
+                } => {
+                    let branch = from_branch
+                        .as_deref()
+                        .map(|b| format!("from {b}"))
+                        .unwrap_or_else(|| "from current".to_string());
+                    if disabled_reason.is_some() {
+                        format!("CreateNamed({name}, {branch}, disabled)")
+                    } else {
+                        format!("CreateNamed({name}, {branch})")
+                    }
+                }
+            })
+            .collect()
+    }
+
+    type PickerWindow = gpui::WindowHandle<Picker<ThreadWorktreePickerDelegate>>;
+
+    async fn make_picker(
+        cx: &mut TestAppContext,
+        all_worktrees: Vec<GitWorktree>,
+        project_worktree_paths: HashSet<PathBuf>,
+        current_branch_name: Option<String>,
+        default_branch_name: Option<String>,
+        has_multiple_repositories: bool,
+    ) -> PickerWindow {
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+
+        cx.add_window(|window, cx| {
+            let delegate = build_delegate(
+                project,
+                all_worktrees,
+                project_worktree_paths,
+                current_branch_name,
+                default_branch_name,
+                has_multiple_repositories,
+            );
+            Picker::list(delegate, window, cx)
+                .list_measure_all()
+                .modal(false)
+        })
+    }
+
+    #[gpui::test]
+    async fn test_empty_query_entries(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        // When on `main` with default branch also `main`, only CreateFromCurrentBranch
+        // is shown as a fixed entry. Worktrees are listed with the current one first.
+        let worktrees = vec![
+            make_worktree("/repo", "main", true),
+            make_worktree("/repo-feature", "feature", false),
+            make_worktree("/repo-bugfix", "bugfix", false),
+        ];
+        let project_paths: HashSet<PathBuf> = [PathBuf::from("/repo")].into_iter().collect();
+
+        let picker = make_picker(
+            cx,
+            worktrees,
+            project_paths,
+            Some("main".into()),
+            Some("main".into()),
+            false,
+        )
+        .await;
+
+        picker
+            .update(cx, |picker, window, cx| picker.refresh(window, cx))
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+
+        assert_eq!(
+            names,
+            vec![
+                "CreateFromCurrentBranch",
+                "---",
+                "Worktree(/repo)",
+                "Worktree(/repo-bugfix)",
+                "Worktree(/repo-feature)",
+            ]
+        );
+
+        // When current branch differs from default, CreateFromDefaultBranch appears.
+        picker
+            .update(cx, |picker, _window, cx| {
+                picker.delegate.current_branch_name = Some("feature".into());
+                picker.delegate.default_branch_name = Some("main".into());
+                cx.notify();
+            })
+            .unwrap();
+        picker
+            .update(cx, |picker, window, cx| picker.refresh(window, cx))
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+
+        assert!(names.contains(&"CreateFromDefaultBranch(main)".to_string()));
+    }
+
+    #[gpui::test]
+    async fn test_query_filtering_and_create_entries(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let picker = make_picker(
+            cx,
+            vec![
+                make_worktree("/repo", "main", true),
+                make_worktree("/repo-feature", "feature", false),
+                make_worktree("/repo-bugfix", "bugfix", false),
+                make_worktree("/my-worktree", "experiment", false),
+            ],
+            HashSet::default(),
+            Some("dev".into()),
+            Some("main".into()),
+            false,
+        )
+        .await;
+
+        // Partial match filters to matching worktrees and offers to create
+        // from both current branch and default branch.
+        picker
+            .update(cx, |picker, window, cx| {
+                picker.set_query("feat", window, cx)
+            })
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+        assert!(names.contains(&"Worktree(/repo-feature)".to_string()));
+        assert!(
+            names.contains(&"CreateNamed(feat, from current)".to_string()),
+            "should offer to create from current branch, got: {names:?}"
+        );
+        assert!(
+            names.contains(&"CreateNamed(feat, from main)".to_string()),
+            "should offer to create from default branch, got: {names:?}"
+        );
+        assert!(!names.contains(&"Worktree(/repo-bugfix)".to_string()));
+
+        // Exact match: both create entries appear but are disabled.
+        picker
+            .update(cx, |picker, window, cx| {
+                picker.set_query("repo-feature", window, cx)
+            })
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+        assert!(
+            names.contains(&"CreateNamed(repo-feature, from current, disabled)".to_string()),
+            "exact name match should show disabled create entries, got: {names:?}"
+        );
+
+        // Spaces are normalized to hyphens: "my worktree" matches "my-worktree".
+        picker
+            .update(cx, |picker, window, cx| {
+                picker.set_query("my worktree", window, cx)
+            })
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+        assert!(
+            names.contains(&"CreateNamed(my-worktree, from current, disabled)".to_string()),
+            "spaces should normalize to hyphens and detect existing worktree, got: {names:?}"
+        );
+    }
+
+    #[gpui::test]
+    async fn test_multi_repo_hides_worktrees_and_disables_create_named(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let picker = make_picker(
+            cx,
+            vec![
+                make_worktree("/repo", "main", true),
+                make_worktree("/repo-feature", "feature", false),
+            ],
+            HashSet::default(),
+            Some("main".into()),
+            Some("main".into()),
+            true,
+        )
+        .await;
+
+        picker
+            .update(cx, |picker, window, cx| picker.refresh(window, cx))
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+        assert_eq!(names, vec!["CreateFromCurrentBranch"]);
+
+        picker
+            .update(cx, |picker, window, cx| {
+                picker.set_query("new-thing", window, cx)
+            })
+            .unwrap();
+        cx.run_until_parked();
+
+        let names = picker
+            .read_with(cx, |picker, _| entry_names(&picker.delegate))
+            .unwrap();
+        assert!(
+            names.contains(&"CreateNamed(new-thing, from current, disabled)".to_string()),
+            "multi-repo should disable create named, got: {names:?}"
+        );
+    }
 }

crates/collab/tests/integration/git_tests.rs πŸ”—

@@ -514,6 +514,7 @@ async fn test_linked_worktrees_sync(
             ref_name: Some("refs/heads/feature-branch".into()),
             sha: "bbb222".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -525,6 +526,7 @@ async fn test_linked_worktrees_sync(
             ref_name: Some("refs/heads/bugfix-branch".into()),
             sha: "ccc333".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -597,6 +599,7 @@ async fn test_linked_worktrees_sync(
                 ref_name: Some("refs/heads/hotfix-branch".into()),
                 sha: "ddd444".into(),
                 is_main: false,
+                is_bare: false,
             },
         )
         .await;

crates/component_preview/src/component_preview.rs πŸ”—

@@ -561,7 +561,7 @@ impl ComponentPreview {
             workspace.update(cx, |workspace, cx| {
                 let status_toast =
                     StatusToast::new("`zed/new-notification-system` created!", cx, |this, _cx| {
-                        this.icon(ToastIcon::new(IconName::GitBranchAlt).color(Color::Muted))
+                        this.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
                             .action("Open Pull Request", |_, cx| {
                                 cx.open_url("https://github.com/")
                             })

crates/fs/src/fake_git_repo.rs πŸ”—

@@ -494,6 +494,7 @@ impl GitRepository for FakeGitRepository {
                     ref_name: Some(branch_ref.into()),
                     sha: head_sha.into(),
                     is_main: true,
+                    is_bare: false,
                 };
                 (main_wt, state.refs.clone())
             })?;
@@ -532,6 +533,7 @@ impl GitRepository for FakeGitRepository {
                         ref_name: ref_name.map(Into::into),
                         sha: sha.into(),
                         is_main: false,
+                        is_bare: false,
                     });
                 }
             }

crates/git/src/repository.rs πŸ”—

@@ -237,6 +237,7 @@ pub struct Worktree {
     // todo(git_worktree) This type should be a Oid
     pub sha: SharedString,
     pub is_main: bool,
+    pub is_bare: bool,
 }
 
 /// Describes how a new worktree should choose or create its checked-out HEAD.
@@ -291,6 +292,34 @@ impl Worktree {
         self.branch_name()
             .unwrap_or(&self.sha[..self.sha.len().min(SHORT_SHA_LENGTH)])
     }
+
+    pub fn directory_name(&self, main_worktree_path: Option<&Path>) -> String {
+        if self.is_main {
+            return "main".to_string();
+        }
+
+        let dir_name = self
+            .path
+            .file_name()
+            .and_then(|name| name.to_str())
+            .unwrap_or(self.display_name());
+
+        if let Some(main_path) = main_worktree_path {
+            let main_dir = main_path.file_name().and_then(|n| n.to_str());
+            if main_dir == Some(dir_name) {
+                if let Some(parent_name) = self
+                    .path
+                    .parent()
+                    .and_then(|p| p.file_name())
+                    .and_then(|n| n.to_str())
+                {
+                    return parent_name.to_string();
+                }
+            }
+        }
+
+        dir_name.to_string()
+    }
 }
 
 pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree> {
@@ -303,6 +332,8 @@ pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree
         let mut sha = None;
         let mut ref_name = None;
 
+        let mut is_bare = false;
+
         for line in entry.lines() {
             let line = line.trim();
             if line.is_empty() {
@@ -314,8 +345,10 @@ pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree
                 sha = Some(rest.to_string());
             } else if let Some(rest) = line.strip_prefix("branch ") {
                 ref_name = Some(rest.to_string());
+            } else if line == "bare" {
+                is_bare = true;
             }
-            // Ignore other lines: detached, bare, locked, prunable, etc.
+            // Ignore other lines: detached, locked, prunable, etc.
         }
 
         if let (Some(path), Some(sha)) = (path, sha) {
@@ -324,6 +357,7 @@ pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree
                 ref_name: ref_name.map(Into::into),
                 sha: sha.into(),
                 is_main: is_first,
+                is_bare,
             });
             is_first = false;
         }
@@ -4118,6 +4152,7 @@ mod tests {
         assert_eq!(result[0].sha.as_ref(), "abc123def");
         assert_eq!(result[0].ref_name, Some("refs/heads/main".into()));
         assert!(result[0].is_main);
+        assert!(!result[0].is_bare);
 
         // Multiple worktrees
         let input = "worktree /home/user/project\nHEAD abc123\nbranch refs/heads/main\n\n\
@@ -4127,9 +4162,11 @@ mod tests {
         assert_eq!(result[0].path, PathBuf::from("/home/user/project"));
         assert_eq!(result[0].ref_name, Some("refs/heads/main".into()));
         assert!(result[0].is_main);
+        assert!(!result[0].is_bare);
         assert_eq!(result[1].path, PathBuf::from("/home/user/project-wt"));
         assert_eq!(result[1].ref_name, Some("refs/heads/feature".into()));
         assert!(!result[1].is_main);
+        assert!(!result[1].is_bare);
 
         // Detached HEAD entry (included with ref_name: None)
         let input = "worktree /home/user/project\nHEAD abc123\nbranch refs/heads/main\n\n\
@@ -4143,6 +4180,7 @@ mod tests {
         assert_eq!(result[1].ref_name, None);
         assert_eq!(result[1].sha.as_ref(), "def456");
         assert!(!result[1].is_main);
+        assert!(!result[1].is_bare);
 
         // Bare repo entry (included with ref_name: None)
         let input = "worktree /home/user/bare.git\nHEAD abc123\nbare\n\n\
@@ -4152,9 +4190,11 @@ mod tests {
         assert_eq!(result[0].path, PathBuf::from("/home/user/bare.git"));
         assert_eq!(result[0].ref_name, None);
         assert!(result[0].is_main);
+        assert!(result[0].is_bare);
         assert_eq!(result[1].path, PathBuf::from("/home/user/project"));
         assert_eq!(result[1].ref_name, Some("refs/heads/main".into()));
         assert!(!result[1].is_main);
+        assert!(!result[1].is_bare);
 
         // Extra porcelain lines (locked, prunable) should be ignored
         let input = "worktree /home/user/project\nHEAD abc123\nbranch refs/heads/main\n\n\

crates/git_ui/src/branch_picker.rs πŸ”—

@@ -944,7 +944,7 @@ impl PickerDelegate for BranchListDelegate {
                 } else if branch.is_remote() {
                     IconName::Screen
                 } else {
-                    IconName::GitBranchAlt
+                    IconName::GitBranch
                 }
             }
         };

crates/git_ui/src/git_panel.rs πŸ”—

@@ -3864,9 +3864,9 @@ impl GitPanel {
             let status_toast = StatusToast::new(message, cx, move |this, _cx| {
                 use remote_output::SuccessStyle::*;
                 match style {
-                    Toast => this.icon(ToastIcon::new(IconName::GitBranchAlt).color(Color::Muted)),
+                    Toast => this.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted)),
                     ToastWithLog { output } => this
-                        .icon(ToastIcon::new(IconName::GitBranchAlt).color(Color::Muted))
+                        .icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
                         .action("View Log", move |window, cx| {
                             let output = output.clone();
                             let output =
@@ -3878,7 +3878,7 @@ impl GitPanel {
                                 .ok();
                         }),
                     PushPrLink { text, link } => this
-                        .icon(ToastIcon::new(IconName::GitBranchAlt).color(Color::Muted))
+                        .icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
                         .action(text, move |_, cx| cx.open_url(&link)),
                 }
                 .dismiss_button(true)
@@ -5807,7 +5807,7 @@ impl Panel for GitPanel {
     }
 
     fn icon(&self, _: &Window, cx: &App) -> Option<ui::IconName> {
-        Some(ui::IconName::GitBranchAlt).filter(|_| GitPanelSettings::get_global(cx).button)
+        Some(ui::IconName::GitBranch).filter(|_| GitPanelSettings::get_global(cx).button)
     }
 
     fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
@@ -6127,15 +6127,13 @@ impl RenderOnce for PanelRepoFooter {
                     .flex_1()
                     .overflow_hidden()
                     .gap_px()
-                    .child(
-                        Icon::new(IconName::GitBranchAlt)
-                            .size(IconSize::Small)
-                            .color(if single_repo {
-                                Color::Disabled
-                            } else {
-                                Color::Muted
-                            }),
-                    )
+                    .child(Icon::new(IconName::GitBranch).size(IconSize::Small).color(
+                        if single_repo {
+                            Color::Disabled
+                        } else {
+                            Color::Muted
+                        },
+                    ))
                     .child(repo_selector)
                     .when(show_separator, |this| {
                         this.child(

crates/git_ui/src/worktree_picker.rs πŸ”—

@@ -11,7 +11,7 @@ use gpui::{
 use picker::{Picker, PickerDelegate, PickerEditorPosition};
 use project::project_settings::ProjectSettings;
 use project::{
-    git_store::Repository,
+    git_store::{Repository, RepositoryEvent},
     trusted_worktrees::{PathTrust, TrustedWorktrees},
 };
 use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier};
@@ -26,8 +26,6 @@ use workspace::{
 
 use crate::git_panel::show_error_toast;
 
-const MAIN_WORKTREE_DISPLAY_NAME: &str = "main";
-
 actions!(
     git,
     [
@@ -64,7 +62,7 @@ pub struct WorktreeList {
     width: Rems,
     pub picker: Entity<Picker<WorktreeListDelegate>>,
     picker_focus_handle: FocusHandle,
-    _subscription: Option<Subscription>,
+    _subscriptions: Vec<Subscription>,
     embedded: bool,
 }
 
@@ -77,9 +75,10 @@ impl WorktreeList {
         cx: &mut Context<Self>,
     ) -> Self {
         let mut this = Self::new_inner(repository, workspace, width, false, window, cx);
-        this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| {
-            cx.emit(DismissEvent);
-        }));
+        this._subscriptions
+            .push(cx.subscribe(&this.picker, |_, _, _, cx| {
+                cx.emit(DismissEvent);
+            }));
         this
     }
 
@@ -104,7 +103,7 @@ impl WorktreeList {
                 .context("No active repository")?
                 .await??
                 .into_iter()
-                .filter(|worktree| worktree.ref_name.is_some()) // hide worktrees without a branch
+                .filter(|worktree| !worktree.is_bare) // hide bare repositories
                 .collect();
 
             let default_branch = default_branch_request
@@ -128,7 +127,7 @@ impl WorktreeList {
         })
         .detach_and_log_err(cx);
 
-        let delegate = WorktreeListDelegate::new(workspace, repository, window, cx);
+        let delegate = WorktreeListDelegate::new(workspace, repository.clone(), window, cx);
         let picker = cx.new(|cx| {
             Picker::uniform_list(delegate, window, cx)
                 .show_scrollbar(true)
@@ -139,11 +138,38 @@ impl WorktreeList {
             picker.delegate.focus_handle = picker_focus_handle.clone();
         });
 
+        let mut subscriptions = Vec::new();
+        if let Some(repo) = &repository {
+            let picker_entity = picker.clone();
+            subscriptions.push(cx.subscribe(
+                repo,
+                move |_this, repo, event: &RepositoryEvent, cx| {
+                    if matches!(event, RepositoryEvent::GitWorktreeListChanged) {
+                        let worktrees_request = repo.update(cx, |repo, _| repo.worktrees());
+                        let picker = picker_entity.clone();
+                        cx.spawn(async move |_, cx| {
+                            let all_worktrees: Vec<_> = worktrees_request
+                                .await??
+                                .into_iter()
+                                .filter(|worktree| !worktree.is_bare)
+                                .collect();
+                            picker.update(cx, |picker, cx| {
+                                picker.delegate.all_worktrees = Some(all_worktrees);
+                                picker.delegate.refresh_forbidden_deletion_path(cx);
+                            });
+                            anyhow::Ok(())
+                        })
+                        .detach_and_log_err(cx);
+                    }
+                },
+            ));
+        }
+
         Self {
             picker,
             picker_focus_handle,
             width,
-            _subscription: None,
+            _subscriptions: subscriptions,
             embedded,
         }
     }
@@ -156,9 +182,10 @@ impl WorktreeList {
         cx: &mut Context<Self>,
     ) -> Self {
         let mut this = Self::new_inner(repository, workspace, width, true, window, cx);
-        this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| {
-            cx.emit(DismissEvent);
-        }));
+        this._subscriptions
+            .push(cx.subscribe(&this.picker, |_, _, _, cx| {
+                cx.emit(DismissEvent);
+            }));
         this
     }
 
@@ -695,6 +722,11 @@ impl PickerDelegate for WorktreeListDelegate {
         };
 
         cx.spawn_in(window, async move |picker, cx| {
+            let main_worktree_path = all_worktrees
+                .iter()
+                .find(|wt| wt.is_main)
+                .map(|wt| wt.path.clone());
+
             let mut matches: Vec<WorktreeEntry> = if query.is_empty() {
                 all_worktrees
                     .into_iter()
@@ -709,12 +741,10 @@ impl PickerDelegate for WorktreeListDelegate {
                     .iter()
                     .enumerate()
                     .map(|(ix, worktree)| {
-                        let name = if worktree.is_main {
-                            MAIN_WORKTREE_DISPLAY_NAME
-                        } else {
-                            worktree.display_name()
-                        };
-                        StringMatchCandidate::new(ix, name)
+                        StringMatchCandidate::new(
+                            ix,
+                            &worktree.directory_name(main_worktree_path.as_deref()),
+                        )
                     })
                     .collect::<Vec<StringMatchCandidate>>();
                 fuzzy::match_strings(
@@ -739,12 +769,7 @@ impl PickerDelegate for WorktreeListDelegate {
                 .update(cx, |picker, _| {
                     if !query.is_empty()
                         && !matches.first().is_some_and(|entry| {
-                            let name = if entry.worktree.is_main {
-                                MAIN_WORKTREE_DISPLAY_NAME
-                            } else {
-                                entry.worktree.display_name()
-                            };
-                            name == query
+                            entry.worktree.directory_name(main_worktree_path.as_deref()) == query
                         })
                     {
                         let query = query.replace(' ', "-");
@@ -754,6 +779,7 @@ impl PickerDelegate for WorktreeListDelegate {
                                 ref_name: Some(format!("refs/heads/{query}").into()),
                                 sha: Default::default(),
                                 is_main: false,
+                                is_bare: false,
                             },
                             positions: Vec::new(),
                             is_new: true,
@@ -821,12 +847,13 @@ impl PickerDelegate for WorktreeListDelegate {
                 ),
             )
         } else {
-            let display_name = if entry.worktree.is_main {
-                MAIN_WORKTREE_DISPLAY_NAME
-            } else {
-                entry.worktree.display_name()
-            };
-            let first_line = display_name.lines().next().unwrap_or(display_name);
+            let main_worktree_path = self
+                .all_worktrees
+                .as_ref()
+                .and_then(|wts| wts.iter().find(|wt| wt.is_main))
+                .map(|wt| wt.path.as_path());
+            let display_name = entry.worktree.directory_name(main_worktree_path);
+            let first_line = display_name.lines().next().unwrap_or(&display_name);
             let positions: Vec<_> = entry
                 .positions
                 .iter()
@@ -903,6 +930,22 @@ impl PickerDelegate for WorktreeListDelegate {
                                         .w_full()
                                         .min_w_0()
                                         .gap_1p5()
+                                        .when_some(
+                                            entry.worktree.branch_name().map(|b| b.to_string()),
+                                            |this, branch| {
+                                                this.child(
+                                                    Label::new(branch)
+                                                        .size(LabelSize::Small)
+                                                        .color(Color::Muted),
+                                                )
+                                                .child(
+                                                    Label::new("β€’")
+                                                        .alpha(0.5)
+                                                        .color(Color::Muted)
+                                                        .size(LabelSize::Small),
+                                                )
+                                            },
+                                        )
                                         .child(
                                             Label::new(sha)
                                                 .size(LabelSize::Small)

crates/icons/src/icons.rs πŸ”—

@@ -146,7 +146,6 @@ pub enum IconName {
     GenericMinimize,
     GenericRestore,
     GitBranch,
-    GitBranchAlt,
     GitBranchPlus,
     GitCommit,
     GitGraph,

crates/notifications/src/status_toast.rs πŸ”—

@@ -206,7 +206,7 @@ impl Component for StatusToast {
 
         let pr_example =
             StatusToast::new("`zed/new-notification-system` created!", cx, |this, _cx| {
-                this.icon(ToastIcon::new(IconName::GitBranchAlt).color(Color::Muted))
+                this.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
                     .action("Open Pull Request", |_, cx| {
                         cx.open_url("https://github.com/")
                     })

crates/project/src/git_store.rs πŸ”—

@@ -7441,15 +7441,21 @@ fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree {
             .unwrap_or_default(),
         sha: worktree.sha.to_string(),
         is_main: worktree.is_main,
+        is_bare: worktree.is_bare,
     }
 }
 
 fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree {
     git::repository::Worktree {
         path: PathBuf::from(proto.path.clone()),
-        ref_name: Some(SharedString::from(&proto.ref_name)),
+        ref_name: if proto.ref_name.is_empty() {
+            None
+        } else {
+            Some(SharedString::from(&proto.ref_name))
+        },
         sha: proto.sha.clone().into(),
         is_main: proto.is_main,
+        is_bare: proto.is_bare,
     }
 }
 

crates/proto/proto/git.proto πŸ”—

@@ -586,6 +586,7 @@ message Worktree {
   string ref_name = 2;
   string sha = 3;
   bool is_main = 4;
+  bool is_bare = 5;
 }
 
 message GitCreateWorktree {

crates/remote_server/src/remote_editing_tests.rs πŸ”—

@@ -1566,6 +1566,7 @@ async fn test_remote_root_repo_common_dir(cx: &mut TestAppContext, server_cx: &m
             ref_name: Some("refs/heads/feature-branch".into()),
             sha: "abc123".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;

crates/sidebar/src/sidebar.rs πŸ”—

@@ -46,7 +46,7 @@ use util::ResultExt as _;
 use util::path_list::PathList;
 use workspace::{
     CloseWindow, FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, NextProject,
-    NextThread, Open, PreviousProject, PreviousThread, ProjectGroupKey, SaveIntent,
+    NextThread, Open, OpenMode, PreviousProject, PreviousThread, ProjectGroupKey, SaveIntent,
     ShowFewerThreads, ShowMoreThreads, Sidebar as WorkspaceSidebar, SidebarSide, Toast,
     ToggleWorkspaceSidebar, Workspace, notifications::NotificationId, sidebar_side_context_menu,
 };
@@ -944,6 +944,8 @@ impl Sidebar {
                 provisional_key,
                 |options, window, cx| connect_remote(active_workspace, options, window, cx),
                 &[],
+                None,
+                OpenMode::Activate,
                 window,
                 cx,
             )
@@ -980,6 +982,8 @@ impl Sidebar {
                 provisional_key,
                 |options, window, cx| connect_remote(active_workspace, options, window, cx),
                 &[],
+                None,
+                OpenMode::Activate,
                 window,
                 cx,
             )
@@ -2522,6 +2526,8 @@ impl Sidebar {
                 provisional_key,
                 |options, window, cx| connect_remote(active_workspace, options, window, cx),
                 &[],
+                None,
+                OpenMode::Activate,
                 window,
                 cx,
             )
@@ -3156,6 +3162,8 @@ impl Sidebar {
                                 connect_remote(active_workspace, options, window, cx)
                             },
                             &excluded,
+                            None,
+                            OpenMode::Activate,
                             window,
                             cx,
                         )

crates/sidebar/src/sidebar_tests.rs πŸ”—

@@ -3347,6 +3347,7 @@ async fn test_cmd_n_shows_new_thread_entry_in_absorbed_worktree(cx: &mut TestApp
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -3463,6 +3464,7 @@ async fn test_search_matches_worktree_name(cx: &mut TestAppContext) {
                 ref_name: Some("refs/heads/rosewood".into()),
                 sha: "abc".into(),
                 is_main: false,
+                is_bare: false,
             },
         )
         .await;
@@ -3547,6 +3549,7 @@ async fn test_git_worktree_added_live_updates_sidebar(cx: &mut TestAppContext) {
                 ref_name: Some("refs/heads/rosewood".into()),
                 sha: "abc".into(),
                 is_main: false,
+                is_bare: false,
             },
         )
         .await;
@@ -3588,6 +3591,7 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -3599,6 +3603,7 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC
             ref_name: Some("refs/heads/feature-b".into()),
             sha: "bbb".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -3698,6 +3703,7 @@ async fn test_threadless_workspace_shows_new_thread_with_worktree_chip(cx: &mut
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -3709,6 +3715,7 @@ async fn test_threadless_workspace_shows_new_thread_with_worktree_chip(cx: &mut
             ref_name: Some("refs/heads/feature-b".into()),
             sha: "bbb".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -3781,6 +3788,7 @@ async fn test_multi_worktree_thread_shows_multiple_chips(cx: &mut TestAppContext
                     ref_name: Some(format!("refs/heads/{branch}").into()),
                     sha: "aaa".into(),
                     is_main: false,
+                    is_bare: false,
                 },
             )
             .await;
@@ -3858,6 +3866,7 @@ async fn test_same_named_worktree_chips_are_deduplicated(cx: &mut TestAppContext
                 ref_name: Some("refs/heads/olivetti".into()),
                 sha: "aaa".into(),
                 is_main: false,
+                is_bare: false,
             },
         )
         .await;
@@ -3931,6 +3940,7 @@ async fn test_absorbed_worktree_running_thread_shows_live_status(cx: &mut TestAp
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -4027,6 +4037,7 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -4111,6 +4122,7 @@ async fn test_clicking_worktree_thread_opens_workspace_when_none_exists(cx: &mut
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -4207,6 +4219,7 @@ async fn test_clicking_worktree_thread_does_not_briefly_render_as_separate_proje
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -4349,6 +4362,7 @@ async fn test_clicking_absorbed_worktree_thread_activates_worktree_workspace(
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -4961,6 +4975,7 @@ async fn test_archive_thread_uses_next_threads_own_workspace(cx: &mut TestAppCon
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5127,6 +5142,7 @@ async fn test_archive_last_worktree_thread_removes_workspace(cx: &mut TestAppCon
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "abc".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5282,6 +5298,7 @@ async fn test_restore_worktree_when_branch_has_moved(cx: &mut TestAppContext) {
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "original-sha".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5394,6 +5411,7 @@ async fn test_restore_worktree_when_branch_has_not_moved(cx: &mut TestAppContext
             ref_name: Some("refs/heads/feature-b".into()),
             sha: "original-sha".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5490,6 +5508,7 @@ async fn test_restore_worktree_when_branch_does_not_exist(cx: &mut TestAppContex
             ref_name: Some("refs/heads/feature-d".into()),
             sha: "original-sha".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5593,6 +5612,7 @@ async fn test_restore_worktree_thread_uses_main_repo_project_group_key(cx: &mut
             ref_name: Some("refs/heads/feature-c".into()),
             sha: "original-sha".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5739,6 +5759,7 @@ async fn test_archive_last_worktree_thread_not_blocked_by_remote_thread_at_same_
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "abc".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -5902,6 +5923,7 @@ async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut Test
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -7376,6 +7398,7 @@ async fn test_archive_last_thread_on_linked_worktree_does_not_create_new_thread_
             ref_name: Some("refs/heads/ochre-drift".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -7545,6 +7568,7 @@ async fn test_archive_last_thread_on_linked_worktree_with_no_siblings_leaves_gro
             ref_name: Some("refs/heads/ochre-drift".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -7675,6 +7699,7 @@ async fn test_unarchive_linked_worktree_thread_into_project_group_shows_only_res
             ref_name: Some("refs/heads/ochre-drift".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -7842,6 +7867,7 @@ async fn test_archive_thread_on_linked_worktree_selects_sibling_thread(cx: &mut
             ref_name: Some("refs/heads/ochre-drift".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -7991,6 +8017,7 @@ async fn test_linked_worktree_workspace_reachable_and_dismissable(cx: &mut TestA
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -8144,6 +8171,7 @@ async fn test_linked_worktree_workspace_shows_main_worktree_threads(cx: &mut Tes
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "abc".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -8391,6 +8419,7 @@ async fn test_legacy_thread_with_canonical_path_opens_main_repo_workspace(cx: &m
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "abc".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -8546,6 +8575,7 @@ async fn test_linked_worktree_workspace_reachable_after_adding_unrelated_project
             ref_name: Some(format!("refs/heads/{}", worktree_name).into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -9061,6 +9091,7 @@ async fn test_worktree_add_only_regroups_threads_for_changed_workspace(cx: &mut
             ref_name: Some("refs/heads/feature".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -9219,6 +9250,7 @@ async fn test_linked_worktree_workspace_reachable_after_adding_worktree_to_proje
             ref_name: Some("refs/heads/wt-0".into()),
             sha: "aaa".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -9660,6 +9692,7 @@ mod property_test {
                             ref_name: Some(format!("refs/heads/{}", worktree_name).into()),
                             sha: "aaa".into(),
                             is_main: false,
+                            is_bare: false,
                         },
                     )
                     .await;
@@ -10448,6 +10481,7 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro
                 ref_name: Some("refs/heads/feature-wt".into()),
                 sha: "abc123".into(),
                 is_main: false,
+                is_bare: false,
             },
         )
         .await;
@@ -10536,6 +10570,7 @@ async fn test_archive_removes_worktree_even_when_workspace_paths_diverge(cx: &mu
             ref_name: Some("refs/heads/feature-a".into()),
             sha: "abc".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -10680,6 +10715,7 @@ async fn test_archive_mixed_workspace_closes_only_archived_worktree_items(cx: &m
             ref_name: Some("refs/heads/feature-b".into()),
             sha: "def".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;

crates/workspace/src/multi_workspace.rs πŸ”—

@@ -864,6 +864,8 @@ impl MultiWorkspace {
                         neighbor_key.path_list().clone(),
                         Some(neighbor_key.clone()),
                         &excluded_workspaces,
+                        None,
+                        OpenMode::Activate,
                         window,
                         cx,
                     );
@@ -992,6 +994,8 @@ impl MultiWorkspace {
         ) -> Task<Result<Option<Entity<remote::RemoteClient>>>>
         + 'static,
         excluding: &[Entity<Workspace>],
+        init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
+        open_mode: OpenMode,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<Workspace>>> {
@@ -1005,6 +1009,8 @@ impl MultiWorkspace {
                 paths,
                 provisional_project_group_key,
                 excluding,
+                init,
+                open_mode,
                 window,
                 cx,
             );
@@ -1067,6 +1073,8 @@ impl MultiWorkspace {
         path_list: PathList,
         project_group: Option<ProjectGroupKey>,
         excluding: &[Entity<Workspace>],
+        init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
+        open_mode: OpenMode,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<Workspace>>> {
@@ -1133,8 +1141,8 @@ impl MultiWorkspace {
                         app_state,
                         requesting_window,
                         None,
-                        None,
-                        OpenMode::Activate,
+                        init,
+                        open_mode,
                         cx,
                     )
                 })
@@ -1675,7 +1683,15 @@ impl MultiWorkspace {
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<Workspace>>> {
         if self.multi_workspace_enabled(cx) {
-            self.find_or_create_local_workspace(PathList::new(&paths), None, &[], window, cx)
+            self.find_or_create_local_workspace(
+                PathList::new(&paths),
+                None,
+                &[],
+                None,
+                OpenMode::Activate,
+                window,
+                cx,
+            )
         } else {
             let workspace = self.workspace().clone();
             cx.spawn_in(window, async move |_this, cx| {

crates/workspace/src/multi_workspace_tests.rs πŸ”—

@@ -368,6 +368,8 @@ async fn test_find_or_create_local_workspace_reuses_active_workspace_when_sideba
                 PathList::new(&[PathBuf::from("/root_a")]),
                 None,
                 &[],
+                None,
+                OpenMode::Activate,
                 window,
                 cx,
             )
@@ -431,6 +433,8 @@ async fn test_find_or_create_workspace_uses_project_group_key_when_paths_are_mis
                 Some(project_group_key.clone()),
                 |_options, _window, _cx| Task::ready(Ok(None)),
                 &[],
+                None,
+                OpenMode::Activate,
                 window,
                 cx,
             )
@@ -496,6 +500,8 @@ async fn test_find_or_create_local_workspace_reuses_active_workspace_after_sideb
                 PathList::new(&[PathBuf::from("/root_a")]),
                 None,
                 &[],
+                None,
+                OpenMode::Activate,
                 window,
                 cx,
             )

crates/workspace/src/persistence.rs πŸ”—

@@ -2513,6 +2513,7 @@ pub fn delete_unloaded_items(
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::OpenMode;
     use crate::PathList;
     use crate::ProjectGroupKey;
     use crate::{
@@ -5066,7 +5067,15 @@ mod tests {
             mw.remove(
                 vec![workspace_a.clone()],
                 move |this, window, cx| {
-                    this.find_or_create_local_workspace(path_list, None, &excluded, window, cx)
+                    this.find_or_create_local_workspace(
+                        path_list,
+                        None,
+                        &excluded,
+                        None,
+                        OpenMode::Activate,
+                        window,
+                        cx,
+                    )
                 },
                 window,
                 cx,

crates/workspace/src/workspace.rs πŸ”—

@@ -89,7 +89,7 @@ use persistence::{SerializedWindowBounds, model::SerializedWorkspace};
 pub use persistence::{
     WorkspaceDb, delete_unloaded_items,
     model::{
-        DockStructure, ItemId, MultiWorkspaceState, SerializedMultiWorkspace,
+        DockData, DockStructure, ItemId, MultiWorkspaceState, SerializedMultiWorkspace,
         SerializedProjectGroup, SerializedWorkspaceLocation, SessionWorkspace,
     },
     read_serialized_multi_workspaces, resolve_worktree_workspaces,
@@ -162,7 +162,7 @@ use crate::{dock::PanelSizeState, item::ItemBufferKind, notifications::Notificat
 use crate::{
     persistence::{
         SerializedAxis,
-        model::{DockData, SerializedItem, SerializedPane, SerializedPaneGroup},
+        model::{SerializedItem, SerializedPane, SerializedPaneGroup},
     },
     security_modal::SecurityModal,
 };

crates/worktree/tests/integration/main.rs πŸ”—

@@ -2769,6 +2769,7 @@ async fn test_root_repo_common_dir(executor: BackgroundExecutor, cx: &mut TestAp
             ref_name: Some("refs/heads/feature".into()),
             sha: "abc123".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;
@@ -2871,6 +2872,7 @@ async fn test_linked_worktree_git_file_event_does_not_panic(
             ref_name: Some("refs/heads/feature".into()),
             sha: "abc123".into(),
             is_main: false,
+            is_bare: false,
         },
     )
     .await;

crates/zed/src/visual_test_runner.rs πŸ”—

@@ -552,27 +552,6 @@ fn run_visual_tests(project_path: PathBuf, update_baseline: bool) -> Result<()>
         }
     }
 
-    // Run Test 11: Thread target selector visual tests
-    #[cfg(feature = "visual-tests")]
-    {
-        println!("\n--- Test 11: start_thread_in_selector (6 variants) ---");
-        match run_start_thread_in_selector_visual_tests(app_state.clone(), &mut cx, update_baseline)
-        {
-            Ok(TestResult::Passed) => {
-                println!("βœ“ start_thread_in_selector: PASSED");
-                passed += 1;
-            }
-            Ok(TestResult::BaselineUpdated(_)) => {
-                println!("βœ“ start_thread_in_selector: Baselines updated");
-                updated += 1;
-            }
-            Err(e) => {
-                eprintln!("βœ— start_thread_in_selector: FAILED - {}", e);
-                failed += 1;
-            }
-        }
-    }
-
     // Run Test: Sidebar with duplicate project names
     println!("\n--- Test: sidebar_duplicate_names ---");
     match run_sidebar_duplicate_project_names_visual_tests(
@@ -3066,30 +3045,6 @@ fn run_error_wrapping_visual_tests(
     Ok(test_result)
 }
 
-#[cfg(all(target_os = "macos", feature = "visual-tests"))]
-/// Runs a git command in the given directory and returns an error with
-/// stderr/stdout context if the command fails (non-zero exit status).
-fn run_git_command(args: &[&str], dir: &std::path::Path) -> Result<()> {
-    let output = std::process::Command::new("git")
-        .args(args)
-        .current_dir(dir)
-        .output()
-        .with_context(|| format!("failed to spawn `git {}`", args.join(" ")))?;
-
-    if !output.status.success() {
-        let stdout = String::from_utf8_lossy(&output.stdout);
-        let stderr = String::from_utf8_lossy(&output.stderr);
-        anyhow::bail!(
-            "`git {}` failed (exit {})\nstdout: {}\nstderr: {}",
-            args.join(" "),
-            output.status,
-            stdout.trim(),
-            stderr.trim(),
-        );
-    }
-    Ok(())
-}
-
 #[cfg(target_os = "macos")]
 /// Helper to create a project, add a worktree at the given path, and return the project.
 fn create_project_with_worktree(
@@ -3362,597 +3317,3 @@ fn run_sidebar_duplicate_project_names_visual_tests(
         Ok(TestResult::Passed)
     }
 }
-
-#[cfg(all(target_os = "macos", feature = "visual-tests"))]
-fn run_start_thread_in_selector_visual_tests(
-    app_state: Arc<AppState>,
-    cx: &mut VisualTestAppContext,
-    update_baseline: bool,
-) -> Result<TestResult> {
-    use agent_ui::{AgentPanel, NewWorktreeBranchTarget, StartThreadIn, WorktreeCreationStatus};
-
-    // Create a temp directory with a real git repo so "New Worktree" is enabled
-    let temp_dir = tempfile::tempdir()?;
-    let temp_path = temp_dir.keep();
-    let canonical_temp = temp_path.canonicalize()?;
-    let project_path = canonical_temp.join("project");
-    std::fs::create_dir_all(&project_path)?;
-
-    // Initialize git repo
-    run_git_command(&["init"], &project_path)?;
-    run_git_command(&["config", "user.email", "test@test.com"], &project_path)?;
-    run_git_command(&["config", "user.name", "Test User"], &project_path)?;
-
-    // Create source files
-    let src_dir = project_path.join("src");
-    std::fs::create_dir_all(&src_dir)?;
-    std::fs::write(
-        src_dir.join("main.rs"),
-        r#"fn main() {
-    println!("Hello, world!");
-
-    let x = 42;
-    let y = x * 2;
-
-    if y > 50 {
-        println!("y is greater than 50");
-    } else {
-        println!("y is not greater than 50");
-    }
-
-    for i in 0..10 {
-        println!("i = {}", i);
-    }
-}
-
-fn helper_function(a: i32, b: i32) -> i32 {
-    a + b
-}
-"#,
-    )?;
-
-    std::fs::write(
-        project_path.join("Cargo.toml"),
-        r#"[package]
-name = "test_project"
-version = "0.1.0"
-edition = "2021"
-"#,
-    )?;
-
-    // Commit so git status is clean
-    run_git_command(&["add", "."], &project_path)?;
-    run_git_command(&["commit", "-m", "Initial commit"], &project_path)?;
-
-    let project = cx.update(|cx| {
-        project::Project::local(
-            app_state.client.clone(),
-            app_state.node_runtime.clone(),
-            app_state.user_store.clone(),
-            app_state.languages.clone(),
-            app_state.fs.clone(),
-            None,
-            project::LocalProjectFlags {
-                init_worktree_trust: false,
-                ..Default::default()
-            },
-            cx,
-        )
-    });
-
-    // Use a wide window so we see project panel + editor + agent panel
-    let window_size = size(px(1280.0), px(800.0));
-    let bounds = Bounds {
-        origin: point(px(0.0), px(0.0)),
-        size: window_size,
-    };
-
-    let workspace_window: WindowHandle<MultiWorkspace> = cx
-        .update(|cx| {
-            cx.open_window(
-                WindowOptions {
-                    window_bounds: Some(WindowBounds::Windowed(bounds)),
-                    focus: false,
-                    show: false,
-                    ..Default::default()
-                },
-                |window, cx| {
-                    let workspace = cx.new(|cx| {
-                        Workspace::new(None, project.clone(), app_state.clone(), window, cx)
-                    });
-                    cx.new(|cx| MultiWorkspace::new(workspace, window, cx))
-                },
-            )
-        })
-        .context("Failed to open thread target selector test window")?;
-
-    cx.run_until_parked();
-
-    // Create the sidebar outside the MultiWorkspace update to avoid a
-    // re-entrant read panic (Sidebar::new reads the MultiWorkspace).
-    let sidebar = cx
-        .update_window(workspace_window.into(), |root_view, window, cx| {
-            let multi_workspace_handle: Entity<MultiWorkspace> = root_view.downcast().unwrap();
-            cx.new(|cx| sidebar::Sidebar::new(multi_workspace_handle, window, cx))
-        })
-        .context("Failed to create sidebar")?;
-
-    workspace_window
-        .update(cx, |multi_workspace, _window, cx| {
-            multi_workspace.register_sidebar(sidebar.clone(), cx);
-        })
-        .context("Failed to register sidebar")?;
-
-    // Open the sidebar
-    workspace_window
-        .update(cx, |multi_workspace, window, cx| {
-            multi_workspace.toggle_sidebar(window, cx);
-        })
-        .context("Failed to toggle sidebar")?;
-
-    cx.run_until_parked();
-
-    // Add the git project as a worktree
-    let add_worktree_task = workspace_window
-        .update(cx, |multi_workspace, _window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            let project = workspace.read(cx).project().clone();
-            project.update(cx, |project, cx| {
-                project.find_or_create_worktree(&project_path, true, cx)
-            })
-        })
-        .context("Failed to start adding worktree")?;
-
-    cx.background_executor.allow_parking();
-    cx.foreground_executor
-        .block_test(add_worktree_task)
-        .context("Failed to add worktree")?;
-    cx.background_executor.forbid_parking();
-
-    cx.run_until_parked();
-
-    // Wait for worktree scan and git status
-    for _ in 0..5 {
-        cx.advance_clock(Duration::from_millis(100));
-        cx.run_until_parked();
-    }
-
-    // Open the project panel
-    let (weak_workspace, async_window_cx) = workspace_window
-        .update(cx, |multi_workspace, window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            (workspace.read(cx).weak_handle(), window.to_async(cx))
-        })
-        .context("Failed to get workspace handle")?;
-
-    cx.background_executor.allow_parking();
-    let project_panel = cx
-        .foreground_executor
-        .block_test(ProjectPanel::load(weak_workspace, async_window_cx))
-        .context("Failed to load project panel")?;
-    cx.background_executor.forbid_parking();
-
-    workspace_window
-        .update(cx, |multi_workspace, window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            workspace.update(cx, |workspace, cx| {
-                workspace.add_panel(project_panel, window, cx);
-                workspace.open_panel::<ProjectPanel>(window, cx);
-            });
-        })
-        .context("Failed to add project panel")?;
-
-    cx.run_until_parked();
-
-    // Open main.rs in the editor
-    let open_file_task = workspace_window
-        .update(cx, |multi_workspace, window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            workspace.update(cx, |workspace, cx| {
-                let worktree = workspace.project().read(cx).worktrees(cx).next();
-                if let Some(worktree) = worktree {
-                    let worktree_id = worktree.read(cx).id();
-                    let rel_path: std::sync::Arc<util::rel_path::RelPath> =
-                        util::rel_path::rel_path("src/main.rs").into();
-                    let project_path: project::ProjectPath = (worktree_id, rel_path).into();
-                    Some(workspace.open_path(project_path, None, true, window, cx))
-                } else {
-                    None
-                }
-            })
-        })
-        .log_err()
-        .flatten();
-
-    if let Some(task) = open_file_task {
-        cx.background_executor.allow_parking();
-        cx.foreground_executor.block_test(task).log_err();
-        cx.background_executor.forbid_parking();
-    }
-
-    cx.run_until_parked();
-
-    // Load the AgentPanel
-    let (weak_workspace, async_window_cx) = workspace_window
-        .update(cx, |multi_workspace, window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            (workspace.read(cx).weak_handle(), window.to_async(cx))
-        })
-        .context("Failed to get workspace handle for agent panel")?;
-
-    // Register an observer so that workspaces created by the worktree creation
-    // flow get AgentPanel and ProjectPanel loaded automatically. Without this,
-    // `workspace.panel::<AgentPanel>(cx)` returns None in the new workspace and
-    // the creation flow's `focus_panel::<AgentPanel>` call is a no-op.
-    let _workspace_observer = cx.update(|cx| {
-        cx.observe_new(move |workspace: &mut Workspace, window, cx| {
-            let Some(window) = window else { return };
-            let panels_task = cx.spawn_in(window, async move |workspace_handle, cx| {
-                let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
-                let agent_panel = AgentPanel::load(workspace_handle.clone(), cx.clone());
-                if let Ok(panel) = project_panel.await {
-                    workspace_handle
-                        .update_in(cx, |workspace, window, cx| {
-                            workspace.add_panel(panel, window, cx);
-                        })
-                        .log_err();
-                }
-                if let Ok(panel) = agent_panel.await {
-                    workspace_handle
-                        .update_in(cx, |workspace, window, cx| {
-                            workspace.add_panel(panel, window, cx);
-                        })
-                        .log_err();
-                }
-                anyhow::Ok(())
-            });
-            workspace.set_panels_task(panels_task);
-        })
-    });
-
-    cx.background_executor.allow_parking();
-    let panel = cx
-        .foreground_executor
-        .block_test(AgentPanel::load(weak_workspace, async_window_cx))
-        .context("Failed to load AgentPanel")?;
-    cx.background_executor.forbid_parking();
-
-    workspace_window
-        .update(cx, |multi_workspace, window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            workspace.update(cx, |workspace, cx| {
-                workspace.add_panel(panel.clone(), window, cx);
-                workspace.open_panel::<AgentPanel>(window, cx);
-            });
-        })
-        .context("Failed to add and open AgentPanel")?;
-
-    cx.run_until_parked();
-
-    // Inject the stub server and open a thread so the toolbar is visible
-    let connection = StubAgentConnection::new();
-    let stub_agent: Rc<dyn AgentServer> = Rc::new(StubAgentServer::new(connection));
-
-    cx.update_window(workspace_window.into(), |_, window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel.open_external_thread_with_server(stub_agent.clone(), window, cx);
-        });
-    })?;
-
-    cx.run_until_parked();
-
-    // ---- Screenshot 1: Default "Local Project" selector (dropdown closed) ----
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.refresh();
-    })?;
-    cx.run_until_parked();
-
-    let result_default = run_visual_test(
-        "start_thread_in_selector_default",
-        workspace_window.into(),
-        cx,
-        update_baseline,
-    );
-
-    // ---- Screenshot 2: Dropdown open showing menu entries ----
-    cx.update_window(workspace_window.into(), |_, window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel.open_start_thread_in_menu_for_tests(window, cx);
-        });
-    })?;
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.refresh();
-    })?;
-    cx.run_until_parked();
-
-    let result_open_dropdown = run_visual_test(
-        "start_thread_in_selector_open",
-        workspace_window.into(),
-        cx,
-        update_baseline,
-    );
-
-    // ---- Screenshot 3: "New Worktree" selected (dropdown closed, label changed) ----
-    // First dismiss the dropdown, then change the target so the toolbar label is visible
-    cx.update_window(workspace_window.into(), |_, _window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel.close_start_thread_in_menu_for_tests(cx);
-        });
-    })?;
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, _window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel.set_start_thread_in_for_tests(
-                StartThreadIn::NewWorktree {
-                    worktree_name: None,
-                    branch_target: NewWorktreeBranchTarget::default(),
-                },
-                cx,
-            );
-        });
-    })?;
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.refresh();
-    })?;
-    cx.run_until_parked();
-
-    let result_new_worktree = run_visual_test(
-        "start_thread_in_selector_new_worktree",
-        workspace_window.into(),
-        cx,
-        update_baseline,
-    );
-
-    // ---- Screenshot 4: "Creating worktree…" status banner ----
-    cx.update_window(workspace_window.into(), |_, _window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel
-                .set_worktree_creation_status_for_tests(Some(WorktreeCreationStatus::Creating), cx);
-        });
-    })?;
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.refresh();
-    })?;
-    cx.run_until_parked();
-
-    let result_creating = run_visual_test(
-        "worktree_creation_status_creating",
-        workspace_window.into(),
-        cx,
-        update_baseline,
-    );
-
-    // ---- Screenshot 5: Error status banner ----
-    cx.update_window(workspace_window.into(), |_, _window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel.set_worktree_creation_status_for_tests(
-                Some(WorktreeCreationStatus::Error(
-                    "Failed to create worktree: branch already exists".into(),
-                )),
-                cx,
-            );
-        });
-    })?;
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.refresh();
-    })?;
-    cx.run_until_parked();
-
-    let result_error = run_visual_test(
-        "worktree_creation_status_error",
-        workspace_window.into(),
-        cx,
-        update_baseline,
-    );
-
-    // ---- Screenshot 6: Worktree creation succeeded ----
-    // Clear the error status and re-select New Worktree to ensure a clean state.
-    cx.update_window(workspace_window.into(), |_, _window, cx| {
-        panel.update(cx, |panel, cx| {
-            panel.set_worktree_creation_status_for_tests(None, cx);
-        });
-    })?;
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, window, cx| {
-        window.dispatch_action(
-            Box::new(StartThreadIn::NewWorktree {
-                worktree_name: None,
-                branch_target: NewWorktreeBranchTarget::default(),
-            }),
-            cx,
-        );
-    })?;
-    cx.run_until_parked();
-
-    // Insert a message into the active thread's message editor and submit.
-    let thread_view = cx
-        .read(|cx| panel.read(cx).active_thread_view(cx))
-        .ok_or_else(|| anyhow::anyhow!("No active thread view"))?;
-
-    cx.update_window(workspace_window.into(), |_, window, cx| {
-        let message_editor = thread_view.read(cx).message_editor.clone();
-        message_editor.update(cx, |message_editor, cx| {
-            message_editor.set_message(
-                vec![acp::ContentBlock::Text(acp::TextContent::new(
-                    "Add a CLI flag to set the log level".to_string(),
-                ))],
-                window,
-                cx,
-            );
-            message_editor.send(cx);
-        });
-    })?;
-    cx.run_until_parked();
-
-    // Wait for the full worktree creation flow to complete. The creation status
-    // is cleared to `None` at the very end of the async task, after panels are
-    // loaded, the agent panel is focused, and the new workspace is activated.
-    cx.background_executor.allow_parking();
-    let mut creation_complete = false;
-    for _ in 0..120 {
-        cx.run_until_parked();
-        let status_cleared = cx.read(|cx| {
-            panel
-                .read(cx)
-                .worktree_creation_status_for_tests()
-                .is_none()
-        });
-        let workspace_count = workspace_window.update(cx, |multi_workspace, _window, _cx| {
-            multi_workspace.workspaces().count()
-        })?;
-        if workspace_count == 2 && status_cleared {
-            creation_complete = true;
-            break;
-        }
-        cx.advance_clock(Duration::from_millis(100));
-    }
-    cx.background_executor.forbid_parking();
-
-    if !creation_complete {
-        return Err(anyhow::anyhow!("Worktree creation did not complete"));
-    }
-
-    // The creation flow called `external_thread` on the new workspace's agent
-    // panel, which tried to launch a real agent binary and failed. Replace the
-    // error state by injecting the stub server, and shrink the panel so the
-    // editor content is visible.
-    workspace_window.update(cx, |multi_workspace, window, cx| {
-        let new_workspace = multi_workspace.workspaces().nth(1).unwrap();
-        new_workspace.update(cx, |workspace, cx| {
-            if let Some(new_panel) = workspace.panel::<AgentPanel>(cx) {
-                new_panel.update(cx, |panel, cx| {
-                    panel.open_external_thread_with_server(stub_agent.clone(), window, cx);
-                });
-            }
-        });
-    })?;
-    cx.run_until_parked();
-
-    // Type and send a message so the thread target dropdown disappears.
-    let new_panel = workspace_window.update(cx, |multi_workspace, _window, cx| {
-        let new_workspace = multi_workspace.workspaces().nth(1).unwrap();
-        new_workspace.read(cx).panel::<AgentPanel>(cx)
-    })?;
-    if let Some(new_panel) = new_panel {
-        let new_thread_view = cx.read(|cx| new_panel.read(cx).active_thread_view(cx));
-        if let Some(new_thread_view) = new_thread_view {
-            cx.update_window(workspace_window.into(), |_, window, cx| {
-                let message_editor = new_thread_view.read(cx).message_editor.clone();
-                message_editor.update(cx, |editor, cx| {
-                    editor.set_message(
-                        vec![acp::ContentBlock::Text(acp::TextContent::new(
-                            "Add a CLI flag to set the log level".to_string(),
-                        ))],
-                        window,
-                        cx,
-                    );
-                    editor.send(cx);
-                });
-            })?;
-            cx.run_until_parked();
-        }
-    }
-
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.refresh();
-    })?;
-    cx.run_until_parked();
-
-    let result_succeeded = run_visual_test(
-        "worktree_creation_succeeded",
-        workspace_window.into(),
-        cx,
-        update_baseline,
-    );
-
-    // Clean up β€” drop the workspace observer first so no new panels are
-    // registered on workspaces created during teardown.
-    drop(_workspace_observer);
-
-    workspace_window
-        .update(cx, |multi_workspace, _window, cx| {
-            let workspace = multi_workspace.workspaces().next().unwrap();
-            let project = workspace.read(cx).project().clone();
-            project.update(cx, |project, cx| {
-                let worktree_ids: Vec<_> =
-                    project.worktrees(cx).map(|wt| wt.read(cx).id()).collect();
-                for id in worktree_ids {
-                    project.remove_worktree(id, cx);
-                }
-            });
-        })
-        .log_err();
-
-    cx.run_until_parked();
-
-    cx.update_window(workspace_window.into(), |_, window, _cx| {
-        window.remove_window();
-    })
-    .log_err();
-
-    cx.run_until_parked();
-
-    for _ in 0..15 {
-        cx.advance_clock(Duration::from_millis(100));
-        cx.run_until_parked();
-    }
-
-    // Delete the preserved temp directory so visual-test runs don't
-    // accumulate filesystem artifacts.
-    if let Err(err) = std::fs::remove_dir_all(&temp_path) {
-        log::warn!(
-            "failed to clean up visual-test temp dir {}: {err}",
-            temp_path.display()
-        );
-    }
-
-    // Reset feature flags
-    cx.update(|cx| {
-        cx.update_flags(false, vec![]);
-    });
-
-    let results = [
-        ("default", result_default),
-        ("open_dropdown", result_open_dropdown),
-        ("new_worktree", result_new_worktree),
-        ("creating", result_creating),
-        ("error", result_error),
-        ("succeeded", result_succeeded),
-    ];
-
-    let mut has_baseline_update = None;
-    let mut failures = Vec::new();
-
-    for (name, result) in &results {
-        match result {
-            Ok(TestResult::Passed) => {}
-            Ok(TestResult::BaselineUpdated(p)) => {
-                has_baseline_update = Some(p.clone());
-            }
-            Err(e) => {
-                failures.push(format!("{}: {}", name, e));
-            }
-        }
-    }
-
-    if !failures.is_empty() {
-        Err(anyhow::anyhow!(
-            "start_thread_in_selector failures: {}",
-            failures.join("; ")
-        ))
-    } else if let Some(p) = has_baseline_update {
-        Ok(TestResult::BaselineUpdated(p))
-    } else {
-        Ok(TestResult::Passed)
-    }
-}