Fix worktree thread appearing under wrong sidebar group

Richard Feldman created

Change group_owns_worktree to use path containment instead of exact
path list match, so multi-project groups that contain the main repo
can claim linked worktree threads. Add cross-group dedup via
current_session_ids to ensure only the first matching group claims
the thread.

Change summary

crates/sidebar/src/project_group_builder.rs | 18 ++++++++----------
crates/sidebar/src/sidebar.rs               | 13 +++++++------
2 files changed, 15 insertions(+), 16 deletions(-)

Detailed changes

crates/sidebar/src/project_group_builder.rs 🔗

@@ -179,7 +179,11 @@ impl ProjectGroupBuilder {
     /// Whether the given group should load threads for a linked worktree at
     /// `worktree_path`. Returns `false` if the worktree already has an open
     /// workspace in the group (its threads are loaded via the workspace loop)
-    /// or if the worktree's canonical path list doesn't match `group_path_list`.
+    /// or if the worktree's canonical repo path isn't one of the group's roots.
+    ///
+    /// When multiple groups contain the same main repo, all of them will
+    /// return `true`. Callers must deduplicate across groups (e.g. using
+    /// `current_session_ids`) so that only the first group claims the thread.
     pub fn group_owns_worktree(
         &self,
         group: &ProjectGroup,
@@ -190,17 +194,11 @@ impl ProjectGroupBuilder {
         if group.covered_paths.contains(&worktree_arc) {
             return false;
         }
-        let canonical = self.canonicalize_path_list(&PathList::new(&[worktree_path]));
-        canonical == *group_path_list
-    }
-
-    fn canonicalize_path_list(&self, path_list: &PathList) -> PathList {
-        let paths: Vec<_> = path_list
+        let canonical = self.canonicalize_path(worktree_path);
+        group_path_list
             .paths()
             .iter()
-            .map(|p| self.canonicalize_path(p).to_path_buf())
-            .collect();
-        PathList::new(&paths)
+            .any(|p| Path::new(p) == canonical)
     }
 
     pub fn groups(&self) -> impl Iterator<Item = (&ProjectGroupName, &ProjectGroup)> {

crates/sidebar/src/sidebar.rs 🔗

@@ -770,6 +770,9 @@ impl Sidebar {
                         if !seen_session_ids.insert(row.session_id.clone()) {
                             continue;
                         }
+                        if current_session_ids.contains(&row.session_id) {
+                            continue;
+                        }
                         let worktree_info = row.folder_paths.paths().first().and_then(|path| {
                             let canonical = project_groups.canonicalize_path(path);
                             if canonical != path.as_path() {
@@ -7140,17 +7143,15 @@ mod tests {
         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.
+        // The thread should appear under [project, project-b] because that
+        // group was created first and contains the main repo /project.
         assert_eq!(
             visible_entries_as_strings(&sidebar, cx),
             vec![
                 "v [project, project-b]",
-                "  [+ New Thread]",
-                "v [project]",
                 "  Worktree Thread {wt-feature}",
+                "v [project]",
+                "  [+ New Thread]",
             ]
         );
     }