From a257f45248a64f7e1a2c88cbec33c2e399456efe Mon Sep 17 00:00:00 2001 From: Chris0Jeky Date: Wed, 9 Sep 2026 21:07:14 +0100 Subject: [PATCH] Fail fast when reconciling a successful chat send --- frontend/taskdeck-web/src/api/chatApi.ts | 4 ++-- .../taskdeck-web/src/composables/useAutomationChat.ts | 4 +++- frontend/taskdeck-web/src/tests/api/chatApi.spec.ts | 8 ++++++++ .../src/tests/composables/useAutomationChat.spec.ts | 3 +++ .../src/tests/store/chatApi.integration.spec.ts | 4 ++-- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/taskdeck-web/src/api/chatApi.ts b/frontend/taskdeck-web/src/api/chatApi.ts index f8399b387b..0639acaa39 100644 --- a/frontend/taskdeck-web/src/api/chatApi.ts +++ b/frontend/taskdeck-web/src/api/chatApi.ts @@ -13,8 +13,8 @@ export const chatApi = { return data }, - async getSession(sessionId: string): Promise { - const { data } = await http.get(`/llm/chat/sessions/${encodeURIComponent(sessionId)}`) + async getSession(sessionId: string, options?: { skipRetry?: boolean }): Promise { + const { data } = await http.get(`/llm/chat/sessions/${encodeURIComponent(sessionId)}`, options) return data }, diff --git a/frontend/taskdeck-web/src/composables/useAutomationChat.ts b/frontend/taskdeck-web/src/composables/useAutomationChat.ts index e597622081..eb9dacc822 100644 --- a/frontend/taskdeck-web/src/composables/useAutomationChat.ts +++ b/frontend/taskdeck-web/src/composables/useAutomationChat.ts @@ -284,7 +284,9 @@ export function useAutomationChat() { async function refreshSelectedSession(sessionId: string) { try { - const result = await chatApi.getSession(sessionId) + // The send already succeeded and its messages are retained locally. A + // failed reconciliation must not hold continuation behind read retries. + const result = await chatApi.getSession(sessionId, { skipRetry: true }) if (isDisposed || requestedSessionId !== sessionId || selectedSession.value?.id !== sessionId) return localMessagesBySession.delete(sessionId) sessionWriteGenerations.set(sessionId, (sessionWriteGenerations.get(sessionId) ?? 0) + 1) diff --git a/frontend/taskdeck-web/src/tests/api/chatApi.spec.ts b/frontend/taskdeck-web/src/tests/api/chatApi.spec.ts index 5343934545..e19556b4c5 100644 --- a/frontend/taskdeck-web/src/tests/api/chatApi.spec.ts +++ b/frontend/taskdeck-web/src/tests/api/chatApi.spec.ts @@ -43,6 +43,14 @@ describe('chatApi', () => { }) }) + it('can fail fast for a post-send reconciliation read', async () => { + const failure = new Error('Refresh unavailable') + vi.mocked(http.get).mockRejectedValue(failure) + + await expect(chatApi.getSession('session/1', { skipRetry: true })).rejects.toBe(failure) + expect(http.get).toHaveBeenCalledWith('/llm/chat/sessions/session%2F1', { skipRetry: true }) + }) + it('loads provider health', async () => { const healthPayload = { isAvailable: true, diff --git a/frontend/taskdeck-web/src/tests/composables/useAutomationChat.spec.ts b/frontend/taskdeck-web/src/tests/composables/useAutomationChat.spec.ts index 0dd1fe9a3e..32dc75da46 100644 --- a/frontend/taskdeck-web/src/tests/composables/useAutomationChat.spec.ts +++ b/frontend/taskdeck-web/src/tests/composables/useAutomationChat.spec.ts @@ -544,6 +544,9 @@ describe('useAutomationChat', () => { chat.messageContent.value = 'new instruction' await chat.handleSendMessage() + expect(chatApiMocks.getSession).toHaveBeenNthCalledWith(1, 's1') + expect(chatApiMocks.getSession).toHaveBeenLastCalledWith('s1', { skipRetry: true }) + expect(chat.sendingMessage.value).toBe(false) expect(chat.selectedSession.value?.recentMessages.map((message) => message.content)).toEqual([ 'older instruction', 'No board linked', diff --git a/frontend/taskdeck-web/src/tests/store/chatApi.integration.spec.ts b/frontend/taskdeck-web/src/tests/store/chatApi.integration.spec.ts index 0f2713a9b9..b433f297f9 100644 --- a/frontend/taskdeck-web/src/tests/store/chatApi.integration.spec.ts +++ b/frontend/taskdeck-web/src/tests/store/chatApi.integration.spec.ts @@ -158,7 +158,7 @@ describe('chatApi — integration (mocked HTTP)', () => { expect(result.recentMessages).toHaveLength(2) expect(result.recentMessages[0].role).toBe('User') expect(result.recentMessages[1].role).toBe('Assistant') - expect(http.get).toHaveBeenCalledWith('/llm/chat/sessions/session-1') + expect(http.get).toHaveBeenCalledWith('/llm/chat/sessions/session-1', undefined) }) it('URL-encodes special characters in the session ID', async () => { @@ -166,7 +166,7 @@ describe('chatApi — integration (mocked HTTP)', () => { await chatApi.getSession('session/special') - expect(http.get).toHaveBeenCalledWith('/llm/chat/sessions/session%2Fspecial') + expect(http.get).toHaveBeenCalledWith('/llm/chat/sessions/session%2Fspecial', undefined) }) it('propagates 404 when session does not exist', async () => {