mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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:
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user