Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions site/src/api/queries/chats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { QueryClient } from "react-query";
import { describe, expect, it, vi } from "vitest";
import { API } from "#/api/api";
import type * as TypesGen from "#/api/typesGenerated";
import { ChatWatchEventKinds } from "#/api/typesGenerated";
import {
ERROR_STATUSES,
SUCCESS_STATUSES,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
invalidateChatListQueries,
invalidateChatMessages,
invalidateChatPrompts,
invalidateChatSearches,
invalidateChatsByWorkspace,
mergeWatchedChatIntoCaches,
mergeWatchedChatSummary,
Expand All @@ -60,6 +62,7 @@ import {
reorderPinnedChat,
setChatGroupRole,
setChatUserRole,
shouldInvalidateChatSearches,
TERMINAL_RUN_STATUSES,
toChatListParams,
unarchiveChat,
Expand Down Expand Up @@ -945,6 +948,7 @@ describe("mutation invalidation scope", () => {
const queryClient = createTestQueryClient();
const chatId = "chat-1";
seedAllActiveQueries(queryClient, chatId);
queryClient.setQueryData(chatSearch({ q: "alpha" }).queryKey, []);

const mutation = createChatMessage(queryClient, chatId);
await mutation.onSuccess?.();
Expand All @@ -956,6 +960,14 @@ describe("mutation invalidation scope", () => {
`${label} should NOT be invalidated by createChatMessage`,
).not.toBe(true);
}
// The send path invalidates searches through
// useChatStore.upsertCacheMessages; doing it here too would
// double-invalidate every send.
expect(
queryClient.getQueryState(chatSearch({ q: "alpha" }).queryKey)
?.isInvalidated,
"chat searches should NOT be invalidated by createChatMessage",
).not.toBe(true);
});

it("createChatMessage invalidates debug runs and chat detail, not messages", async () => {
Expand Down Expand Up @@ -1550,6 +1562,51 @@ describe("mutation invalidation scope", () => {
"chat list should NOT be invalidated",
).not.toBe(true);
});

it.each<{
name: string;
settle: (queryClient: QueryClient) => unknown;
}>([
{
name: "archiveChat onSettled",
settle: (queryClient) =>
archiveChat(queryClient).onSettled(undefined, undefined, "chat-1"),
},
{
name: "unarchiveChat onSettled",
settle: (queryClient) =>
unarchiveChat(queryClient).onSettled(undefined, undefined, "chat-1"),
},
{
name: "updateChatTitle onSettled",
settle: (queryClient) =>
updateChatTitle(queryClient).onSettled(undefined, undefined, {
chatId: "chat-1",
title: "New",
}),
},
{
name: "editChatMessage onSettled",
settle: (queryClient) =>
editChatMessage(queryClient, "chat-1").onSettled(),
},
{
name: "createChat onSuccess",
settle: (queryClient) => createChat(queryClient).onSuccess(),
},
])("$name invalidates chat searches", async ({ settle }) => {
const queryClient = createTestQueryClient();
queryClient.setQueryData(chatSearch({ q: "alpha" }).queryKey, []);

settle(queryClient);
await new Promise((r) => setTimeout(r, 0));

expect(
queryClient.getQueryState(chatSearch({ q: "alpha" }).queryKey)
?.isInvalidated,
"chat search entry should be invalidated",
).toBe(true);
});
});

describe("chatListKey shape", () => {
Expand Down Expand Up @@ -3057,6 +3114,63 @@ describe("semantic cache operations: prefix invalidations", () => {
"messages entry should NOT be invalidated",
).not.toBe(true);
});

it("invalidateChatSearches touches every search entry and nothing outside the family", async () => {
const queryClient = createTestQueryClient();
queryClient.setQueryData(chatSearch({ q: "alpha" }).queryKey, []);
queryClient.setQueryData(chatSearch({ q: "beta" }).queryKey, []);
seedInfiniteChats(queryClient, [makeChat("chat-1")]);
queryClient.setQueryData(chatsByWorkspace(["ws-1"]).queryKey, {});
queryClient.setQueryData(chatEntityKey("chat-1"), makeChat("chat-1"));
queryClient.setQueryData(chatMessagesKey("chat-1"), []);
queryClient.setQueryData(chatCostTreeKey("chat-1"), {});

await invalidateChatSearches(queryClient);

expect(
queryClient.getQueryState(chatSearch({ q: "alpha" }).queryKey)
?.isInvalidated,
).toBe(true);
expect(
queryClient.getQueryState(chatSearch({ q: "beta" }).queryKey)
?.isInvalidated,
).toBe(true);
for (const [label, key] of [
["chat list", infiniteChatsTestKey],
["by-workspace", chatsByWorkspace(["ws-1"]).queryKey],
["chat detail", chatEntityKey("chat-1")],
["messages", chatMessagesKey("chat-1")],
["cost tree", chatCostTreeKey("chat-1")],
] as const) {
expect(
queryClient.getQueryState(key)?.isInvalidated,
`${label} entry should NOT be invalidated`,
).not.toBe(true);
}
});

describe(shouldInvalidateChatSearches.name, () => {
// Search results render title, status, diff status, and the
// action-required badge. Summary and context events are excluded:
// stale last_turn_summary subtitles are accepted until
// reconciliation lands. The created and deleted kinds are handled
// by their own watch branches before the merge path runs.
const expectedByKind: Record<TypesGen.ChatWatchEventKind, boolean> = {
action_required: true,
chat_summary_change: false,
context_dirty: false,
created: false,
deleted: false,
diff_status_change: true,
status_change: true,
summary_change: false,
title_change: true,
};

it.each(ChatWatchEventKinds)("%s", (kind) => {
expect(shouldInvalidateChatSearches(kind)).toBe(expectedByKind[kind]);
});
});
});

