fix(web): respect manual session path collapse (#656)

This commit is contained in:
CoColate
2026-05-21 21:18:27 +08:00
committed by GitHub
parent 611c4eec49
commit 5b90a48b33
2 changed files with 120 additions and 10 deletions
@@ -1,4 +1,4 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { ReactNode } from 'react'
@@ -95,3 +95,102 @@ describe('SessionList directory action', () => {
expect(screen.queryByRole('button', { name: 'New session in this directory' })).toBeNull()
})
})
describe('SessionList collapse behavior', () => {
function renderSessionList(sessions: SessionSummary[], selectedSessionId = 'session-running') {
return (
<QueryClientProvider client={new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
}
})}>
<I18nProvider>
<SessionList
sessions={sessions}
selectedSessionId={selectedSessionId}
onSelect={vi.fn()}
onNewSession={vi.fn()}
onRefresh={vi.fn()}
isLoading={false}
renderHeader={false}
api={null}
/>
</I18nProvider>
</QueryClientProvider>
)
}
function getProjectPanel(): Element {
const header = screen.getByTitle('/work/hapi')
const panel = header.nextElementSibling
if (!panel) {
throw new Error('Expected project collapse panel')
}
return panel
}
it('keeps a selected running path collapsed across live session-list refreshes', async () => {
const baseSessions = [
makeSession({
id: 'session-running',
active: true,
thinking: true,
pendingRequestsCount: 1,
updatedAt: 100,
metadata: { path: '/work/hapi', name: 'Running task', flavor: 'codex' },
}),
makeSession({
id: 'session-old',
updatedAt: 50,
metadata: { path: '/work/hapi', name: 'Older task', flavor: 'codex' },
})
]
const { rerender } = render(renderSessionList(baseSessions))
expect(getProjectPanel().getAttribute('data-open')).toBe('true')
fireEvent.click(screen.getByTitle('/work/hapi'))
expect(getProjectPanel().getAttribute('data-open')).toBeNull()
rerender(renderSessionList([
{
...baseSessions[0]!,
pendingRequestsCount: 2,
updatedAt: 200,
},
baseSessions[1]!
]))
await waitFor(() => {
expect(getProjectPanel().getAttribute('data-open')).toBeNull()
})
})
it('auto-expands the path again when the selected session changes', async () => {
const sessions = [
makeSession({
id: 'session-running',
active: true,
thinking: true,
updatedAt: 100,
metadata: { path: '/work/hapi', name: 'Running task', flavor: 'codex' },
}),
makeSession({
id: 'session-next',
updatedAt: 90,
metadata: { path: '/work/hapi', name: 'Next task', flavor: 'codex' },
})
]
const { rerender } = render(renderSessionList(sessions))
fireEvent.click(screen.getByTitle('/work/hapi'))
expect(getProjectPanel().getAttribute('data-open')).toBeNull()
rerender(renderSessionList(sessions, 'session-next'))
await waitFor(() => {
expect(getProjectPanel().getAttribute('data-open')).toBe('true')
})
})
})
+20 -9
View File
@@ -739,6 +739,7 @@ export function SessionList(props: {
const [collapseOverrides, setCollapseOverrides] = useState<Map<string, boolean>>(
() => new Map()
)
const autoExpandedSelectedSessionKeyRef = useRef<string | null>(null)
const isGroupCollapsed = (group: SessionGroup): boolean => {
if (isSearching) return false
const override = collapseOverrides.get(group.key)
@@ -812,16 +813,26 @@ export function SessionList(props: {
})
}
// Auto-expand group (and machine) containing selected session
// Auto-expand group (and machine) containing the selected session only when
// the selected-session/group pair changes. Without this guard, every live
// session-list refresh (for example tool-call updates from a running selected
// session) reopens a path the user just collapsed.
useEffect(() => {
if (!selectedSessionId) return
setCollapseOverrides(prev => {
const group = allGroups.find(g =>
g.sessions.some(s => s.id === selectedSessionId)
)
if (!group) return prev
return expandSelectedSessionCollapseOverrides(prev, group)
})
if (!selectedSessionId) {
autoExpandedSelectedSessionKeyRef.current = null
return
}
const group = allGroups.find(g =>
g.sessions.some(s => s.id === selectedSessionId)
)
if (!group) return
const autoExpandKey = `${selectedSessionId}::${group.key}`
if (autoExpandedSelectedSessionKeyRef.current === autoExpandKey) return
autoExpandedSelectedSessionKeyRef.current = autoExpandKey
setCollapseOverrides(prev => expandSelectedSessionCollapseOverrides(prev, group))
}, [selectedSessionId, allGroups])
// Clean up stale collapse overrides