Replace 5 worktree tests with group-level vs individual-level tests

Nathan Sobo created

Remove tests that relied on old WorktreePathAdded/WorktreePathRemoved
events and sibling cascading behavior. Replace with 3 focused tests:

- test_group_level_folder_add_cascades_to_all_workspaces: verifies
  add_folders_to_project_group cascades to all group members
- test_individual_workspace_folder_change_moves_workspace_to_new_group:
  verifies individual changes move the workspace without cascading
- test_individual_workspace_change_merges_into_existing_group: verifies
  natural merge when a workspace's key matches an existing group

All 87 sidebar tests pass. All 160 workspace tests pass.

Change summary

crates/sidebar/src/sidebar_tests.rs | 775 +++---------------------------
1 file changed, 89 insertions(+), 686 deletions(-)

Detailed changes

crates/sidebar/src/sidebar_tests.rs 🔗

@@ -2639,338 +2639,86 @@ async fn test_new_thread_button_works_after_adding_folder(cx: &mut TestAppContex
 }
 
 #[gpui::test]
-async fn test_worktree_add_and_remove_migrates_threads(cx: &mut TestAppContext) {
-    // When a worktree is added to a project, the project group key changes
-    // and all historical threads should be migrated to the new key. Removing
-    // the worktree should migrate them back.
-    let (_fs, project) = init_multi_project_test(&["/project-a", "/project-b"], cx).await;
+async fn test_group_level_folder_add_cascades_to_all_workspaces(cx: &mut TestAppContext) {
+    // Group-level operations (add_folders_to_project_group) should cascade
+    // to all workspaces in the group and update the group key in-place.
+    // The group identity (ID) stays the same.
+    let (fs, project_a) = init_multi_project_test(&["/project-a", "/project-b"], cx).await;
     let (multi_workspace, cx) =
-        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
-    let sidebar = setup_sidebar(&multi_workspace, cx);
-
-    // Save two threads against the initial project group [/project-a].
-    save_n_test_threads(2, &project, cx).await;
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a]",
-            "  Thread 2",
-            "  Thread 1",
-        ]
-    );
-
-    // Verify the metadata store has threads under the old key.
-    let old_key_paths = PathList::new(&[PathBuf::from("/project-a")]);
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        assert_eq!(
-            store.entries_for_main_worktree_path(&old_key_paths).count(),
-            2,
-            "should have 2 threads under old key before add"
-        );
-    });
-
-    // Add a second worktree to the same project.
-    project
-        .update(cx, |project, cx| {
-            project.find_or_create_worktree("/project-b", true, cx)
-        })
-        .await
-        .expect("should add worktree");
-    cx.run_until_parked();
-
-    // The project group key should now be [/project-a, /project-b].
-    let new_key_paths = PathList::new(&[PathBuf::from("/project-a"), PathBuf::from("/project-b")]);
-
-    // Verify multi-workspace state: exactly one project group key, the new one.
-    multi_workspace.read_with(cx, |mw, _cx| {
-        let keys: Vec<_> = mw.project_group_keys().cloned().collect();
-        assert_eq!(
-            keys.len(),
-            1,
-            "should have exactly 1 project group key after add"
-        );
-        assert_eq!(
-            keys[0].path_list(),
-            &new_key_paths,
-            "the key should be the new combined path list"
-        );
-    });
-
-    // Verify threads were migrated to the new key.
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        assert_eq!(
-            store.entries_for_main_worktree_path(&old_key_paths).count(),
-            0,
-            "should have 0 threads under old key after migration"
-        );
-        assert_eq!(
-            store.entries_for_main_worktree_path(&new_key_paths).count(),
-            2,
-            "should have 2 threads under new key after migration"
-        );
-    });
-
-    // Sidebar should show threads under the new header.
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a, project-b]",
-            "  Thread 2",
-            "  Thread 1",
-        ]
-    );
+        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
+    let _sidebar = setup_sidebar(&multi_workspace, cx);
 
