feat(coding): add conversation archive and first-message titles

This commit is contained in:
2026-09-20 12:43:51 +08:00
parent d61226532a
commit fd0293fe3d
22 changed files with 698 additions and 69 deletions

View File

@@ -2,6 +2,7 @@ import { act, renderHook, waitFor } from '@testing-library/react';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { useStore } from 'zustand';
import { codingWorkspaceStore } from '@/stores/coding-workspace';
import { describe, expect, it, vi } from 'vitest';
import { AppError } from '@/lib/error-model';
import {
@@ -29,7 +30,7 @@ class FakeEventSource {
this.listeners.set(type, listeners);
}
emit(type: 'snapshot' | 'patch-batch', value: unknown): void {
emit(type: 'snapshot' | 'patch-batch' | 'conversation.metadata-changed', value: unknown): void {
const event = new MessageEvent(type, { data: JSON.stringify(value) });
for (const listener of this.listeners.get(type) ?? []) {
if (typeof listener === 'function') listener(event);
@@ -138,6 +139,27 @@ function ids() {
}
describe('coding Conversation store', () => {
it('refreshes only metadata on title events and EventSource reconnect without preparing workers', async () => {
const previous = codingWorkspaceStore.getState();
const refresh = vi.fn(async () => undefined);
codingWorkspaceStore.setState({ activeProjectId: 'project-metadata', refreshConversations: refresh });
const source = new FakeEventSource();
const getSnapshot = vi.fn();
const store = createCodingConversationStore({ openEvents: async () => source as unknown as EventSource, getSnapshot });
try {
await store.getState().connectEvents();
source.emit('conversation.metadata-changed', { projectId: 'project-metadata', conversationId: 'hidden' });
source.open();
source.fail();
source.open();
expect(refresh.mock.calls).toEqual([['project-metadata'], ['project-metadata'], ['project-metadata']]);
expect(getSnapshot).not.toHaveBeenCalled();
} finally {
store.getState().disconnectEvents();
codingWorkspaceStore.setState(previous, true);
}
});
it('queues observation sync only for a live completed ordinary prompt', () => {
const queueSettledSessionSync = vi.fn();
const store = createCodingConversationStore({