diff --git a/packages/ui/src/components/session/sidebar/hooks/useSwitcherItems.ts b/packages/ui/src/components/session/sidebar/hooks/useSwitcherItems.ts index 5f99dab1..417e5f8f 100644 --- a/packages/ui/src/components/session/sidebar/hooks/useSwitcherItems.ts +++ b/packages/ui/src/components/session/sidebar/hooks/useSwitcherItems.ts @@ -7,7 +7,7 @@ import { useSessionPinnedStore } from '@/stores/useSessionPinnedStore'; import { useGitAllBranches, useGitStore } from '@/stores/useGitStore'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import type { SessionNode } from '../types'; -import { compareSessionsByPinnedAndTime } from '../utils'; +import { compareSessionsByPinnedAndTime, isPathWithinProject } from '../utils'; export type SwitcherItem = { node: SessionNode; @@ -60,7 +60,7 @@ export const useSwitcherItems = (enabled: boolean, options: SwitcherItemsOptions (directory: string | null) => { if (!directory) return null; const matches = normalizedProjects - .filter((project) => directory === project.normalizedPath || directory.startsWith(`${project.normalizedPath}/`)) + .filter((project) => isPathWithinProject(directory, project.normalizedPath)) .sort((a, b) => (b.normalizedPath?.length ?? 0) - (a.normalizedPath?.length ?? 0)); return matches[0] ?? null; }, diff --git a/packages/ui/src/components/session/sidebar/utils.test.ts b/packages/ui/src/components/session/sidebar/utils.test.ts new file mode 100644 index 00000000..31a17ad0 --- /dev/null +++ b/packages/ui/src/components/session/sidebar/utils.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, test } from 'bun:test'; + +import { isPathWithinProject } from './utils'; + +describe('isPathWithinProject', () => { + test('matches child directories for root projects', () => { + expect(isPathWithinProject('/workspace/app', '/')).toBe(true); + }); + + test('matches exact project directories', () => { + expect(isPathWithinProject('/workspace/app', '/workspace/app')).toBe(true); + }); + + test('does not match sibling directory prefixes', () => { + expect(isPathWithinProject('/workspace/app2', '/workspace/app')).toBe(false); + }); + + test('returns false when directory is null', () => { + expect(isPathWithinProject(null, '/workspace/app')).toBe(false); + }); + + test('returns false when projectPath is null', () => { + expect(isPathWithinProject('/workspace/app', null)).toBe(false); + }); + + test('matches deep child directories', () => { + expect(isPathWithinProject('/workspace/app/sub/dir', '/workspace/app')).toBe(true); + }); +}); diff --git a/packages/ui/src/components/session/sidebar/utils.tsx b/packages/ui/src/components/session/sidebar/utils.tsx index fb6c0231..f4cbafe8 100644 --- a/packages/ui/src/components/session/sidebar/utils.tsx +++ b/packages/ui/src/components/session/sidebar/utils.tsx @@ -81,6 +81,15 @@ export const normalizePath = (value?: string | null) => { return normalized.length === 0 ? '/' : normalized; }; +export const isPathWithinProject = (directory?: string | null, projectPath?: string | null): boolean => { + const normalizedDirectory = normalizePath(directory); + const normalizedProjectPath = normalizePath(projectPath); + if (!normalizedDirectory || !normalizedProjectPath) return false; + if (normalizedDirectory === normalizedProjectPath) return true; + if (normalizedProjectPath === '/') return normalizedDirectory.startsWith('/'); + return normalizedDirectory.startsWith(`${normalizedProjectPath}/`); +}; + export const normalizeForBranchComparison = (value: string): string => { return value .toLowerCase()