fix(desktop): refresh profile rail after deletion (#49289)

This commit is contained in:
Tranquil-Flow 2026-06-20 02:08:37 +02:00 committed by brooklyn!
parent c5e8a60b0a
commit c3f06a8fda
3 changed files with 44 additions and 6 deletions

View file

@ -19,7 +19,6 @@ import { Textarea } from '@/components/ui/textarea'
import {
createProfile,
deleteProfile,
getProfiles,
getProfileSoul,
type ProfileInfo,
renameProfile,
@ -31,7 +30,7 @@ import { profileColorSoft, resolveProfileColor } from '@/lib/profile-color'
import { slug } from '@/lib/sanitize'
import { cn } from '@/lib/utils'
import { notify, notifyError } from '@/store/notifications'
import { $profileColors } from '@/store/profile'
import { $profileColors, refreshProfiles } from '@/store/profile'
import { useRefreshHotkey } from '../hooks/use-refresh-hotkey'
import {
@ -72,7 +71,7 @@ export function ProfilesView({ onClose }: ProfilesViewProps) {
const refresh = useCallback(async () => {
try {
const { profiles: list } = await getProfiles()
const list = await refreshProfiles()
setProfiles(list)
setSelectedName(current => {
if (current && list.some(p => p.name === current)) {

View file

@ -2,6 +2,7 @@ import { atom } from 'nanostores'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { HermesConnection } from '@/global'
import type { ProfileInfo } from '@/types/hermes'
// Keep profile.ts's side-effecting imports inert: the gateway socket layer and
// the REST query client must not run for real in a unit test.
@ -17,9 +18,20 @@ vi.mock('@/hermes', () => ({
vi.mock('@/lib/query-client', () => ({ queryClient: { invalidateQueries: vi.fn() } }))
vi.mock('@/store/starmap', () => ({ resetStarmapGraph }))
const { $activeGatewayProfile, ensureGatewayProfile } = await import('./profile')
const { $activeGatewayProfile, $profiles, ensureGatewayProfile, refreshProfiles } = await import('./profile')
const { $connection } = await import('./session')
const { queryClient } = await import('@/lib/query-client')
const { getProfiles } = await import('@/hermes')
const profile = (name: string, isDefault = false): ProfileInfo => ({
has_env: false,
is_default: isDefault,
model: null,
name,
path: `/tmp/hermes/${name}`,
provider: null,
skill_count: 0
})
const remoteConn = (over: Partial<HermesConnection> = {}): HermesConnection =>
({ baseUrl: 'https://hermes-roy.tail.ts.net', mode: 'remote', profile: 'vps-remote', ...over }) as HermesConnection
@ -35,6 +47,7 @@ beforeEach(() => {
$gateway.set({ id: 'live-socket' })
$activeGatewayProfile.set('default')
$connection.set(localConn())
$profiles.set([])
vi.stubGlobal('window', { hermesDesktop: { getConnection } })
vi.mocked(queryClient.invalidateQueries).mockClear()
resetStarmapGraph.mockClear()
@ -101,3 +114,23 @@ describe('profile-scoped cache invalidation', () => {
expect(resetStarmapGraph).toHaveBeenCalledTimes(1)
})
})
describe('refreshProfiles shared rail list (#49289)', () => {
it('removes a deleted profile from the shared $profiles cache after Manage Profiles refreshes', async () => {
$profiles.set([profile('default', true), profile('test1')])
vi.mocked(getProfiles).mockResolvedValueOnce({ profiles: [profile('default', true)] })
await refreshProfiles()
expect($profiles.get().map(profile => profile.name)).toEqual(['default'])
})
it('leaves the shared $profiles cache intact when the refresh fails', async () => {
$profiles.set([profile('default', true), profile('test1')])
vi.mocked(getProfiles).mockRejectedValueOnce(new Error('backend unavailable'))
await expect(refreshProfiles()).rejects.toThrow('backend unavailable')
expect($profiles.get().map(profile => profile.name)).toEqual(['default', 'test1'])
})
})

View file

@ -38,6 +38,13 @@ export function setActiveProfile(name: string): void {
$activeProfile.set(name || 'default')
}
export async function refreshProfiles(): Promise<ProfileInfo[]> {
const { profiles } = await getProfiles()
$profiles.set(profiles)
return profiles
}
// ── Rail order ─────────────────────────────────────────────────────────────
// User-defined order for the named (non-default) profile squares in the rail.
// Names absent from the list fall back to alphabetical, appended at the tail —
@ -111,8 +118,7 @@ export async function refreshActiveProfile(): Promise<void> {
}
try {
const { profiles } = await getProfiles()
$profiles.set(profiles)
await refreshProfiles()
} catch {
// Leave the cached list in place.
}