diff --git a/desktop/src/stores/sessionStore.test.ts b/desktop/src/stores/sessionStore.test.ts index 6c91cb85..48d57d8d 100644 --- a/desktop/src/stores/sessionStore.test.ts +++ b/desktop/src/stores/sessionStore.test.ts @@ -15,6 +15,7 @@ vi.mock('../api/sessions', () => ({ })) import { useSessionStore } from './sessionStore' +import { useTabStore } from './tabStore' const initialState = useSessionStore.getState() @@ -43,10 +44,12 @@ describe('sessionStore', () => { selectedProjects: [], availableProjects: [], }) + useTabStore.setState({ tabs: [], activeTabId: null }) }) afterEach(() => { useSessionStore.setState(initialState) + useTabStore.setState({ tabs: [], activeTabId: null }) }) it('returns a new session id before the background refresh completes', async () => { @@ -111,6 +114,27 @@ describe('sessionStore', () => { expect(useSessionStore.getState().sessions[0]?.title).toBe('开始优化UI') }) + it('syncs refreshed session titles into already-open tabs', async () => { + useTabStore.getState().openTab('session-title-2', '```json {"title":') + listMock.mockResolvedValue({ + sessions: [{ + id: 'session-title-2', + title: '使用bash写一个shell,随便写点什么东西', + createdAt: '2026-05-07T00:00:00.000Z', + modifiedAt: '2026-05-07T00:00:01.000Z', + messageCount: 3, + projectPath: '', + workDir: '/workspace/project', + workDirExists: true, + }], + total: 1, + }) + + await useSessionStore.getState().fetchSessions() + + expect(useTabStore.getState().tabs[0]?.title).toBe('使用bash写一个shell,随便写点什么东西') + }) + it('forwards direct branch switch repository options when creating a session', async () => { createMock.mockResolvedValue({ sessionId: 'session-branch-switch', workDir: '/workspace/repo' }) listMock.mockResolvedValue({ sessions: [], total: 0 }) diff --git a/desktop/src/stores/sessionStore.ts b/desktop/src/stores/sessionStore.ts index cf11b11c..223aaa1e 100644 --- a/desktop/src/stores/sessionStore.ts +++ b/desktop/src/stores/sessionStore.ts @@ -1,6 +1,7 @@ import { create } from 'zustand' import { sessionsApi, type CreateSessionRepositoryOptions } from '../api/sessions' import { useSessionRuntimeStore } from './sessionRuntimeStore' +import { useTabStore } from './tabStore' import type { SessionListItem } from '../types/session' import { isPlaceholderSessionTitle } from '../lib/sessionTitle' @@ -37,6 +38,7 @@ export const useSessionStore = create((set, get) => ({ set({ isLoading: true, error: null }) try { const { sessions: raw } = await sessionsApi.list({ project, limit: 100 }) + let syncedSessions: SessionListItem[] = [] set((state) => { const currentById = new Map(state.sessions.map((session) => [session.id, session])) // Deduplicate by session ID - keep the most recently modified entry. @@ -50,9 +52,11 @@ export const useSessionStore = create((set, get) => ({ } } const sessions = [...byId.values()] + syncedSessions = sessions const availableProjects = [...new Set(sessions.map((s) => s.projectPath).filter(Boolean))].sort() return { sessions, availableProjects, isLoading: false } }) + syncOpenSessionTabTitles(syncedSessions) } catch (err) { set({ error: (err as Error).message, isLoading: false }) } @@ -126,3 +130,15 @@ function preserveLocalTitle( } return incoming } + +function syncOpenSessionTabTitles(sessions: SessionListItem[]): void { + const titleById = new Map(sessions.map((session) => [session.id, session.title])) + const { tabs, updateTabTitle } = useTabStore.getState() + for (const tab of tabs) { + if (tab.type !== 'session') continue + const title = titleById.get(tab.sessionId) + if (title && title !== tab.title) { + updateTabTitle(tab.sessionId, title) + } + } +} diff --git a/desktop/src/stores/tabStore.test.ts b/desktop/src/stores/tabStore.test.ts new file mode 100644 index 00000000..b90db356 --- /dev/null +++ b/desktop/src/stores/tabStore.test.ts @@ -0,0 +1,22 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import { useTabStore } from './tabStore' + +describe('tabStore', () => { + beforeEach(() => { + useTabStore.setState({ tabs: [], activeTabId: null }) + localStorage.clear() + }) + + it('refreshes an existing tab title when opening the same session again', () => { + useTabStore.getState().openTab('session-1', '```json {"title":') + useTabStore.getState().openTab('session-1', '使用bash写一个shell,随便写点什么东西') + + expect(useTabStore.getState().tabs).toHaveLength(1) + expect(useTabStore.getState().tabs[0]).toMatchObject({ + sessionId: 'session-1', + title: '使用bash写一个shell,随便写点什么东西', + type: 'session', + }) + expect(useTabStore.getState().activeTabId).toBe('session-1') + }) +}) diff --git a/desktop/src/stores/tabStore.ts b/desktop/src/stores/tabStore.ts index edc9ac03..1d4d7ad9 100644 --- a/desktop/src/stores/tabStore.ts +++ b/desktop/src/stores/tabStore.ts @@ -49,8 +49,12 @@ export const useTabStore = create((set, get) => ({ if (existing) { set({ tabs: tabs.map((tab) => - tab.sessionId === sessionId && !(tab as Partial).type - ? { ...tab, type } + tab.sessionId === sessionId + ? { + ...tab, + title, + ...(!(tab as Partial).type ? { type } : {}), + } : tab, ), activeTabId: sessionId,