fix(site): scope chat cache helpers to chat-list queries only (#23134)

## 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",
<filterOpts>]`) and skips per-chat queries (`["chats", <id>, ...]`).
This commit is contained in:
Kyle Carberry
2026-03-16 14:23:56 -04:00
committed by GitHub
parent 3f76f312e4
commit b7eab35734
+18 -15
View File
@@ -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", <object|undefined>].
* Per-chat keys look like ["chats", <string-id>, ...]. The predicate
* distinguishes them by allowlisting known sidebar key shapes.
* Per-chat keys look like ["chats", <string-id>, ...].
*/
const isChatListQuery = (query: { queryKey: readonly unknown[] }): boolean => {
const key = query.queryKey;
// Match: ["chats"] (flat list).
if (key.length <= 1) return true;
// Match: ["chats", <object | undefined>] (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", <object | undefined>] (infinite query
// with optional filter opts like {archived, q}).
const segment = key[1];
return segment === undefined || typeof segment === "object";
},
predicate: isChatListQuery,
});
};