Skip to content

Commit a72ab9c

Browse files
committed
Keep registered worktree projects separate
1 parent 60223a4 commit a72ab9c

3 files changed

Lines changed: 16 additions & 54 deletions

File tree

src/composables/useDesktopState.test.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('filterGroupsByWorkspaceRoots', () => {
162162
])
163163
})
164164

165-
it('keeps managed Codex worktrees under the main root even when a worktree root is registered', () => {
165+
it('keeps unregistered managed worktrees under the main root when another managed worktree root is registered', () => {
166166
const groups: UiProjectGroup[] = [
167167
{
168168
projectName: 'codex-web-local',
@@ -186,28 +186,8 @@ describe('filterGroupsByWorkspaceRoots', () => {
186186
}
187187

188188
expect(filterGroupsByWorkspaceRoots(groups, rootsState).map((group) => [group.projectName, group.threads.map((row) => row.id)])).toEqual([
189-
['/Users/igor/Git-projects/codex-web-local', ['main-chat', 'registered-worktree-chat', 'unregistered-worktree-chat']],
190-
])
191-
})
192-
193-
it('keeps a managed Codex worktree root visible when no canonical root exists', () => {
194-
const groups: UiProjectGroup[] = [
195-
{
196-
projectName: 'codex-web-local',
197-
threads: [
198-
thread('worktree-chat', '/Users/igor/.codex/worktrees/a77f/codex-web-local', { hasWorktree: true }),
199-
],
200-
},
201-
]
202-
const rootsState: WorkspaceRootsState = {
203-
order: ['/Users/igor/.codex/worktrees/a77f/codex-web-local'],
204-
labels: {},
205-
active: ['/Users/igor/.codex/worktrees/a77f/codex-web-local'],
206-
projectOrder: ['/Users/igor/.codex/worktrees/a77f/codex-web-local'],
207-
}
208-
209-
expect(filterGroupsByWorkspaceRoots(groups, rootsState).map((group) => [group.projectName, group.threads.map((row) => row.id)])).toEqual([
210-
['codex-web-local', ['worktree-chat']],
189+
['/Users/igor/Git-projects/codex-web-local', ['main-chat', 'unregistered-worktree-chat']],
190+
['/Users/igor/.codex/worktrees/a77f/codex-web-local', ['registered-worktree-chat']],
211191
])
212192
})
213193

src/composables/useDesktopState.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -998,35 +998,17 @@ function getWorkspaceProjectOrderPaths(rootsState: WorkspaceRootsState | null):
998998
return orderedRoots
999999
}
10001000

1001-
function hasCanonicalWorkspaceRootForManagedWorktree(rootPath: string, rootsState: WorkspaceRootsState | null): boolean {
1002-
const normalizedRootPath = normalizePathForUi(rootPath).trim()
1003-
if (!isManagedCodexWorktreePath(normalizedRootPath)) return false
1004-
const leafName = toProjectNameFromWorkspaceRoot(normalizedRootPath)
1005-
return (rootsState?.order ?? []).some((candidate) => {
1006-
const normalizedCandidate = normalizePathForUi(candidate).trim()
1007-
return normalizedCandidate.length > 0
1008-
&& !isManagedCodexWorktreePath(normalizedCandidate)
1009-
&& toProjectNameFromWorkspaceRoot(normalizedCandidate) === leafName
1010-
})
1011-
}
1012-
10131001
function getWorkspaceProjectOrderNames(
10141002
rootsState: WorkspaceRootsState | null,
10151003
duplicateLeafNames: Set<string>,
10161004
): string[] {
10171005
const remoteProjectsById = getRemoteProjectById(rootsState)
1018-
const names: string[] = []
1019-
for (const rootPath of getWorkspaceProjectOrderPaths(rootsState)) {
1020-
if (remoteProjectsById.has(rootPath)) {
1021-
names.push(rootPath)
1022-
continue
1023-
}
1024-
if (hasCanonicalWorkspaceRootForManagedWorktree(rootPath, rootsState)) continue
1006+
return getWorkspaceProjectOrderPaths(rootsState).map((rootPath) => {
1007+
if (remoteProjectsById.has(rootPath)) return rootPath
10251008
const normalizedRootPath = normalizePathForUi(rootPath).trim()
10261009
const leafName = toProjectNameFromWorkspaceRoot(normalizedRootPath)
1027-
names.push(duplicateLeafNames.has(leafName) ? normalizedRootPath : leafName)
1028-
}
1029-
return names
1010+
return duplicateLeafNames.has(leafName) ? normalizedRootPath : leafName
1011+
})
10301012
}
10311013

10321014
function matchesWorkspaceRootProject(rootPath: string, projectName: string): boolean {
@@ -1185,9 +1167,11 @@ function disambiguateProjectGroupsByCwd(
11851167
const uniqueCanonicalWorkspaceRootLeafNames = new Set<string>()
11861168
const duplicateCanonicalWorkspaceRootLeafNames = new Set<string>()
11871169
const canonicalWorkspaceRootByLeafName = new Map<string, string>()
1170+
const registeredWorkspaceRoots = new Set<string>()
11881171
for (const rootPath of rootsState?.order ?? []) {
11891172
const normalizedRootPath = normalizePathForUi(rootPath).trim()
11901173
if (!normalizedRootPath) continue
1174+
registeredWorkspaceRoots.add(normalizedRootPath)
11911175
if (isManagedCodexWorktreePath(normalizedRootPath)) continue
11921176
const leafName = toProjectName(normalizedRootPath)
11931177
if (uniqueCanonicalWorkspaceRootLeafNames.has(leafName)) {
@@ -1206,7 +1190,9 @@ function disambiguateProjectGroupsByCwd(
12061190
for (const thread of group.threads) {
12071191
const normalizedCwd = normalizePathForUi(thread.cwd).trim()
12081192
const leafName = toProjectName(normalizedCwd)
1193+
const isRegisteredRoot = registeredWorkspaceRoots.has(normalizedCwd)
12091194
const isCanonicalWorktreeThread = isManagedCodexWorktreePath(normalizedCwd)
1195+
&& !isRegisteredRoot
12101196
&& uniqueCanonicalWorkspaceRootLeafNames.has(leafName)
12111197
let projectName = group.projectName
12121198
if (isCanonicalWorktreeThread && duplicateLeafNames.has(leafName)) {
@@ -1248,7 +1234,6 @@ function addWorkspaceRootPlaceholderGroups(
12481234
}
12491235
const normalizedRootPath = normalizePathForUi(rootPath).trim()
12501236
if (!normalizedRootPath) continue
1251-
if (hasCanonicalWorkspaceRootForManagedWorktree(normalizedRootPath, rootsState)) continue
12521237
const leafName = toProjectNameFromWorkspaceRoot(normalizedRootPath)
12531238
const projectName = duplicateLeafNames.has(leafName) ? normalizedRootPath : leafName
12541239
if (existingProjectNames.has(projectName)) continue

tests.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,30 +4149,27 @@ Composer runtime options and project menu worktree actions are hidden when the s
41494149
### Project worktree threads under canonical project
41504150

41514151
#### Feature/Change Name
4152-
Managed worktree threads remain visible under their matching canonical workspace-root project, and registered managed worktree roots do not appear as separate empty projects when the canonical root exists.
4152+
Managed worktree threads remain visible under their matching canonical workspace-root project, and path-like project tooltips expose the full path.
41534153

41544154
#### Prerequisites/Setup
41554155
1. Dev server running (`pnpm run dev`)
41564156
2. Codex global workspace roots include `/Users/igor/Git-projects/codex-web-local`
41574157
3. Thread history contains at least one thread whose cwd is under `/Users/igor/.codex/worktrees/*/codex-web-local`
4158-
4. Optional: Codex global workspace roots include a registered worktree root such as `/Users/igor/.codex/worktrees/a77f/codex-web-local`
4159-
5. Light theme and dark theme both available from the appearance switcher
4158+
4. Light theme and dark theme both available from the appearance switcher
41604159

41614160
#### Steps
41624161
1. In light theme, open the sidebar Projects section.
41634162
2. Scroll to the `codex-web-local` project.
41644163
3. Confirm the project includes the main-root thread and managed worktree threads.
41654164
4. Confirm worktree rows still show the worktree icon.
4166-
5. Confirm a registered managed worktree root with the same leaf folder name does not appear as a separate empty project such as `codex-web-local2 a77f`.
4167-
6. Confirm unrelated `.git/worktrees` rows with the same leaf folder name are not grouped into this project.
4168-
7. Hover any shortened path-like duplicate project title and confirm the tooltip shows the full project path, not only the friendly label.
4169-
8. Switch to dark theme and repeat steps 1-7.
4165+
5. Confirm unrelated `.git/worktrees` rows with the same leaf folder name are not grouped into this project.
4166+
6. Hover any shortened path-like duplicate project title and confirm the tooltip shows the full project path, not only the friendly label.
4167+
7. Switch to dark theme and repeat steps 1-6.
41704168

41714169
#### Expected Results
41724170
- Managed worktree threads with the same leaf folder name are not split into hidden path-like project groups.
41734171
- Generic `.git/worktrees` rows are not treated as managed Codex worktrees for project-root grouping.
41744172
- The canonical `codex-web-local` project shows both main-root and worktree threads.
4175-
- Registered managed Codex worktree roots are suppressed as standalone empty groups when the canonical workspace root is also registered.
41764173
- Path-like project tooltips expose the full project path.
41774174
- Project rows and worktree icons remain readable in light and dark themes.
41784175

0 commit comments

Comments
 (0)