Add test reproducing AI-105 worktree thread misplacement bug

Richard Feldman created

Change summary

crates/sidebar/src/sidebar.rs | 94 +++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+)

Detailed changes

crates/sidebar/src/sidebar.rs 🔗

@@ -7061,6 +7061,100 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_worktree_thread_appears_under_creation_workspace(cx: &mut TestAppContext) {
+        // AI-105: When a multi-project workspace ([/project, /project-b]) has a
+        // linked worktree (/wt-feature), threads from that worktree should appear
+        // under the multi-project group, not under the individual [project] group.
+        //
+        // Current behavior (bug): the thread lands under [project] because
+        // group_owns_worktree matches the worktree's canonical path ([/project])
+        // against the single-project group rather than the multi-project group
+        // that the worktree was created from.
+        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("/project-b", serde_json::json!({ "src": {} }))
+            .await;
+
+        fs.insert_tree(
+            "/wt-feature",
+            serde_json::json!({
+                ".git": "gitdir: /project/.git/worktrees/feature",
+                "src": {},
+            }),
+        )
+        .await;
+
+        cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
+
+        fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
+            state.worktrees.push(git::repository::Worktree {
+                path: std::path::PathBuf::from("/wt-feature"),
+                ref_name: Some("refs/heads/feature".into()),
+                sha: "aaa".into(),
+            });
+        })
+        .unwrap();
+
+        let project_multi =
+            project::Project::test(fs.clone(), ["/project".as_ref(), "/project-b".as_ref()], cx)
+                .await;
+        project_multi
+            .update(cx, |p, cx| p.git_scans_complete(cx))
+            .await;
+
+        let project_individual =
+            project::Project::test(fs.clone(), ["/project".as_ref()], cx).await;
+        project_individual
+            .update(cx, |p, cx| p.git_scans_complete(cx))
+            .await;
+
+        let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
+            MultiWorkspace::test_new(project_multi.clone(), window, cx)
+        });
+        multi_workspace.update_in(cx, |mw, window, cx| {
+            mw.test_add_workspace(project_individual.clone(), window, cx);
+        });
+        let sidebar = setup_sidebar(&multi_workspace, cx);
+
+        let wt_paths = PathList::new(&[std::path::PathBuf::from("/wt-feature")]);
+        save_named_thread_metadata("wt-thread", "Worktree Thread", &wt_paths, cx).await;
+
+        multi_workspace.update_in(cx, |_, _window, cx| cx.notify());
+        cx.run_until_parked();
+
+        // BUG: The thread appears under [project] (the individual workspace)
+        // because group_owns_worktree canonicalizes /wt-feature to /project
+        // and matches it to the [project] group. It should instead appear
+        // under [project, project-b] since that workspace owns the repo.
+        assert_eq!(
+            visible_entries_as_strings(&sidebar, cx),
+            vec![
+                "v [project, project-b]",
+                "  [+ New Thread]",
+                "v [project]",
+                "  Worktree Thread {wt-feature}",
+            ]
+        );
+    }
+
     #[gpui::test]
     async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut TestAppContext) {
         // When a multi-root workspace (e.g. [/other, /project]) shares a