diff --git a/web/src/components/SessionList.test.ts b/web/src/components/SessionList.test.ts index 3cfe5460..06582f6a 100644 --- a/web/src/components/SessionList.test.ts +++ b/web/src/components/SessionList.test.ts @@ -6,6 +6,7 @@ import { filterActiveSessionsOnly, getNextSessionVisibleCount, getSessionDedupKey, + getWorktreeSessionLabel, getVisibleSessionPreview, isSidebarEmptySessionStub, normalizeSearch, @@ -34,6 +35,51 @@ function makeSession(overrides: Partial & { id: string }): Sessi } } +describe('getWorktreeSessionLabel', () => { + it('returns the worktree name for sessions grouped under a shared repository', () => { + const session = makeSession({ + id: 'worktree-session', + metadata: { + path: '/work/hapi-worktrees/fix-resume', + worktree: { + basePath: '/work/hapi', + branch: 'fix/resume', + name: 'fix-resume', + worktreePath: '/work/hapi-worktrees/fix-resume' + } + } + }) + + expect(getWorktreeSessionLabel(session)).toBe('fix-resume') + }) + + it('does not add a subtitle to ordinary sessions', () => { + const session = makeSession({ + id: 'ordinary-session', + metadata: { path: '/work/hapi' } + }) + + expect(getWorktreeSessionLabel(session)).toBeNull() + }) + + it('falls back to the worktree directory name when metadata name is blank', () => { + const session = makeSession({ + id: 'windows-worktree-session', + metadata: { + path: 'C:\\work\\hapi-worktrees\\fix-resume', + worktree: { + basePath: 'C:\\work\\hapi', + branch: 'fix/resume', + name: ' ', + worktreePath: 'C:\\work\\hapi-worktrees\\fix-resume\\' + } + } + }) + + expect(getWorktreeSessionLabel(session)).toBe('fix-resume') + }) +}) + describe('deduplicateSessionsByAgentId', () => { it('deduplicates sessions with the same agentSessionId', () => { const sessions = [ @@ -242,6 +288,24 @@ describe('session list search helpers', () => { expect(sessionMatchesQuery(session, normalizeSearch('desktop'), 'desktop')).toBe(true) expect(sessionMatchesQuery(session, normalizeSearch('missing'), 'desktop')).toBe(false) }) + + it('matches the displayed worktree label and worktree path', () => { + const session = makeSession({ + id: 'worktree-session', + metadata: { + path: '/work/hapi', + worktree: { + basePath: '/work/hapi', + branch: 'fix/sidebar-search', + name: 'sidebar-search', + worktreePath: '/work/hapi-worktrees/fix-sidebar-search' + } + } + }) + + expect(sessionMatchesQuery(session, normalizeSearch('sidebar-search'), 'desktop')).toBe(true) + expect(sessionMatchesQuery(session, normalizeSearch('hapi-worktrees'), 'desktop')).toBe(true) + }) }) describe('getVisibleSessionPreview', () => { diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index aceb74ef..dc6d00de 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -442,6 +442,22 @@ function ChevronIcon(props: { className?: string; collapsed?: boolean }) { export { getSessionTitle } from '@/lib/sessionTitle' +export function getWorktreeSessionLabel(session: SessionSummary): string | null { + const worktree = session.metadata?.worktree + if (!worktree) { + return null + } + + const name = worktree.name.trim() + if (name) { + return name + } + + const path = (worktree.worktreePath ?? session.metadata?.path ?? '').replace(/[\\/]+$/, '') + const parts = path.split(/[\\/]+/).filter(Boolean) + return parts.at(-1) ?? null +} + function getTodoProgress(session: SessionSummary): { completed: number; total: number } | null { if (!session.todoProgress) return null if (session.todoProgress.completed === session.todoProgress.total) return null @@ -456,9 +472,11 @@ export function sessionMatchesQuery(session: SessionSummary, query: string, mach if (!query) return true const searchable = [ getSessionTitle(session), + getWorktreeSessionLabel(session), session.id, session.metadata?.path, session.metadata?.worktree?.basePath, + session.metadata?.worktree?.worktreePath, session.metadata?.name, session.metadata?.summary?.text, session.metadata?.flavor, @@ -615,6 +633,7 @@ function SessionItem(props: { }) const sessionName = getSessionTitle(s) + const worktreeLabel = getWorktreeSessionLabel(s) const todoProgress = getTodoProgress(s) const attention = useMemo( () => showDetailedStatus @@ -695,9 +714,14 @@ function SessionItem(props: { - {showPath ? ( -
- {s.metadata?.path ?? s.id} + {showPath || worktreeLabel ? ( +
+ {worktreeLabel ?? s.metadata?.path ?? s.id}
) : null}