From c4b658b8500640f7ad104926f87ad51ad15f1a85 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 5 Aug 2026 11:58:23 +0000 Subject: [PATCH 1/3] fix(site/src/pages/AgentsPage): show error state with retry when chat fetch fails The chat detail and initial messages queries rendered "Chat not found" for any failure, making a transport error indistinguishable from a genuine 404. Render an error view with a retry button when either query fails without data; reserve the not-found view for a real 404 from the detail endpoint. --- .../AgentsPage/AgentChatPage.stories.tsx | 138 ++++++++++++++++++ site/src/pages/AgentsPage/AgentChatPage.tsx | 37 +++++ .../pages/AgentsPage/AgentChatPageView.tsx | 55 ++++++- 3 files changed, 227 insertions(+), 3 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx index 7364b71aa42..d75df494bc6 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx @@ -3059,6 +3059,144 @@ export const SendResponseAfterChatSwitch: Story = { }, }; +// --------------------------------------------------------------------------- +// Query failure states: a transport failure must render an error view with +// retry, never "Chat not found". Only a 404 from the detail endpoint means +// the chat is genuinely absent. +// --------------------------------------------------------------------------- + +const minimalChat: TypesGen.Chat = { + id: CHAT_ID, + ...baseChatFields, + title: "Failing chat", + status: "waiting", +}; + +const minimalMessages: TypesGen.ChatMessagesResponse = { + messages: [], + queued_messages: [], + has_more: false, +}; + +const serverError = { + isAxiosError: true, + response: { + status: 500, + data: { message: "Internal server error." }, + }, +}; + +// Spy cleanup matters here: an invalidated query from an earlier story's +// play function can refetch after that story unmounted, through whatever +// spy is still registered. Returning restore functions from beforeEach +// keeps each story's mocks scoped to that story. +const mockChatFetchError = (error: unknown) => { + const getChatSpy = spyOn(API.experimental, "getChat").mockImplementation( + (chatId) => + chatId === CHAT_ID ? Promise.reject(error) : Promise.resolve(minimalChat), + ); + const getChatMessagesSpy = spyOn( + API.experimental, + "getChatMessages", + ).mockResolvedValue(minimalMessages); + return () => { + getChatSpy.mockRestore(); + getChatMessagesSpy.mockRestore(); + }; +}; + +/** The detail query fails with a 500: error view, not "Chat not found". */ +export const DetailQueryError: Story = { + beforeEach: () => mockChatFetchError(serverError), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(await canvas.findByText("Failed to load chat")).toBeVisible(); + expect(canvas.queryByText("Chat not found")).not.toBeInTheDocument(); + expect( + canvas.getByRole("button", { name: "Try again" }), + ).toBeInTheDocument(); + }, +}; + +/** The initial messages query fails: error view, not "Chat not found". */ +export const InitialMessagesError: Story = { + beforeEach: () => { + const getChatSpy = spyOn(API.experimental, "getChat").mockResolvedValue( + minimalChat, + ); + const getChatMessagesSpy = spyOn( + API.experimental, + "getChatMessages", + ).mockImplementation((chatId) => + chatId === CHAT_ID + ? Promise.reject(serverError) + : Promise.resolve(minimalMessages), + ); + return () => { + getChatSpy.mockRestore(); + getChatMessagesSpy.mockRestore(); + }; + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(await canvas.findByText("Failed to load chat")).toBeVisible(); + expect(canvas.queryByText("Chat not found")).not.toBeInTheDocument(); + }, +}; + +/** Clicking retry refetches both queries and recovers the chat. */ +export const ErrorRetryRecovers: Story = { + beforeEach: ({ parameters }) => { + let shouldFail = true; + const getChatSpy = spyOn(API.experimental, "getChat").mockImplementation( + (chatId) => { + if (chatId === CHAT_ID && shouldFail) { + shouldFail = false; + return Promise.reject(serverError); + } + return Promise.resolve(minimalChat); + }, + ); + parameters.getChatCallsForChat = () => + getChatSpy.mock.calls.filter(([chatId]) => chatId === CHAT_ID).length; + const getChatMessagesSpy = spyOn( + API.experimental, + "getChatMessages", + ).mockResolvedValue(minimalMessages); + return () => { + getChatSpy.mockRestore(); + getChatMessagesSpy.mockRestore(); + }; + }, + play: async ({ canvasElement, parameters }) => { + const canvas = within(canvasElement); + expect(await canvas.findByText("Failed to load chat")).toBeVisible(); + await userEvent.click(canvas.getByRole("button", { name: "Try again" })); + await waitFor(() => { + expect(canvas.queryByText("Failed to load chat")).not.toBeInTheDocument(); + }); + expect(canvas.queryByText("Chat not found")).not.toBeInTheDocument(); + expect(parameters.getChatCallsForChat()).toBeGreaterThanOrEqual(2); + }, +}; + +/** A genuine 404 from the detail endpoint still renders "Chat not found". */ +export const ChatNotFound: Story = { + beforeEach: () => + mockChatFetchError({ + isAxiosError: true, + response: { + status: 404, + data: { message: "Chat not found." }, + }, + }), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(await canvas.findByText("Chat not found")).toBeVisible(); + expect(canvas.queryByText("Failed to load chat")).not.toBeInTheDocument(); + }, +}; + export const SendRejectedByHookDispatchFailure: Story = { parameters: { queries: buildQueries( diff --git a/site/src/pages/AgentsPage/AgentChatPage.tsx b/site/src/pages/AgentsPage/AgentChatPage.tsx index 7a4ef233e7a..e6ea553fd8d 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.tsx @@ -1,3 +1,5 @@ +import { isAxiosError } from "axios"; + import { type FC, useEffect, @@ -69,6 +71,7 @@ import { pageTitle } from "#/utils/page"; import { rewriteLocalhostURL } from "#/utils/portForward"; import { createReconnectingWebSocket } from "#/utils/reconnectingWebSocket"; import { + AgentChatPageErrorView, AgentChatPageLoadingView, AgentChatPageNotFoundView, AgentChatPageView, @@ -1881,6 +1884,40 @@ const AgentChatPage: FC = () => { ); } + // The detail endpoint throws on 404, so only treat a query failure as + // "not found" when the server actually said the chat is gone. Anything + // else (network, 5xx, timeout) is a retriable transport failure. This + // only applies when the query has no data to show; a background refetch + // that fails after content already rendered must not blank the page. + if ( + (chatQuery.isError && chatQuery.data === undefined) || + (chatMessagesQuery.isError && chatMessagesQuery.data === undefined) + ) { + const chatNotFoundError = + isAxiosError(chatQuery.error) && chatQuery.error.response?.status === 404; + if (chatNotFoundError) { + return ( + + ); + } + return ( + { + void chatQuery.refetch(); + void chatMessagesQuery.refetch(); + }} + /> + ); + } + if (!chatQuery.data || !chatMessagesQuery.data?.pages?.length || !agentId) { return ( = ({ ); }; -interface AgentChatPageNotFoundViewProps { +interface AgentChatPageStatusViewProps { titleElement: React.ReactNode; isSidebarCollapsed: boolean; onToggleSidebarCollapsed: () => void; + children: React.ReactNode; } -export const AgentChatPageNotFoundView: FC = ({ +const AgentChatPageStatusView: FC = ({ titleElement, isSidebarCollapsed, onToggleSidebarCollapsed, + children, }) => { return (
@@ -1180,8 +1183,54 @@ export const AgentChatPageNotFoundView: FC = ({ onToggleSidebarCollapsed={onToggleSidebarCollapsed} />
- Chat not found + {children}
); }; + +interface AgentChatPageNotFoundViewProps { + titleElement: React.ReactNode; + isSidebarCollapsed: boolean; + onToggleSidebarCollapsed: () => void; +} + +export const AgentChatPageNotFoundView: FC = ( + props, +) => { + return ( + Chat not found + ); +}; + +interface AgentChatPageErrorViewProps { + titleElement: React.ReactNode; + isSidebarCollapsed: boolean; + onToggleSidebarCollapsed: () => void; + error: unknown; + onRetry: () => void; +} + +export const AgentChatPageErrorView: FC = ({ + error, + onRetry, + ...statusViewProps +}) => { + const message = + error instanceof Error ? error.message : "The chat could not be loaded."; + return ( + +
+
+

+ Failed to load chat +

+

{message}

+
+ +
+
+ ); +}; From 2b619f2d0dc289d66b99f997e9f84738533c6cf1 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 5 Aug 2026 12:21:36 +0000 Subject: [PATCH 2/3] fix(site/src/pages/AgentsPage): simplify chat fetch errors --- .../AgentsPage/AgentChatPage.stories.tsx | 127 +++++++----------- site/src/pages/AgentsPage/AgentChatPage.tsx | 33 ++--- .../AgentsPage/AgentChatPageErrorView.tsx | 60 +++++++++ .../pages/AgentsPage/AgentChatPageView.tsx | 55 +------- 4 files changed, 126 insertions(+), 149 deletions(-) create mode 100644 site/src/pages/AgentsPage/AgentChatPageErrorView.tsx diff --git a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx index d75df494bc6..512448f59dd 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import type { FC } from "react"; import { useRef } from "react"; +import { hashKey } from "react-query"; import { Outlet, useNavigate } from "react-router"; import { expect, spyOn, userEvent, waitFor, within } from "storybook/test"; import { @@ -22,6 +23,7 @@ import { import { workspaceByIdKey } from "#/api/queries/workspaces"; import type * as TypesGen from "#/api/typesGenerated"; import { + MockChat, MockChatMessage, MockChatQueuedMessage, } from "#/testHelpers/chatEntities"; @@ -32,6 +34,7 @@ import { MockOrganizationMember2, MockUserOwner, MockWorkspace, + mockApiError, } from "#/testHelpers/entities"; import { withAuthProvider, @@ -3059,55 +3062,39 @@ export const SendResponseAfterChatSwitch: Story = { }, }; -// --------------------------------------------------------------------------- -// Query failure states: a transport failure must render an error view with -// retry, never "Chat not found". Only a 404 from the detail endpoint means -// the chat is genuinely absent. -// --------------------------------------------------------------------------- - -const minimalChat: TypesGen.Chat = { +const queryErrorChat: TypesGen.Chat = { + ...MockChat, id: CHAT_ID, ...baseChatFields, title: "Failing chat", - status: "waiting", }; -const minimalMessages: TypesGen.ChatMessagesResponse = { +const queryErrorMessages: TypesGen.ChatMessagesResponse = { messages: [], queued_messages: [], has_more: false, }; const serverError = { - isAxiosError: true, - response: { - status: 500, - data: { message: "Internal server error." }, - }, + ...mockApiError({ message: "Internal server error." }), + status: 500, }; -// Spy cleanup matters here: an invalidated query from an earlier story's -// play function can refetch after that story unmounted, through whatever -// spy is still registered. Returning restore functions from beforeEach -// keeps each story's mocks scoped to that story. -const mockChatFetchError = (error: unknown) => { - const getChatSpy = spyOn(API.experimental, "getChat").mockImplementation( - (chatId) => - chatId === CHAT_ID ? Promise.reject(error) : Promise.resolve(minimalChat), - ); - const getChatMessagesSpy = spyOn( - API.experimental, - "getChatMessages", - ).mockResolvedValue(minimalMessages); - return () => { - getChatSpy.mockRestore(); - getChatMessagesSpy.mockRestore(); - }; -}; +const withoutQuery = ( + queries: ReturnType, + queryKey: readonly unknown[], +) => queries.filter(({ key }) => hashKey(key) !== hashKey(queryKey)); -/** The detail query fails with a 500: error view, not "Chat not found". */ export const DetailQueryError: Story = { - beforeEach: () => mockChatFetchError(serverError), + parameters: { + queries: withoutQuery( + buildQueries(queryErrorChat, queryErrorMessages), + chatKey(CHAT_ID), + ), + }, + beforeEach: () => { + spyOn(API.experimental, "getChat").mockRejectedValue(serverError); + }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(await canvas.findByText("Failed to load chat")).toBeVisible(); @@ -3118,24 +3105,15 @@ export const DetailQueryError: Story = { }, }; -/** The initial messages query fails: error view, not "Chat not found". */ export const InitialMessagesError: Story = { + parameters: { + queries: withoutQuery( + buildQueries(queryErrorChat, queryErrorMessages), + chatMessagesKey(CHAT_ID), + ), + }, beforeEach: () => { - const getChatSpy = spyOn(API.experimental, "getChat").mockResolvedValue( - minimalChat, - ); - const getChatMessagesSpy = spyOn( - API.experimental, - "getChatMessages", - ).mockImplementation((chatId) => - chatId === CHAT_ID - ? Promise.reject(serverError) - : Promise.resolve(minimalMessages), - ); - return () => { - getChatSpy.mockRestore(); - getChatMessagesSpy.mockRestore(); - }; + spyOn(API.experimental, "getChatMessages").mockRejectedValue(serverError); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -3144,29 +3122,19 @@ export const InitialMessagesError: Story = { }, }; -/** Clicking retry refetches both queries and recovers the chat. */ export const ErrorRetryRecovers: Story = { + parameters: { + queries: withoutQuery( + buildQueries(queryErrorChat, queryErrorMessages), + chatKey(CHAT_ID), + ), + }, beforeEach: ({ parameters }) => { - let shouldFail = true; - const getChatSpy = spyOn(API.experimental, "getChat").mockImplementation( - (chatId) => { - if (chatId === CHAT_ID && shouldFail) { - shouldFail = false; - return Promise.reject(serverError); - } - return Promise.resolve(minimalChat); - }, - ); + const getChatSpy = spyOn(API.experimental, "getChat") + .mockRejectedValueOnce(serverError) + .mockResolvedValue(queryErrorChat); parameters.getChatCallsForChat = () => getChatSpy.mock.calls.filter(([chatId]) => chatId === CHAT_ID).length; - const getChatMessagesSpy = spyOn( - API.experimental, - "getChatMessages", - ).mockResolvedValue(minimalMessages); - return () => { - getChatSpy.mockRestore(); - getChatMessagesSpy.mockRestore(); - }; }, play: async ({ canvasElement, parameters }) => { const canvas = within(canvasElement); @@ -3180,16 +3148,19 @@ export const ErrorRetryRecovers: Story = { }, }; -/** A genuine 404 from the detail endpoint still renders "Chat not found". */ export const ChatNotFound: Story = { - beforeEach: () => - mockChatFetchError({ - isAxiosError: true, - response: { - status: 404, - data: { message: "Chat not found." }, - }, - }), + parameters: { + queries: withoutQuery( + buildQueries(queryErrorChat, queryErrorMessages), + chatKey(CHAT_ID), + ), + }, + beforeEach: () => { + spyOn(API.experimental, "getChat").mockRejectedValue({ + ...mockApiError({ message: "Chat not found." }), + status: 404, + }); + }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(await canvas.findByText("Chat not found")).toBeVisible(); diff --git a/site/src/pages/AgentsPage/AgentChatPage.tsx b/site/src/pages/AgentsPage/AgentChatPage.tsx index e6ea553fd8d..8242f78d5ae 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.tsx @@ -1,5 +1,3 @@ -import { isAxiosError } from "axios"; - import { type FC, useEffect, @@ -24,7 +22,7 @@ import { type CreateChatMessageRequestWithClearablePlanMode, watchWorkspace, } from "#/api/api"; -import { getErrorMessage, isApiError } from "#/api/errors"; +import { getErrorMessage, getErrorStatus, isApiError } from "#/api/errors"; import { checkAuthorization } from "#/api/queries/authCheck"; import { buildOptimisticEditedMessage } from "#/api/queries/chatMessageEdits"; import { @@ -70,8 +68,8 @@ import { isMobileViewport } from "#/utils/mobile"; import { pageTitle } from "#/utils/page"; import { rewriteLocalhostURL } from "#/utils/portForward"; import { createReconnectingWebSocket } from "#/utils/reconnectingWebSocket"; +import { AgentChatPageErrorView } from "./AgentChatPageErrorView"; import { - AgentChatPageErrorView, AgentChatPageLoadingView, AgentChatPageNotFoundView, AgentChatPageView, @@ -1884,18 +1882,8 @@ const AgentChatPage: FC = () => { ); } - // The detail endpoint throws on 404, so only treat a query failure as - // "not found" when the server actually said the chat is gone. Anything - // else (network, 5xx, timeout) is a retriable transport failure. This - // only applies when the query has no data to show; a background refetch - // that fails after content already rendered must not blank the page. - if ( - (chatQuery.isError && chatQuery.data === undefined) || - (chatMessagesQuery.isError && chatMessagesQuery.data === undefined) - ) { - const chatNotFoundError = - isAxiosError(chatQuery.error) && chatQuery.error.response?.status === 404; - if (chatNotFoundError) { + if (chatQuery.isLoadingError || chatMessagesQuery.isLoadingError) { + if (getErrorStatus(chatQuery.error) === 404) { return ( { /> ); } + return ( { - void chatQuery.refetch(); - void chatMessagesQuery.refetch(); + if (chatQuery.isLoadingError) { + void chatQuery.refetch(); + } + if (chatMessagesQuery.isLoadingError) { + void chatMessagesQuery.refetch(); + } }} /> ); diff --git a/site/src/pages/AgentsPage/AgentChatPageErrorView.tsx b/site/src/pages/AgentsPage/AgentChatPageErrorView.tsx new file mode 100644 index 00000000000..0972f38f3ff --- /dev/null +++ b/site/src/pages/AgentsPage/AgentChatPageErrorView.tsx @@ -0,0 +1,60 @@ +import { RotateCcwIcon } from "lucide-react"; +import type { FC, ReactNode } from "react"; +import { getErrorDetail, getErrorMessage } from "#/api/errors"; +import { Button } from "#/components/Button/Button"; +import { ChatTopBar } from "./components/ChatTopBar"; + +interface AgentChatPageErrorViewProps { + titleElement: ReactNode; + isSidebarCollapsed: boolean; + onToggleSidebarCollapsed: () => void; + error: unknown; + onRetry: () => void; +} + +export const AgentChatPageErrorView: FC = ({ + titleElement, + isSidebarCollapsed, + onToggleSidebarCollapsed, + error, + onRetry, +}) => { + const detail = getErrorDetail(error); + + return ( +
+ {titleElement} + {}, + }} + onArchiveAgent={() => {}} + onUnarchiveAgent={() => {}} + onArchiveAndDeleteWorkspace={() => {}} + hasWorkspace={false} + isSidebarCollapsed={isSidebarCollapsed} + onToggleSidebarCollapsed={onToggleSidebarCollapsed} + /> +
+
+

+ Failed to load chat +

+

+ {getErrorMessage(error, "The chat could not be loaded.")} +

+ {detail && ( +

+ {detail} +

+ )} + +
+
+
+ ); +}; diff --git a/site/src/pages/AgentsPage/AgentChatPageView.tsx b/site/src/pages/AgentsPage/AgentChatPageView.tsx index 464154296df..bb772d3dba3 100644 --- a/site/src/pages/AgentsPage/AgentChatPageView.tsx +++ b/site/src/pages/AgentsPage/AgentChatPageView.tsx @@ -18,7 +18,6 @@ import type { ChatDiffStatus, ChatMessagePart, } from "#/api/typesGenerated"; -import { Button } from "#/components/Button/Button"; import { useProxy } from "#/contexts/ProxyContext"; import { isWorkspaceAppEmbeddable } from "#/modules/apps/apps"; import { WorkspaceAppFrame } from "#/modules/apps/WorkspaceAppFrame"; @@ -1154,18 +1153,16 @@ export const AgentChatPageLoadingView: FC = ({ ); }; -interface AgentChatPageStatusViewProps { +interface AgentChatPageNotFoundViewProps { titleElement: React.ReactNode; isSidebarCollapsed: boolean; onToggleSidebarCollapsed: () => void; - children: React.ReactNode; } -const AgentChatPageStatusView: FC = ({ +export const AgentChatPageNotFoundView: FC = ({ titleElement, isSidebarCollapsed, onToggleSidebarCollapsed, - children, }) => { return (
@@ -1183,54 +1180,8 @@ const AgentChatPageStatusView: FC = ({ onToggleSidebarCollapsed={onToggleSidebarCollapsed} />
- {children} + Chat not found
); }; - -interface AgentChatPageNotFoundViewProps { - titleElement: React.ReactNode; - isSidebarCollapsed: boolean; - onToggleSidebarCollapsed: () => void; -} - -export const AgentChatPageNotFoundView: FC = ( - props, -) => { - return ( - Chat not found - ); -}; - -interface AgentChatPageErrorViewProps { - titleElement: React.ReactNode; - isSidebarCollapsed: boolean; - onToggleSidebarCollapsed: () => void; - error: unknown; - onRetry: () => void; -} - -export const AgentChatPageErrorView: FC = ({ - error, - onRetry, - ...statusViewProps -}) => { - const message = - error instanceof Error ? error.message : "The chat could not be loaded."; - return ( - -
-
-

- Failed to load chat -

-

{message}

-
- -
-
- ); -}; From d5155b95970615206aea0823657b1e37b77cc273 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 5 Aug 2026 12:32:55 +0000 Subject: [PATCH 3/3] refactor(site/src/pages/AgentsPage): rename error story consts and inline empty messages --- .../AgentsPage/AgentChatPage.stories.tsx | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx index 512448f59dd..eb9f1f631bf 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx @@ -3062,20 +3062,14 @@ export const SendResponseAfterChatSwitch: Story = { }, }; -const queryErrorChat: TypesGen.Chat = { +const mockErrorChat: TypesGen.Chat = { ...MockChat, id: CHAT_ID, ...baseChatFields, title: "Failing chat", }; -const queryErrorMessages: TypesGen.ChatMessagesResponse = { - messages: [], - queued_messages: [], - has_more: false, -}; - -const serverError = { +const mockServerError = { ...mockApiError({ message: "Internal server error." }), status: 500, }; @@ -3088,12 +3082,16 @@ const withoutQuery = ( export const DetailQueryError: Story = { parameters: { queries: withoutQuery( - buildQueries(queryErrorChat, queryErrorMessages), + buildQueries(mockErrorChat, { + messages: [], + queued_messages: [], + has_more: false, + }), chatKey(CHAT_ID), ), }, beforeEach: () => { - spyOn(API.experimental, "getChat").mockRejectedValue(serverError); + spyOn(API.experimental, "getChat").mockRejectedValue(mockServerError); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -3108,12 +3106,18 @@ export const DetailQueryError: Story = { export const InitialMessagesError: Story = { parameters: { queries: withoutQuery( - buildQueries(queryErrorChat, queryErrorMessages), + buildQueries(mockErrorChat, { + messages: [], + queued_messages: [], + has_more: false, + }), chatMessagesKey(CHAT_ID), ), }, beforeEach: () => { - spyOn(API.experimental, "getChatMessages").mockRejectedValue(serverError); + spyOn(API.experimental, "getChatMessages").mockRejectedValue( + mockServerError, + ); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -3125,14 +3129,18 @@ export const InitialMessagesError: Story = { export const ErrorRetryRecovers: Story = { parameters: { queries: withoutQuery( - buildQueries(queryErrorChat, queryErrorMessages), + buildQueries(mockErrorChat, { + messages: [], + queued_messages: [], + has_more: false, + }), chatKey(CHAT_ID), ), }, beforeEach: ({ parameters }) => { const getChatSpy = spyOn(API.experimental, "getChat") - .mockRejectedValueOnce(serverError) - .mockResolvedValue(queryErrorChat); + .mockRejectedValueOnce(mockServerError) + .mockResolvedValue(mockErrorChat); parameters.getChatCallsForChat = () => getChatSpy.mock.calls.filter(([chatId]) => chatId === CHAT_ID).length; }, @@ -3151,7 +3159,11 @@ export const ErrorRetryRecovers: Story = { export const ChatNotFound: Story = { parameters: { queries: withoutQuery( - buildQueries(queryErrorChat, queryErrorMessages), + buildQueries(mockErrorChat, { + messages: [], + queued_messages: [], + has_more: false, + }), chatKey(CHAT_ID), ), },