fix(desktop): unarchive from Settings into the session's own sidebar slice

The Settings unarchive prepended the row into $sessions regardless of
source, so a messaging or cron session showed under SESSIONS until the
next refresh. Route it through restoreListedSession, which picks the slice
from the row's source.
This commit is contained in:
teknium1
2026-09-18 04:40:47 -07:00
committed by Teknium
parent f1e89cbba1
commit 8dff4e9bc5
2 changed files with 63 additions and 2 deletions

View File

@@ -0,0 +1,60 @@
// @vitest-environment jsdom
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { listAllProfileSessions, setSessionArchived } from '@/hermes'
import { en } from '@/i18n/en'
import { $messagingSessions, $sessions, setMessagingSessions, setSessions } from '@/store/session'
import type { SessionInfo } from '@/types/hermes'
import { SessionsSettings } from './sessions-settings'
vi.mock('@/i18n', () => ({ useI18n: () => ({ t: en }) }))
vi.mock('@/hermes', async importOriginal => ({
...(await importOriginal<Record<string, unknown>>()),
getHermesConfigRecord: vi.fn().mockResolvedValue({ config: {} }),
listAllProfileSessions: vi.fn(),
setSessionArchived: vi.fn().mockResolvedValue(undefined)
}))
const archivedMatrixSession = {
archived: true,
ended_at: null,
id: 'matrix-1',
input_tokens: 0,
is_active: false,
last_active: 1,
message_count: 2,
model: null,
output_tokens: 0,
preview: null,
source: 'matrix',
started_at: 1,
title: 'archived room',
tool_call_count: 0
} as SessionInfo
beforeEach(() => {
setSessions([])
setMessagingSessions([])
vi.mocked(listAllProfileSessions).mockResolvedValue({ sessions: [archivedMatrixSession], total: 1 } as never)
})
afterEach(() => {
cleanup()
})
describe('SessionsSettings unarchive', () => {
it('restores a messaging-source session into $messagingSessions, not $sessions', async () => {
render(<SessionsSettings />)
const button = await screen.findByRole('button', { name: en.settings.sessions.unarchive })
await act(async () => fireEvent.click(button))
await waitFor(() => expect(setSessionArchived).toHaveBeenCalledWith('matrix-1', false, undefined))
expect($messagingSessions.get().map(session => session.id)).toEqual(['matrix-1'])
expect($messagingSessions.get()[0]?.archived).toBe(false)
expect($sessions.get()).toEqual([])
})
})

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { restoreListedSession } from '@/app/session/hooks/use-session-actions/utils'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Tip } from '@/components/ui/tooltip'
@@ -17,7 +18,7 @@ import { triggerHaptic } from '@/lib/haptics'
import { Archive, ArchiveOff, FolderOpen, Loader2, Trash2 } from '@/lib/icons'
import { confirm } from '@/store/confirm'
import { notify, notifyError } from '@/store/notifications'
import { applyConfiguredDefaultProjectDir, ensureDefaultWorkspaceCwd, setSessions } from '@/store/session'
import { applyConfiguredDefaultProjectDir, ensureDefaultWorkspaceCwd } from '@/store/session'
import { untombstoneSessions } from '@/store/session-removal'
import { forgetSessionUnread } from '@/store/session-unread'
import type { HermesConfigRecord, SessionInfo } from '@/types/hermes'
@@ -63,7 +64,7 @@ export function SessionsSettings() {
// Surface it again in the sidebar without waiting for a full refresh, and
// lift any optimistic eviction so the grouped tree shows it again too.
untombstoneSessions([session.id, session._lineage_root_id])
setSessions(prev => [{ ...session, archived: false }, ...prev.filter(s => s.id !== session.id)])
restoreListedSession({ ...session, archived: false })
triggerHaptic('selection')
notify({ durationMs: 2_000, kind: 'success', message: s.restored })
} catch (err) {