From b7eab35734ae183c6b6c55cd5594334edddccf7d Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 16 Mar 2026 11:23:56 -0700 Subject: [PATCH] fix(site): scope chat cache helpers to chat-list queries only (#23134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `updateInfiniteChatsCache`, `prependToInfiniteChatsCache`, and `readInfiniteChatsCache` use `setQueriesData({ queryKey: ["chats"] })` which prefix-matches **all** queries starting with `"chats"`, including `["chats", chatId, "messages"]`. After #23083 converted chat messages to `useInfiniteQuery`, the cached messages data gained a `.pages` property containing `ChatMessagesResponse` objects (not `Chat[]` arrays). The `if (!prev.pages)` guard no longer bailed out, and the updater called `.map()` on these objects — `TypeError: Z.map is not a function`. ## Fix Extract the `isChatListQuery` predicate that already existed inline in `invalidateChatListQueries` and apply it to all four cache helpers. This scopes them to sidebar queries (`["chats"]` or `["chats", ]`) and skips per-chat queries (`["chats", , ...]`). --- site/src/api/queries/chats.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/site/src/api/queries/chats.ts b/site/src/api/queries/chats.ts index 5c69696ccc..49500c55e5 100644 --- a/site/src/api/queries/chats.ts +++ b/site/src/api/queries/chats.ts @@ -20,7 +20,7 @@ export const updateInfiniteChatsCache = ( queryClient.setQueriesData<{ pages: TypesGen.Chat[][]; pageParams: unknown[]; - }>({ queryKey: chatsKey }, (prev) => { + }>({ queryKey: chatsKey, predicate: isChatListQuery }, (prev) => { if (!prev) return prev; if (!prev.pages) return prev; const nextPages = prev.pages.map((page) => updater(page)); @@ -44,7 +44,7 @@ export const prependToInfiniteChatsCache = ( queryClient.setQueriesData<{ pages: TypesGen.Chat[][]; pageParams: unknown[]; - }>({ queryKey: chatsKey }, (prev) => { + }>({ queryKey: chatsKey, predicate: isChatListQuery }, (prev) => { if (!prev?.pages) return prev; // Check across ALL pages to avoid duplicates. const exists = prev.pages.some((page) => @@ -69,7 +69,7 @@ export const readInfiniteChatsCache = ( const queries = queryClient.getQueriesData<{ pages: TypesGen.Chat[][]; pageParams: unknown[]; - }>({ queryKey: chatsKey }); + }>({ queryKey: chatsKey, predicate: isChatListQuery }); for (const [, data] of queries) { if (data?.pages) { return data.pages.flat(); @@ -80,24 +80,27 @@ export const readInfiniteChatsCache = ( /** * Invalidate only the sidebar chat-list queries (flat + infinite) - * without cascading to per-chat queries (detail, messages, diffs, cost). +/** + * Predicate that matches only chat-list queries (the sidebar), not + * per-chat queries (detail, messages, diffs, cost). * * Sidebar keys look like ["chats"] or ["chats", ]. - * Per-chat keys look like ["chats", , ...]. The predicate - * distinguishes them by allowlisting known sidebar key shapes. + * Per-chat keys look like ["chats", , ...]. */ +const isChatListQuery = (query: { queryKey: readonly unknown[] }): boolean => { + const key = query.queryKey; + // Match: ["chats"] (flat list). + if (key.length <= 1) return true; + // Match: ["chats", ] (infinite query + // with optional filter opts like {archived, q}). + const segment = key[1]; + return segment === undefined || typeof segment === "object"; +}; + export const invalidateChatListQueries = (queryClient: QueryClient) => { return queryClient.invalidateQueries({ queryKey: chatsKey, - predicate: (query) => { - const key = query.queryKey; - // Match: ["chats"] (flat list). - if (key.length <= 1) return true; - // Match: ["chats", ] (infinite query - // with optional filter opts like {archived, q}). - const segment = key[1]; - return segment === undefined || typeof segment === "object"; - }, + predicate: isChatListQuery, }); };