describe("semantic cache operations: cancellation", () => {
Expand Down
25 changes: 25 additions & 0 deletions site/src/api/queries/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,26 @@ export const invalidateChatsByWorkspace = (queryClient: QueryClient) =>
queryKey: chatsByWorkspaceFamilyKey,
});

// Watch events that change fields rendered in search results (title,
// status, diff status, action-required badge). Summary events are
// deliberately excluded: stale last_turn_summary subtitles are accepted
// until reconciliation lands.
const SEARCH_AFFECTING_EVENT_KINDS = new Set<TypesGen.ChatWatchEventKind>([
"title_change",
"status_change",
"diff_status_change",
"action_required",
]);

export const shouldInvalidateChatSearches = (
eventKind: TypesGen.ChatWatchEventKind,
): boolean => SEARCH_AFFECTING_EVENT_KINDS.has(eventKind);

export const invalidateChatSearches = (queryClient: QueryClient) =>
queryClient.invalidateQueries({
queryKey: chatSearchFamilyKey,
});

export const invalidateChatDebugRuns = (
queryClient: QueryClient,
chatId: string,
Expand Down Expand Up @@ -993,6 +1013,7 @@ export const archiveChat = (queryClient: QueryClient) => ({
void invalidateChatListQueries(queryClient);
void invalidateChatEntity(queryClient, chatId);
void invalidateChatsByWorkspace(queryClient);
void invalidateChatSearches(queryClient);
},
});

Expand Down Expand Up @@ -1042,6 +1063,7 @@ export const unarchiveChat = (queryClient: QueryClient) => ({
void invalidateChatListQueries(queryClient);
void invalidateChatEntity(queryClient, chatId);
void invalidateChatsByWorkspace(queryClient);
void invalidateChatSearches(queryClient);
},
});

Expand Down Expand Up @@ -1329,6 +1351,7 @@ export const updateChatTitle = (queryClient: QueryClient) => ({
) => {
void invalidateChatListQueries(queryClient);
void invalidateChatEntity(queryClient, chatId);
void invalidateChatSearches(queryClient);
},
});

