fix(desktop): refresh archived sessions on external changes
Co-authored-by: DavidMetcalfe <80915+DavidMetcalfe@users.noreply.github.com>
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import { act, cleanup, renderHook } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { $sidebarShowArchived } from '@/store/layout'
|
||||
import { $changeEventsAvailable, $cronChangeTick, $sessionsChangeTick } from '@/store/live-sync'
|
||||
import { $activeSessionId } from '@/store/session'
|
||||
import { loadArchivedSessions } from '@/store/sidebar-archive'
|
||||
|
||||
import { useBackgroundSync } from './use-background-sync'
|
||||
|
||||
vi.mock('@/store/sidebar-archive', () => ({
|
||||
loadArchivedSessions: vi.fn()
|
||||
}))
|
||||
|
||||
const noop = () => undefined
|
||||
const requestGateway = async () => ({ sessions: [] })
|
||||
|
||||
@@ -45,6 +51,8 @@ describe('useBackgroundSync profile-scoped session refresh', () => {
|
||||
$changeEventsAvailable.set(false)
|
||||
$cronChangeTick.set(0)
|
||||
$sessionsChangeTick.set(0)
|
||||
$sidebarShowArchived.set(false)
|
||||
vi.mocked(loadArchivedSessions).mockReset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -103,4 +111,27 @@ describe('useBackgroundSync profile-scoped session refresh', () => {
|
||||
await act(async () => undefined)
|
||||
expect(refreshSessions).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('reloads archived sessions after an external session change while the Archived view is open', async () => {
|
||||
$changeEventsAvailable.set(true)
|
||||
$sidebarShowArchived.set(true)
|
||||
render('default', 'local', async () => undefined)
|
||||
|
||||
await act(async () => {
|
||||
$sessionsChangeTick.set(1)
|
||||
})
|
||||
|
||||
expect(loadArchivedSessions).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not reload archived sessions when the Archived view is closed', async () => {
|
||||
$changeEventsAvailable.set(true)
|
||||
render('default', 'local', async () => undefined)
|
||||
|
||||
await act(async () => {
|
||||
$sessionsChangeTick.set(1)
|
||||
})
|
||||
|
||||
expect(loadArchivedSessions).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getLatestSessionMessages, type ProfileScope } from '@/hermes'
|
||||
import { preserveLocalAssistantErrors, sealOpenToolParts, toChatMessages } from '@/lib/chat-messages'
|
||||
import { createClientSessionState } from '@/lib/chat-runtime'
|
||||
import { sessionMessagesSignature } from '@/lib/session-signatures'
|
||||
import { $sidebarShowArchived } from '@/store/layout'
|
||||
import { $changeEventsAvailable, $cronChangeTick, $sessionsChangeTick } from '@/store/live-sync'
|
||||
import { $onBattery, batteryPollInterval } from '@/store/power'
|
||||
import { refreshActiveProfile } from '@/store/profile'
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
SESSION_WATCHDOG_TIMEOUT_MS,
|
||||
setSessionStalled
|
||||
} from '@/store/session-states'
|
||||
import { loadArchivedSessions } from '@/store/sidebar-archive'
|
||||
|
||||
import type { ClientSessionState } from '../../types'
|
||||
import type { GatewayRequester } from '../types'
|
||||
@@ -753,11 +755,19 @@ export function useBackgroundSync({
|
||||
lastRunAt = Date.now()
|
||||
void refreshSessions()
|
||||
void refreshMessagingSessions()
|
||||
|
||||
// Archived sessions use their own capped query. Reload it only while the
|
||||
// sidebar is showing that view, matching the on-demand fetch contract.
|
||||
if ($sidebarShowArchived.get()) {
|
||||
void loadArchivedSessions()
|
||||
}
|
||||
|
||||
// The project tree is a grouping of the same stored rows, so a session
|
||||
// created/deleted/renamed/re-homed outside this window goes stale in the
|
||||
// Projects sidebar without this (#100354). refreshProjectTree() keeps the
|
||||
// cached tree on failure, so a not-yet-ready backend costs nothing.
|
||||
void refreshProjectTree()
|
||||
|
||||
requestActiveTranscriptRefresh(true)
|
||||
// Bot canonical chats live in workspace tiles, never in the main-pane
|
||||
// selection — without this they never see background deliveries
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { listAllProfileSessions, type SessionInfo } from '@/hermes'
|
||||
|
||||
import { $archivedSessions, loadArchivedSessions } from './sidebar-archive'
|
||||
|
||||
vi.mock('@/hermes', () => ({
|
||||
listAllProfileSessions: vi.fn()
|
||||
}))
|
||||
|
||||
describe('loadArchivedSessions', () => {
|
||||
beforeEach(() => {
|
||||
$archivedSessions.set([])
|
||||
vi.mocked(listAllProfileSessions).mockReset()
|
||||
})
|
||||
|
||||
it('keeps the last successful result when a refresh fails', async () => {
|
||||
const existing = { id: 'archived-1', title: 'Keep me' } as SessionInfo
|
||||
$archivedSessions.set([existing])
|
||||
vi.mocked(listAllProfileSessions).mockRejectedValue(new Error('offline'))
|
||||
|
||||
await loadArchivedSessions()
|
||||
|
||||
expect($archivedSessions.get()).toEqual([existing])
|
||||
})
|
||||
})
|
||||
@@ -23,7 +23,8 @@ export async function loadArchivedSessions(): Promise<void> {
|
||||
|
||||
$archivedSessions.set(result.sessions)
|
||||
} catch {
|
||||
$archivedSessions.set([])
|
||||
// A background refresh must not turn a usable Archived view into an empty
|
||||
// one when the backend is temporarily unavailable. Keep the last good set.
|
||||
} finally {
|
||||
$archivedSessionsLoading.set(false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user