-    // Now remove the second worktree.
-    let worktree_id = project.read_with(cx, |project, cx| {
-        project
-            .visible_worktrees(cx)
-            .find(|wt| wt.read(cx).abs_path().as_ref() == Path::new("/project-b"))
-            .map(|wt| wt.read(cx).id())
-            .expect("should find project-b worktree")
-    });
-    project.update(cx, |project, cx| {
-        project.remove_worktree(worktree_id, cx);
+    // Create a second workspace with its own project in the same group.
+    let project_b =
+        project::Project::test(fs.clone() as Arc<dyn Fs>, ["/project-a".as_ref()], cx).await;
+    let _workspace_b = multi_workspace.update_in(cx, |mw, window, cx| {
+        mw.test_add_workspace(project_b.clone(), window, cx)
     });
     cx.run_until_parked();
 
-    // The key should revert to [/project-a].
-    multi_workspace.read_with(cx, |mw, _cx| {
-        let keys: Vec<_> = mw.project_group_keys().cloned().collect();
-        assert_eq!(
-            keys.len(),
-            1,
-            "should have exactly 1 project group key after remove"
-        );
-        assert_eq!(
-            keys[0].path_list(),
-            &old_key_paths,
-            "the key should revert to the original path list"
-        );
-    });
-
-    // Threads should be migrated back to the old key.
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        assert_eq!(
-            store.entries_for_main_worktree_path(&new_key_paths).count(),
-            0,
-            "should have 0 threads under new key after revert"
-        );
-        assert_eq!(
-            store.entries_for_main_worktree_path(&old_key_paths).count(),
-            2,
-            "should have 2 threads under old key after revert"
-        );
+    // Both workspaces should be in one group with key [/project-a].
+    let group_id = multi_workspace.read_with(cx, |mw, _| {
+        assert_eq!(mw.project_groups().len(), 1);
+        assert_eq!(mw.project_groups()[0].workspaces.len(), 2);
+        mw.project_groups()[0].id
     });
 
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a]",
-            "  Thread 2",
-            "  Thread 1",
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_worktree_add_and_remove_preserves_thread_path_associations(cx: &mut TestAppContext) {
-    // Verifies that adding/removing folders to a project correctly updates
-    // each thread's worktree_paths (both folder_paths and main_worktree_paths)
-    // while preserving per-path associations for linked worktrees.
-    init_test(cx);
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/project",
-        serde_json::json!({
-            ".git": {},
-            "src": {},
-        }),
-    )
-    .await;
-    fs.add_linked_worktree_for_repo(
-        Path::new("/project/.git"),
-        false,
-        git::repository::Worktree {
-            path: PathBuf::from("/wt-feature"),
-            ref_name: Some("refs/heads/feature".into()),
-            sha: "aaa".into(),
-            is_main: false,
-        },
-    )
-    .await;
-    fs.insert_tree("/other-project", serde_json::json!({ ".git": {} }))
-        .await;
-    cx.update(|cx| <dyn Fs>::set_global(fs.clone(), cx));
-
-    // Start with a linked worktree workspace: visible root is /wt-feature,
-    // main repo is /project.
-    let project =
-        project::Project::test(fs.clone() as Arc<dyn Fs>, ["/wt-feature".as_ref()], cx).await;
-    let (multi_workspace, cx) =
-        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
-    let _sidebar = setup_sidebar(&multi_workspace, cx);
-
-    // Save a thread. It should have folder_paths=[/wt-feature], main=[/project].
-    save_named_thread_metadata("thread-1", "Thread 1", &project, cx).await;
-
-    let session_id = acp::SessionId::new(Arc::from("thread-1"));
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        let thread = store.entry(&session_id).expect("thread should exist");
-        assert_eq!(
-            thread.folder_paths().paths(),
-            &[PathBuf::from("/wt-feature")],
-            "initial folder_paths should be the linked worktree"
-        );
-        assert_eq!(
-            thread.main_worktree_paths().paths(),
-            &[PathBuf::from("/project")],
-            "initial main_worktree_paths should be the main repo"
-        );
+    // Add /project-b via the group-level API.
+    multi_workspace.update(cx, |mw, cx| {
+        mw.add_folders_to_project_group(group_id, vec![PathBuf::from("/project-b")], cx);
     });
-
-    // Add /other-project to the workspace.
-    project
-        .update(cx, |project, cx| {
-            project.find_or_create_worktree("/other-project", true, cx)
-        })
-        .await
-        .expect("should add worktree");
     cx.run_until_parked();
 
-    // Thread should now have both paths, with correct associations.
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        let thread = store.entry(&session_id).expect("thread should exist");
-        let pairs: Vec<_> = thread
-            .worktree_paths
-            .ordered_pairs()
-            .map(|(m, f)| (m.clone(), f.clone()))
-            .collect();
-        assert!(
-            pairs.contains(&(PathBuf::from("/project"), PathBuf::from("/wt-feature"))),
-            "linked worktree association should be preserved, got: {:?}",
-            pairs
-        );
+    // The group key should be updated.
+    multi_workspace.read_with(cx, |mw, _| {
+        assert_eq!(mw.project_groups().len(), 1, "still one group");
+        assert_eq!(mw.project_groups()[0].id, group_id, "same group ID");
+        let paths = mw.project_groups()[0].key.path_list().paths().to_vec();
         assert!(
-            pairs.contains(&(
-                PathBuf::from("/other-project"),
-                PathBuf::from("/other-project")
-            )),
-            "new folder should have main == folder, got: {:?}",
-            pairs
+            paths.contains(&PathBuf::from("/project-a"))
+                && paths.contains(&PathBuf::from("/project-b")),
+            "group key should contain both paths, got {:?}",
+            paths,
         );
     });
 
-    // Remove /other-project.
-    let worktree_id = project.read_with(cx, |project, cx| {
-        project
-            .visible_worktrees(cx)
-            .find(|wt| wt.read(cx).abs_path().as_ref() == Path::new("/other-project"))
-            .map(|wt| wt.read(cx).id())
-            .expect("should find other-project worktree")
-    });
-    project.update(cx, |project, cx| {
-        project.remove_worktree(worktree_id, cx);
-    });
-    cx.run_until_parked();
-
-    // Thread should be back to original state.
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        let thread = store.entry(&session_id).expect("thread should exist");
-        assert_eq!(
-            thread.folder_paths().paths(),
-            &[PathBuf::from("/wt-feature")],
-            "folder_paths should revert to just the linked worktree"
-        );
-        assert_eq!(
-            thread.main_worktree_paths().paths(),
-            &[PathBuf::from("/project")],
-            "main_worktree_paths should revert to just the main repo"
-        );
-        let pairs: Vec<_> = thread
-            .worktree_paths
-            .ordered_pairs()
-            .map(|(m, f)| (m.clone(), f.clone()))
-            .collect();
-        assert_eq!(
-            pairs,
-            vec![(PathBuf::from("/project"), PathBuf::from("/wt-feature"))],
-            "linked worktree association should be preserved through add+remove cycle"
-        );
-    });
+    // Both workspaces should have gotten the new worktree.
+    let worktree_count_a = project_a.read_with(cx, |p, cx| p.visible_worktrees(cx).count());
+    let worktree_count_b = project_b.read_with(cx, |p, cx| p.visible_worktrees(cx).count());
+    assert_eq!(worktree_count_a, 2, "project A should have 2 worktrees");
+    assert_eq!(worktree_count_b, 2, "project B should have 2 worktrees");
 }
 
 #[gpui::test]
-async fn test_worktree_add_key_collision_removes_duplicate_workspace(cx: &mut TestAppContext) {
-    // When a worktree is added to workspace A and the resulting key matches
-    // an existing workspace B's key (and B has the same root paths), B
-    // should be removed as a true duplicate.
+async fn test_individual_workspace_folder_change_moves_workspace_to_new_group(
+    cx: &mut TestAppContext,
+) {
+    // When a folder is added to an individual workspace (not via the group
+    // API), that workspace should move to a new or existing group matching
+    // its new key. Sibling workspaces in the old group are NOT affected.
     let (fs, project_a) = init_multi_project_test(&["/project-a", "/project-b"], cx).await;
     let (multi_workspace, cx) =
         cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
-    let sidebar = setup_sidebar(&multi_workspace, cx);
-
-    let workspace_a = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
-
-    // Save a thread against workspace A [/project-a].
-    save_named_thread_metadata("thread-a", "Thread A", &project_a, cx).await;
+    let _sidebar = setup_sidebar(&multi_workspace, cx);
 
-    // Create workspace B with both worktrees [/project-a, /project-b].
-    let project_b = project::Project::test(
-        fs.clone() as Arc<dyn Fs>,
-        ["/project-a".as_ref(), "/project-b".as_ref()],
-        cx,
-    )
-    .await;
-    let workspace_b = multi_workspace.update_in(cx, |mw, window, cx| {
+    // Create a second workspace that shares the same group.
+    let project_b =
+        project::Project::test(fs.clone() as Arc<dyn Fs>, ["/project-a".as_ref()], cx).await;
+    let _workspace_b = multi_workspace.update_in(cx, |mw, window, cx| {
         mw.test_add_workspace(project_b.clone(), window, cx)
     });
     cx.run_until_parked();
 
-    // Switch back to workspace A so it's the active workspace when the collision happens.
-    multi_workspace.update_in(cx, |mw, window, cx| {
-        mw.activate(workspace_a, window, cx);
+    multi_workspace.read_with(cx, |mw, _| {
+        assert_eq!(mw.project_groups().len(), 1, "one group to start");
+        assert_eq!(
+            mw.project_groups()[0].workspaces.len(),
+            2,
+            "two workspaces in it"
+        );
     });
-    cx.run_until_parked();
-
-    // Save a thread against workspace B [/project-a, /project-b].
-    save_named_thread_metadata("thread-b", "Thread B", &project_b, cx).await;
-
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    // Both project groups should be visible.
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a, project-b]",
-            "  Thread B",
-            "v [project-a]",
-            "  Thread A",
-        ]
-    );
-
-    let workspace_b_id = workspace_b.entity_id();
 
-    // Now add /project-b to workspace A's project, causing a key collision.
+    // Add /project-b to project_a directly (individual workspace change).
     project_a
         .update(cx, |project, cx| {
             project.find_or_create_worktree("/project-b", true, cx)
@@ -2979,181 +2727,58 @@ async fn test_worktree_add_key_collision_removes_duplicate_workspace(cx: &mut Te
         .expect("should add worktree");
     cx.run_until_parked();
 
-    // Workspace B should have been removed (true duplicate — same root paths).
-    multi_workspace.read_with(cx, |mw, _cx| {
-        let workspace_ids: Vec<_> = mw.workspaces().map(|ws| ws.entity_id()).collect();
-        assert!(
-            !workspace_ids.contains(&workspace_b_id),
-            "workspace B should have been removed after key collision"
-        );
-    });
-
-    // There should be exactly one project group key now.
-    let combined_paths = PathList::new(&[PathBuf::from("/project-a"), PathBuf::from("/project-b")]);
-    multi_workspace.read_with(cx, |mw, _cx| {
-        let keys: Vec<_> = mw.project_group_keys().cloned().collect();
-        assert_eq!(
-            keys.len(),
-            1,
-            "should have exactly 1 project group key after collision"
-        );
+    // project_a's workspace should have moved to a new group.
+    // project_b's workspace should stay in the old group, unchanged.
+    multi_workspace.read_with(cx, |mw, _| {
+        assert_eq!(mw.project_groups().len(), 2, "should now have 2 groups");
+        let mut group_sizes: Vec<usize> = mw
+            .project_groups()
+            .iter()
+            .map(|g| g.workspaces.len())
+            .collect();
+        group_sizes.sort();
         assert_eq!(
-            keys[0].path_list(),
-            &combined_paths,
-            "the remaining key should be the combined paths"
+            group_sizes,
+            vec![1, 1],
+            "each group should have 1 workspace"
         );
     });
 
-    // Both threads should be visible under the merged group.
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
+    // project_b should still have only 1 worktree (no cascading).
+    let worktree_count_b = project_b.read_with(cx, |p, cx| p.visible_worktrees(cx).count());
     assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a, project-b]",
-            "  Thread A",
-            "  Thread B",
-        ]
+        worktree_count_b, 1,
+        "sibling workspace should NOT get the new folder"
     );
 }
 
 #[gpui::test]
-async fn test_worktree_collision_keeps_active_workspace(cx: &mut TestAppContext) {
-    // When workspace A adds a folder that makes it collide with workspace B,
-    // and B is the *active* workspace, A (the incoming one) should be
-    // dropped so the user stays on B. A linked worktree sibling of A
-    // should migrate into B's group.
-    init_test(cx);
-    let fs = FakeFs::new(cx.executor());
-
-    // Set up /project-a with a linked worktree.
-    fs.insert_tree(
-        "/project-a",
-        serde_json::json!({
-            ".git": {
-                "worktrees": {
-                    "feature": {
-                        "commondir": "../../",
-                        "HEAD": "ref: refs/heads/feature",
-                    },
-                },
-            },
-            "src": {},
-        }),
-    )
-    .await;
-    fs.insert_tree(
-        "/wt-feature",
-        serde_json::json!({
-            ".git": "gitdir: /project-a/.git/worktrees/feature",
-            "src": {},
-        }),
-    )
-    .await;
-    fs.add_linked_worktree_for_repo(
-        Path::new("/project-a/.git"),
-        false,
-        git::repository::Worktree {
-            path: PathBuf::from("/wt-feature"),
-            ref_name: Some("refs/heads/feature".into()),
-            sha: "aaa".into(),
-            is_main: false,
-        },
-    )
-    .await;
-    fs.insert_tree("/project-b", serde_json::json!({ ".git": {}, "src": {} }))
-        .await;
-    cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
-
-    let project_a = project::Project::test(fs.clone(), ["/project-a".as_ref()], cx).await;
-    project_a.update(cx, |p, cx| p.git_scans_complete(cx)).await;
-
-    // Linked worktree sibling of A.
-    let project_wt = project::Project::test(fs.clone(), ["/wt-feature".as_ref()], cx).await;
-    project_wt
-        .update(cx, |p, cx| p.git_scans_complete(cx))
-        .await;
+async fn test_individual_workspace_change_merges_into_existing_group(cx: &mut TestAppContext) {
+    // When an individual workspace's key changes to match an existing group,
+    // it should move into that group (natural merge).
+    let (fs, project_a) = init_multi_project_test(&["/project-a", "/project-b"], cx).await;
+    let (multi_workspace, cx) =
+        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
+    let _sidebar = setup_sidebar(&multi_workspace, cx);
 
-    // Workspace B has both folders already.
+    // Create workspace B with both folders [/project-a, /project-b].
     let project_b = project::Project::test(
         fs.clone() as Arc<dyn Fs>,
         ["/project-a".as_ref(), "/project-b".as_ref()],
         cx,
     )
     .await;
-
-    let (multi_workspace, cx) =
-        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
-    let sidebar = setup_sidebar(&multi_workspace, cx);
-
-    // Add agent panels to all workspaces.
-    let workspace_a_entity = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
-    add_agent_panel(&workspace_a_entity, cx);
-
-    // Add the linked worktree workspace (sibling of A).
-    let workspace_wt = multi_workspace.update_in(cx, |mw, window, cx| {
-        mw.test_add_workspace(project_wt.clone(), window, cx)
-    });
-    add_agent_panel(&workspace_wt, cx);
-    cx.run_until_parked();
-
-    // Add workspace B (will become active).
-    let workspace_b = multi_workspace.update_in(cx, |mw, window, cx| {
+    let _workspace_b = multi_workspace.update_in(cx, |mw, window, cx| {
         mw.test_add_workspace(project_b.clone(), window, cx)
     });
-    add_agent_panel(&workspace_b, cx);
     cx.run_until_parked();
 
-    // Save threads in each group.
-    save_named_thread_metadata("thread-a", "Thread A", &project_a, cx).await;
-    save_thread_metadata_with_main_paths(
-        "thread-wt",
-        "Worktree Thread",
-        PathList::new(&[PathBuf::from("/wt-feature")]),
-        PathList::new(&[PathBuf::from("/project-a")]),
-        cx,
-    );
-    save_named_thread_metadata("thread-b", "Thread B", &project_b, cx).await;
-
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    // B is active, A and wt-feature are in one group, B in another.
-    assert_eq!(
-        multi_workspace.read_with(cx, |mw, _| mw.workspace().entity_id()),
-        workspace_b.entity_id(),
-        "workspace B should be active"
-    );
-    multi_workspace.read_with(cx, |mw, _cx| {
-        assert_eq!(mw.project_group_keys().count(), 2, "should have 2 groups");
-        assert_eq!(mw.workspaces().count(), 3, "should have 3 workspaces");
+    // Should have 2 groups: one [/project-a], one [/project-a, /project-b].
+    multi_workspace.read_with(cx, |mw, _| {
+        assert_eq!(mw.project_groups().len(), 2);
     });
 
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a, project-b]",
-            "  Thread B",
-            "v [project-a]",
-            "  Thread A",
-            "  Worktree Thread {wt-feature}",
-        ]
-    );
-
-    let workspace_a = multi_workspace.read_with(cx, |mw, _| {
-        mw.workspaces()
-            .find(|ws| {
-                ws.entity_id() != workspace_b.entity_id()
-                    && ws.entity_id() != workspace_wt.entity_id()
-            })
-            .unwrap()
-            .clone()
-    });
-
-    // Add /project-b to workspace A's project, causing a collision with B.
+    // Add /project-b to project_a directly. Its key now matches project_b's group.
     project_a
         .update(cx, |project, cx| {
             project.find_or_create_worktree("/project-b", true, cx)
@@ -3162,241 +2787,19 @@ async fn test_worktree_collision_keeps_active_workspace(cx: &mut TestAppContext)
         .expect("should add worktree");
     cx.run_until_parked();
 
-    // Workspace A (the incoming duplicate) should have been dropped.
-    multi_workspace.read_with(cx, |mw, _cx| {
-        let workspace_ids: Vec<_> = mw.workspaces().map(|ws| ws.entity_id()).collect();
-        assert!(
-            !workspace_ids.contains(&workspace_a.entity_id()),
-            "workspace A should have been dropped"
-        );
-    });
-
-    // The active workspace should still be B.
-    assert_eq!(
-        multi_workspace.read_with(cx, |mw, _| mw.workspace().entity_id()),
-        workspace_b.entity_id(),
-        "workspace B should still be active"
-    );
-
-    // The linked worktree sibling should have migrated into B's group
-    // (it got the folder add and now shares the same key).
-    multi_workspace.read_with(cx, |mw, _cx| {
-        let workspace_ids: Vec<_> = mw.workspaces().map(|ws| ws.entity_id()).collect();
-        assert!(
-            workspace_ids.contains(&workspace_wt.entity_id()),
-            "linked worktree workspace should still exist"
-        );
+    // Both workspaces should now be in one group.
+    multi_workspace.read_with(cx, |mw, _| {
         assert_eq!(
-            mw.project_group_keys().count(),
+            mw.project_groups().len(),
             1,
-            "should have 1 group after merge"
+            "should have merged into 1 group"
         );
         assert_eq!(
-            mw.workspaces().count(),
+            mw.project_groups()[0].workspaces.len(),
             2,
-            "should have 2 workspaces (B + linked worktree)"
+            "both workspaces in the merged group"
         );
     });
-
-    // The linked worktree workspace should have gotten the new folder.
-    let wt_worktree_count =
-        project_wt.read_with(cx, |project, cx| project.visible_worktrees(cx).count());
-    assert_eq!(
-        wt_worktree_count, 2,
-        "linked worktree project should have gotten /project-b"
-    );
-
-    // After: everything merged under one group. Thread A migrated,
-    // worktree thread shows its chip, B's thread and draft remain.
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project-a, project-b]",
-            "  Thread A",
-            "  Worktree Thread {project-a:wt-feature}",
-            "  Thread B",
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_worktree_add_syncs_linked_worktree_sibling(cx: &mut TestAppContext) {
-    // When a worktree is added to the main workspace, a linked worktree
-    // sibling (different root paths, same project group key) should also
-    // get the new folder added to its project.
-    init_test(cx);
-    let fs = FakeFs::new(cx.executor());
-
-    fs.insert_tree(
-        "/project",
-        serde_json::json!({
-            ".git": {
-                "worktrees": {
-                    "feature": {
-                        "commondir": "../../",
-                        "HEAD": "ref: refs/heads/feature",
-                    },
-                },
-            },
-            "src": {},
-        }),
-    )
-    .await;
-
-    fs.insert_tree(
-        "/wt-feature",
-        serde_json::json!({
-            ".git": "gitdir: /project/.git/worktrees/feature",
-            "src": {},
-        }),
-    )
-    .await;
-
-    fs.add_linked_worktree_for_repo(
-        Path::new("/project/.git"),
-        false,
-        git::repository::Worktree {
-            path: PathBuf::from("/wt-feature"),
-            ref_name: Some("refs/heads/feature".into()),
-            sha: "aaa".into(),
-            is_main: false,
-        },
-    )
-    .await;
-
-    // Create a second independent project to add as a folder later.
-    fs.insert_tree(
-        "/other-project",
-        serde_json::json!({ ".git": {}, "src": {} }),
-    )
-    .await;
-
-    cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
-
-    let main_project = project::Project::test(fs.clone(), ["/project".as_ref()], cx).await;
-    let worktree_project = project::Project::test(fs.clone(), ["/wt-feature".as_ref()], cx).await;
-
-    main_project
-        .update(cx, |p, cx| p.git_scans_complete(cx))
-        .await;
-    worktree_project
-        .update(cx, |p, cx| p.git_scans_complete(cx))
-        .await;
-
-    let (multi_workspace, cx) =
-        cx.add_window_view(|window, cx| MultiWorkspace::test_new(main_project.clone(), window, cx));
-    let sidebar = setup_sidebar(&multi_workspace, cx);
-
-    // Add agent panel to the main workspace.
-    let main_workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
-    add_agent_panel(&main_workspace, cx);
-
-    // Open the linked worktree as a separate workspace.
-    let wt_workspace = multi_workspace.update_in(cx, |mw, window, cx| {
-        mw.test_add_workspace(worktree_project.clone(), window, cx)
-    });
-    add_agent_panel(&wt_workspace, cx);
-    cx.run_until_parked();
-
-    // Both workspaces should share the same project group key [/project].
-    multi_workspace.read_with(cx, |mw, _cx| {
-        assert_eq!(
-            mw.project_group_keys().count(),
-            1,
-            "should have 1 project group key before add"
-        );
-        assert_eq!(mw.workspaces().count(), 2, "should have 2 workspaces");
-    });
-
-    // Save threads against each workspace.
-    save_named_thread_metadata("main-thread", "Main Thread", &main_project, cx).await;
-    save_named_thread_metadata("wt-thread", "Worktree Thread", &worktree_project, cx).await;
-
-    // Verify both threads are under the old key [/project].
-    let old_key_paths = PathList::new(&[PathBuf::from("/project")]);
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        assert_eq!(
-            store.entries_for_main_worktree_path(&old_key_paths).count(),
-            2,
-            "should have 2 threads under old key before add"
-        );
-    });
-
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project]",
-            "  Worktree Thread {wt-feature}",
-            "  Main Thread",
-        ]
-    );
-
-    // Add /other-project as a folder to the main workspace.
-    main_project
-        .update(cx, |project, cx| {
-            project.find_or_create_worktree("/other-project", true, cx)
-        })
-        .await
-        .expect("should add worktree");
-    cx.run_until_parked();
-
-    // The linked worktree workspace should have gotten the new folder too.
-    let wt_worktree_count =
-        worktree_project.read_with(cx, |project, cx| project.visible_worktrees(cx).count());
-    assert_eq!(
-        wt_worktree_count, 2,
-        "linked worktree project should have gotten the new folder"
-    );
-
-    // Both workspaces should still exist under one key.
-    multi_workspace.read_with(cx, |mw, _cx| {
-        assert_eq!(mw.workspaces().count(), 2, "both workspaces should survive");
-        assert_eq!(
-            mw.project_group_keys().count(),
-            1,
-            "should still have 1 project group key"
-        );
-    });
-
-    // Threads should have been migrated to the new key.
-    let new_key_paths =
-        PathList::new(&[PathBuf::from("/other-project"), PathBuf::from("/project")]);
-    cx.update(|_window, cx| {
-        let store = ThreadMetadataStore::global(cx).read(cx);
-        assert_eq!(
-            store.entries_for_main_worktree_path(&old_key_paths).count(),
-            0,
-            "should have 0 threads under old key after migration"
-        );
-        assert_eq!(
-            store.entries_for_main_worktree_path(&new_key_paths).count(),
-            2,
-            "should have 2 threads under new key after migration"
-        );
-    });
-
-    // Both threads should still be visible in the sidebar.
-    sidebar.update_in(cx, |sidebar, _window, cx| sidebar.update_entries(cx));
-    cx.run_until_parked();
-
-    assert_eq!(
-        visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [other-project, project]",
-            "  Worktree Thread {project:wt-feature}",
-            "  Main Thread",
-        ]
-    );
 }
 
 #[gpui::test]