Skip to content

Commit 87c1791

Browse files
committed
fix(ui): preserve Windows catalog root kinds
1 parent 377b873 commit 87c1791

2 files changed

Lines changed: 69 additions & 7 deletions

File tree

ui/src/lib/sessions/catalog-project-grouping.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ describe("groupCatalogSessionsByProject", () => {
7676
expect(result.ungrouped.map((item) => item.threadId)).toEqual(["missing", "blank"]);
7777
});
7878

79+
it("leaves Windows filesystem roots and root worktrees ungrouped", () => {
80+
const result = groupCatalogSessionsByProject([
81+
session("drive-root", "C:\\"),
82+
session("drive-root-worktree", "c:\\.CLAUDE\\WORKTREES\\fix-1\\src"),
83+
session("current-drive-root", "\\"),
84+
session("current-drive-root-worktree", "\\.claude\\worktrees\\fix-2\\src"),
85+
]);
86+
87+
expect(result.groups).toHaveLength(0);
88+
expect(result.ungrouped.map((item) => item.threadId)).toEqual([
89+
"drive-root",
90+
"drive-root-worktree",
91+
"current-drive-root",
92+
"current-drive-root-worktree",
93+
]);
94+
});
95+
7996
it.each([
8097
[" /Users/dev/openclaw/// ", "/Users/dev/openclaw", "openclaw"],
8198
["C:\\Users\\dev\\openclaw\\", "C:\\Users\\dev\\openclaw", "openclaw"],
@@ -109,6 +126,34 @@ describe("groupCatalogSessionsByProject", () => {
109126
]);
110127
});
111128

129+
it("preserves Windows root kinds while grouping equivalent UNC paths", () => {
130+
const result = groupCatalogSessionsByProject([
131+
session("unc-first", "\\\\Server\\Share\\Project"),
132+
session("unc-second", "\\\\server\\share\\project"),
133+
session("current-drive-rooted", "\\Server\\Share\\Project"),
134+
]);
135+
136+
expect(result.groups).toHaveLength(2);
137+
expect(result.groups[0]?.sessions.map((item) => item.threadId)).toEqual([
138+
"unc-first",
139+
"unc-second",
140+
]);
141+
expect(result.groups[1]?.sessions.map((item) => item.threadId)).toEqual([
142+
"current-drive-rooted",
143+
]);
144+
});
145+
146+
it("keeps an UNC share root groupable as a project root", () => {
147+
const result = groupCatalogSessionsByProject([
148+
session("first", "\\\\Server\\Share\\"),
149+
session("second", "\\\\server\\share"),
150+
]);
151+
152+
expect(result.groups).toHaveLength(1);
153+
expect(result.groups[0]?.sessions.map((item) => item.threadId)).toEqual(["first", "second"]);
154+
expect(result.ungrouped).toHaveLength(0);
155+
});
156+
112157
it("folds case-varied Windows worktree paths into their origin project", () => {
113158
const result = groupCatalogSessionsByProject([
114159
session("direct", "C:\\Work\\OpenClaw"),

ui/src/lib/sessions/catalog-project-grouping.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,31 @@ type CatalogProjectGroup = {
1313
sessions: SessionCatalogSession[];
1414
};
1515

16+
type WindowsPathRootKind = "drive" | "unc" | "rooted";
17+
18+
function windowsPathRootKind(value: string): WindowsPathRootKind | undefined {
19+
if (/^[A-Za-z]:[\\/]/.test(value)) {
20+
return "drive";
21+
}
22+
if (value.startsWith("\\\\")) {
23+
return "unc";
24+
}
25+
if (value.startsWith("\\")) {
26+
return "rooted";
27+
}
28+
return undefined;
29+
}
30+
1631
function isWindowsPath(value: string): boolean {
17-
return /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\");
32+
return windowsPathRootKind(value) !== undefined;
1833
}
1934

2035
function catalogProjectPathIdentity(value: string): string {
21-
if (!isWindowsPath(value)) {
36+
const rootKind = windowsPathRootKind(value);
37+
if (!rootKind) {
2238
return value;
2339
}
24-
return `windows:${value
40+
return `windows:${rootKind}:${value
2541
.split(/[\\/]+/)
2642
.filter(Boolean)
2743
.map((segment) => segment.toLowerCase())
@@ -58,10 +74,11 @@ export function groupCatalogSessionsByProject(sessions: readonly SessionCatalogS
5874
group.sessions.push(session);
5975
continue;
6076
}
61-
// Accepted tradeoff: filesystem-root cwds ("/", "C:\") are not real harness
62-
// session roots; after trimming they fall to the ungrouped flat tail by design.
77+
// Accepted tradeoff: local filesystem-root cwds ("/", "C:\") are not real
78+
// harness session roots, so they fall to the ungrouped flat tail. UNC share
79+
// roots remain groupable because the share itself can be a project root.
6380
let projectPath = session.cwd?.trim().replace(/[\\/]+$/, "");
64-
if (!projectPath) {
81+
if (!projectPath || /^[A-Za-z]:$/.test(projectPath)) {
6582
ungrouped.push(session);
6683
continue;
6784
}
@@ -72,7 +89,7 @@ export function groupCatalogSessionsByProject(sessions: readonly SessionCatalogS
7289
: /^(.*?)[\\/]\.claude[\\/]worktrees[\\/][^\\/]/;
7390
const worktreeMatch = projectPath.match(worktreePattern);
7491
projectPath = worktreeMatch?.[1] ?? projectPath;
75-
if (!projectPath) {
92+
if (!projectPath || /^[A-Za-z]:$/.test(projectPath)) {
7693
ungrouped.push(session);
7794
continue;
7895
}

0 commit comments

Comments
 (0)