fix(web): distinguish worktree sessions in sidebar (#1039)

* test: reproduce issue #782

* fix: distinguish worktree sessions in sidebar (closes #782)

* test: cover worktree label fallback

* test: cover worktree session search

* fix: search worktree session details
This commit is contained in:
SSU-WEI HUANG
2026-07-16 12:32:37 +08:00
committed by GitHub
parent 2ce6d3ef3a
commit dd2684a4e7
2 changed files with 91 additions and 3 deletions
+64
View File
@@ -6,6 +6,7 @@ import {
filterActiveSessionsOnly,
getNextSessionVisibleCount,
getSessionDedupKey,
getWorktreeSessionLabel,
getVisibleSessionPreview,
isSidebarEmptySessionStub,
normalizeSearch,
@@ -34,6 +35,51 @@ function makeSession(overrides: Partial<SessionSummary> & { 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', () => {
+27 -3
View File
@@ -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: {
</span>
</div>
</div>
{showPath ? (
<div className="truncate text-xs text-[var(--app-hint)]">
{s.metadata?.path ?? s.id}
{showPath || worktreeLabel ? (
<div
className="truncate text-xs text-[var(--app-hint)]"
title={worktreeLabel
? s.metadata?.worktree?.worktreePath ?? s.metadata?.path
: undefined}
>
{worktreeLabel ?? s.metadata?.path ?? s.id}
</div>
) : null}
</button>