fix: dedupe shared worktree ownership

Cherry-picked from claude branch 7b3bec48 (fix: dedupe shared worktree ownership and title sessions immediately).

Only the worktree-dedupe half applies to main: partitionWorktreesByRegisteredProject and its callers (SessionSidebar, ElectronMiniChatApp, MobileApp, MobileSessionsSheet) plus tests and sidebar docs.

The session-title half (title sessions immediately) remains on the claude branch: the session-title runtime and its claude-code transcript translator imports do not exist on main and cannot build there until the Claude harness lands.
This commit is contained in:
Serhii Dziupin
2026-08-02 09:34:49 +03:00
parent 37ff3a8164
commit 5d24d6cb2a
7 changed files with 199 additions and 22 deletions
@@ -47,7 +47,11 @@ import { SessionNodeItem } from './sidebar/SessionNodeItem';
import type { SessionNodeRenderExtras } from './sidebar/sessionNodeItemUtils';
import { useUpdateStore } from '@/stores/useUpdateStore';
import { useShallow } from 'zustand/react/shallow';
import { listProjectWorktrees, worktreeMapsEqual } from '@/lib/worktrees/worktreeManager';
import {
listProjectWorktrees,
partitionWorktreesByRegisteredProject,
worktreeMapsEqual,
} from '@/lib/worktrees/worktreeManager';
import { checkIsGitRepository } from '@/lib/gitApi';
import type { WorktreeMetadata } from '@/types/worktree';
import type { SortableDragHandleProps } from './sidebar/sortableItems';
@@ -564,8 +568,8 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
return;
}
const currentByProject = useSessionUIStore.getState().availableWorktreesByProject;
const worktreesByProject = new Map(currentByProject);
const knownWorktreesByProject = useSessionUIStore.getState().availableWorktreesByProject;
const worktreesByProject = new Map(knownWorktreesByProject);
const unresolvedProjectPaths = new Set<string>();
// Constrain fanout: previously `Promise.all(projects.map(...))` could
@@ -618,16 +622,17 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
worktreesByProject.delete(projectPath);
}
}
const allWorktrees = [...worktreesByProject.values()].flat();
const partitionedWorktreesByProject = partitionWorktreesByRegisteredProject(projectEntries, worktreesByProject);
const allWorktrees = [...partitionedWorktreesByProject.values()].flat();
// Newly appearing worktrees sort to the top of their project's
// worktree list (see worktreeFirstSeen.ts).
recordWorktreesSeen(allWorktrees.map((worktree) => worktree.path), Date.now());
// Skip update if nothing changed — see worktreeMapsEqual JSDoc.
if (!worktreeMapsEqual(worktreesByProject, currentByProject)) {
if (!worktreeMapsEqual(partitionedWorktreesByProject, knownWorktreesByProject)) {
useSessionUIStore.setState({
availableWorktrees: allWorktrees,
availableWorktreesByProject: worktreesByProject,
availableWorktreesByProject: partitionedWorktreesByProject,
});
}
setUnresolvedWorktreeProjectPaths(unresolvedProjectPaths);
@@ -13,6 +13,7 @@
- Group-level PR-status polling/indicators and worktree-group drag-to-reorder were removed together with the worktree grouping level; `oc.sessions.groupOrder` is no longer read or written. Worktree PR/branch context lives in the Worktrees surface.
- Root session menus can quickly create a worktree from the session directory's current branch and move the full session subtree there while idle.
- Directory loading is demand-driven: the sidebar publishes one complete priority plan for all known project/worktree directories, while the sync layer owns bounded execution.
- When multiple configured projects are checkouts of the same Git repository, exactly one project owns the shared worktree topology: the configured canonical primary root when present, otherwise the first configured source for that repository. Any worktree path that is also a configured project is omitted from subordinate worktree groups, so every directory has one sidebar location while remaining part of bootstrap demand.
## VS Code grouping