fix(coding): keep optimistic identity across snapshots

This commit is contained in:
2026-08-23 22:39:34 +08:00
parent 1abb2fff36
commit 934ba02718
3 changed files with 77 additions and 9 deletions

View File

@@ -337,6 +337,43 @@ describe('coding Conversation store', () => {
expect(store.getState().requestsByConversationId['conversation-a']).toEqual({});
});
it('preserves the optimistic UI id when a reconnect snapshot is already durable', async () => {
const store = createCodingConversationStore({
getSnapshot: vi.fn(),
openEvents: vi.fn(),
submitPrompt: vi.fn(async (input) => acceptance(
input.conversationId,
input.clientRequestId,
)),
createId: ids(),
});
store.getState().applySnapshotEvent(snapshotEvent(snapshot('conversation-a')));
store.getState().setDraft('conversation-a', 'Already durable');
await store.getState().submitPrompt({ conversationId: 'conversation-a', mode: 'prompt' });
const durable = snapshot('conversation-a', 1, 1);
store.getState().applySnapshotEvent(snapshotEvent({
...durable,
nodes: [{
kind: 'message',
id: 'durable-user-1',
sourceEntryId: 'entry-user-1',
clientRequestId: 'request-1',
role: 'user',
status: 'complete',
blocks: [{ kind: 'text', id: 'durable-text-1', text: 'Already durable', status: 'complete' }],
}],
}));
expect(selectCodingConversationSnapshot('conversation-a')(store.getState())?.nodes[0])
.toMatchObject({
id: 'node-1',
sourceEntryId: 'entry-user-1',
clientRequestId: 'request-1',
status: 'complete',
});
expect(store.getState().requestsByConversationId['conversation-a']).toEqual({});
});
it('restores draft attachments and marks the optimistic node on definite rejection', async () => {
const submitPrompt = vi.fn(async () => {
throw new AppError('RUNTIME', 'Model unavailable', undefined, {