fix(desktop): match a project to its cwd across Windows path spellings

Project ownership compared paths literally, so a nested cwd failed to match
its project whenever the separator or drive-letter case differed — which on
Windows is routine. Normalise both sides for comparison only, folding case
for drive and UNC paths.
This commit is contained in:
Dan Bennett
2026-08-08 14:33:30 -05:00
committed by Brooklyn Nicholson
parent 6dda0c91d9
commit 7ff9d7db91
2 changed files with 28 additions and 2 deletions
+8
View File
@@ -22,6 +22,7 @@ import {
exitProjectScope,
openProjectCreate,
pickProjectFolder,
projectIdForCwd,
projectNameForCwd,
refreshProjects,
refreshProjectTree,
@@ -241,6 +242,13 @@ describe('projectNameForCwd', () => {
expect(projectNameForCwd('/elsewhere/mono-feature/src')).toBe('Monorepo')
})
it('matches nested Windows paths across separator and case differences', () => {
$projectTree.set([treeNode({ id: 'p_win', label: 'Windows app', path: 'C:\\Repos\\App' })])
expect(projectIdForCwd('c:/repos/app/src')).toBe('p_win')
expect(projectNameForCwd('c:/repos/app/src')).toBe('Windows app')
})
it('ignores auto-projects and the No-project bucket (no named identity)', () => {
$projectTree.set([
treeNode({ id: '/repos/loose', label: 'loose', path: '/repos/loose', isAuto: true }),
+20 -2
View File
@@ -229,8 +229,26 @@ export function resolveNewSessionCwd(): string {
return workspaceCwdForNewSession()
}
const underPath = (parent: string, child: string): boolean =>
child === parent || child.startsWith(parent.endsWith('/') ? parent : `${parent}/`)
// Comparison-only path spelling: accept either separator everywhere, trim a
// trailing separator, and fold case for Windows drive/UNC paths. The backend
// uses the same host-aware identity rule when assigning sessions to projects.
const comparisonPath = (path: string): string => {
const raw = path.trim()
const windows = /^[A-Za-z]:[/\\]/.test(raw) || raw.startsWith('\\') || raw.startsWith('//')
const normalized = raw.replace(/\\/g, '/').replace(/\/+$/, '')
return windows ? normalized.toLowerCase() : normalized
}
const underPath = (parent: string, child: string): boolean => {
const normalizedParent = comparisonPath(parent)
const normalizedChild = comparisonPath(child)
return (
normalizedChild === normalizedParent ||
normalizedChild.startsWith(normalizedParent.endsWith('/') ? normalizedParent : `${normalizedParent}/`)
)
}
// The project (explicit or auto) that owns `cwd`, by longest path match across
// the live tree. Null when no project covers it (it'll surface as a fresh