From 1f7b1e68c4e5f783fe3797de1860da850a3d56f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Tue, 23 Jun 2026 00:17:18 +0800 Subject: [PATCH] fix(desktop): keep session refresh usable Fixes #893. Reproduced the stuck session-list refresh by keeping the automatic fetch promise pending, then clicking the manual refresh button. Also covered the existing-sessions loading state so a background refresh does not leave the control disabled or spinning forever. Tested: cd desktop && bun run test -- src/components/layout/Sidebar.test.tsx --run Tested: bun run check:desktop Confidence: high Scope-risk: narrow --- .../src/components/layout/Sidebar.test.tsx | 29 +++++++++++++++++++ desktop/src/components/layout/Sidebar.tsx | 6 ++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/desktop/src/components/layout/Sidebar.test.tsx b/desktop/src/components/layout/Sidebar.test.tsx index 64e3f548..bb4cb2f0 100644 --- a/desktop/src/components/layout/Sidebar.test.tsx +++ b/desktop/src/components/layout/Sidebar.test.tsx @@ -293,6 +293,35 @@ describe('Sidebar', () => { expect(screen.getByRole('button', { name: 'Collapse display' })).toBeInTheDocument() }) + it('lets a manual session refresh supersede a stuck automatic refresh', async () => { + fetchSessions.mockReturnValue(new Promise(() => {})) + + render() + + await waitFor(() => expect(fetchSessions).toHaveBeenCalledTimes(1)) + fireEvent.click(screen.getByRole('button', { name: 'Refresh sessions' })) + + await waitFor(() => expect(fetchSessions).toHaveBeenCalledTimes(2)) + }) + + it('keeps the session refresh control usable when a background refresh is still loading existing sessions', async () => { + useSessionStore.setState({ + sessions: [ + makeSession('session-loaded', 'Loaded session', '/workspace/alpha', '2026-05-15T10:00:00.000Z'), + ], + isLoading: true, + }) + + render() + + const refreshButton = screen.getByRole('button', { name: 'Refresh sessions' }) + expect(refreshButton).not.toBeDisabled() + expect(refreshButton.querySelector('svg')).not.toHaveClass('animate-spin') + + fireEvent.click(refreshButton) + await waitFor(() => expect(fetchSessions).toHaveBeenCalled()) + }) + it('reorders project groups by dragging project headers while preserving expanded state', async () => { const base = new Date('2026-05-15T10:00:00.000Z').getTime() useSessionStore.setState({ diff --git a/desktop/src/components/layout/Sidebar.tsx b/desktop/src/components/layout/Sidebar.tsx index 3dc7ca0a..31e0f92d 100644 --- a/desktop/src/components/layout/Sidebar.tsx +++ b/desktop/src/components/layout/Sidebar.tsx @@ -127,6 +127,7 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { )) }, [hiddenProjectKeys, orderedProjectGroups]) const showInitialLoading = isLoading && sessions.length === 0 + const showRefreshLoading = showInitialLoading const filteredSessionIds = useMemo(() => filteredSessions.map((session) => session.id), [filteredSessions]) const selectedCount = selectedSessionIds.size const sessionsById = useMemo( @@ -709,12 +710,11 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {