diff --git a/site/src/api/queries/chats.ts b/site/src/api/queries/chats.ts index 76e1c64010..6ced2b384f 100644 --- a/site/src/api/queries/chats.ts +++ b/site/src/api/queries/chats.ts @@ -30,6 +30,35 @@ export const updateInfiniteChatsCache = ( }); }; +/** + * Prepends a new chat to the first page of every infinite chats query + * in the cache, but only if the chat doesn't already exist in any + * page. This avoids the per-page duplication that would occur if + * a prepend updater were passed to updateInfiniteChatsCache, which + * runs independently on each page. + */ +export const prependToInfiniteChatsCache = ( + queryClient: QueryClient, + chat: TypesGen.Chat, +) => { + queryClient.setQueriesData<{ + pages: TypesGen.Chat[][]; + pageParams: unknown[]; + }>({ queryKey: chatsKey }, (prev) => { + if (!prev?.pages) return prev; + // Check across ALL pages to avoid duplicates. + const exists = prev.pages.some((page) => + page.some((c) => c.id === chat.id), + ); + if (exists) return prev; + // Only prepend to the first page. + const nextPages = prev.pages.map((page, i) => + i === 0 ? [chat, ...page] : page, + ); + return { ...prev, pages: nextPages }; + }); +}; + /** * Reads the flat list of chats from the first matching infinite query * in the cache. Returns undefined when no data is cached yet. diff --git a/site/src/pages/AgentsPage/AgentsPage.tsx b/site/src/pages/AgentsPage/AgentsPage.tsx index d96d31dc0e..eb28894fec 100644 --- a/site/src/pages/AgentsPage/AgentsPage.tsx +++ b/site/src/pages/AgentsPage/AgentsPage.tsx @@ -10,6 +10,7 @@ import { chatsKey, createChat, infiniteChats, + prependToInfiniteChatsCache, readInfiniteChatsCache, unarchiveChat, updateInfiniteChatsCache, @@ -405,9 +406,15 @@ const AgentsPage: FC = () => { const isTitleEvent = chatEvent.kind === "title_change"; const isStatusEvent = chatEvent.kind === "status_change"; - updateInfiniteChatsCache(queryClient, (chats) => { - const exists = chats.some((c) => c.id === updatedChat.id); - if (exists) { + // For "created" events, use a cross-page existence + // check and prepend only to the first page. + // updateInfiniteChatsCache runs the updater per + // page, so a naive prepend would duplicate the + // chat into every loaded page. + if (chatEvent.kind === "created") { + prependToInfiniteChatsCache(queryClient, updatedChat); + } else { + updateInfiniteChatsCache(queryClient, (chats) => { return chats.map((c) => { if (c.id !== updatedChat.id) return c; return { @@ -420,12 +427,8 @@ const AgentsPage: FC = () => { : updatedChat.updated_at, }; }); - } - if (chatEvent.kind === "created") { - return [updatedChat, ...chats]; - } - return chats; - }); + }); + } queryClient.setQueryData( chatKey(updatedChat.id), (previousChat) => {