Expand Down Expand Up @@ -1409,6 +1432,7 @@ export const createChat = (queryClient: QueryClient) => ({
onSuccess: () => {
void invalidateChatListQueries(queryClient);
void invalidateChatsByWorkspace(queryClient);
void invalidateChatSearches(queryClient);
},
});

Expand Down Expand Up @@ -1501,6 +1525,7 @@ export const editChatMessage = (queryClient: QueryClient, chatId: string) => ({
void invalidateChatEntity(queryClient, chatId);
void invalidateChatPrompts(queryClient, chatId);
void invalidateChatDebugRuns(queryClient, chatId);
void invalidateChatSearches(queryClient);
},
});

Expand Down
10 changes: 10 additions & 0 deletions site/src/pages/AgentsPage/AgentsPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
invalidateChatDiffContents,
invalidateChatEntity,
invalidateChatListQueries,
invalidateChatSearches,
invalidateChatsByWorkspace,
mergeWatchedChatIntoCaches,
pinChat,
Expand All @@ -38,6 +39,7 @@ import {
removeChatEntity,
removeChildFromParentInCache,
reorderPinnedChat,
shouldInvalidateChatSearches,
unarchiveChat,
unpinChat,
updateChatTitle,
Expand Down Expand Up @@ -308,6 +310,7 @@ const AgentsPageLayout: FC = () => {
void invalidateChatListQueries(queryClient);
void invalidateChatEntity(queryClient, chatId);
void invalidateChatsByWorkspace(queryClient);
void invalidateChatSearches(queryClient);
void invalidateWorkspaceMutationQueries(queryClient, {
organizationName,
username: user.username,
Expand Down Expand Up @@ -576,6 +579,7 @@ const AgentsPageLayout: FC = () => {
return changed ? next : chats;
});
void invalidateChatListQueries(queryClient);
void invalidateChatSearches(queryClient);
}, [agentId, queryClient]);
useEffect(() => {
return createReconnectingWebSocket({
Expand Down Expand Up @@ -615,6 +619,7 @@ const AgentsPageLayout: FC = () => {
);
removeChildFromParentInCache(queryClient, updatedChat.id);
removeChatEntity(queryClient, updatedChat.id);
void invalidateChatSearches(queryClient);
return;
}
if (chatEvent.kind === "diff_status_change") {
Expand Down Expand Up @@ -650,6 +655,7 @@ const AgentsPageLayout: FC = () => {
} else {
prependToInfiniteChatsCache(queryClient, updatedChat);
void invalidateChatListQueries(queryClient);
void invalidateChatSearches(queryClient);
}
} else {
mergeWatchedChatIntoCaches(queryClient, updatedChat, {
Expand All @@ -659,6 +665,9 @@ const AgentsPageLayout: FC = () => {
if (shouldInvalidateFilteredChatList(updatedChat, chatEvent.kind)) {
void invalidateChatListQueries(queryClient);
}
if (shouldInvalidateChatSearches(chatEvent.kind)) {
void invalidateChatSearches(queryClient);
Comment thread
DanielleMaywood marked this conversation as resolved.
}
const costChatId = chatCostIdToInvalidate(
updatedChat,
chatEvent.kind,
Expand All @@ -681,6 +690,7 @@ const AgentsPageLayout: FC = () => {
},
onOpen() {
void invalidateChatListQueries(queryClient);
void invalidateChatSearches(queryClient);
},
});
}, [queryClient]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { watchChat } from "#/api/api";
import {
chatMessagesKey,
invalidateChatPrompts,
invalidateChatSearches,
patchChatMessages,
updateInfiniteChatsCache,
} from "#/api/queries/chats";
Expand Down Expand Up @@ -234,6 +235,7 @@ export const useChatStore = (
if (hasNewUserPrompt) {
void invalidateChatPrompts(queryClient, chatID);
}
void invalidateChatSearches(queryClient);
},
[chatID, queryClient],
);
Expand All @@ -255,6 +257,7 @@ export const useChatStore = (
pageParams: currentData.pageParams.slice(0, 1),
};
});
void invalidateChatSearches(queryClient);
},
[chatID, queryClient],
);
Expand Down
Loading