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
Show all changes
35 commits
Select commit Hold shift + click to select a range
dabecc2
feat(site): surface chat lifecycle hook outcomes in the chats UI
ibetitsmike Jul 23, 2026
2f645c9
fix(site): make queued hook notices keyboard-accessible and keep prom…
ibetitsmike Jul 23, 2026
d0e0ad8
fix(site): hide the inactive sticky message copy from assistive tech
ibetitsmike Jul 23, 2026
125563d
fix(site): refresh chat state after promoted sends and failed edits
ibetitsmike Jul 23, 2026
009182d
fix(site): drop avoidable cast in the HookNotice story
ibetitsmike Jul 23, 2026
b8eabe1
fix(site): keep suppressed queued messages out of the query cache
ibetitsmike Jul 23, 2026
aa40360
fix(site): drop manual useCallback in the compiler-managed chat store…
ibetitsmike Jul 23, 2026
9128684
fix(site): thread urlTransform through sticky user message hook notices
ibetitsmike Jul 23, 2026
cdf442e
style(site/src/pages/AgentsPage): remove redundant test comments
ibetitsmike Jul 24, 2026
aa1a656
test(site/src/pages/AgentsPage): cover promoted queued sends with an …
ibetitsmike Jul 24, 2026
2b47978
fix(site): surface tool error reasons for denied and failed tool calls
ibetitsmike Jul 24, 2026
6420a24
fix(site): surface edit_files failures and cover the execute errorTex…
ibetitsmike Jul 24, 2026
f4fc28d
fix(site): align hook denial story fixtures with the external policy …
ibetitsmike Jul 24, 2026
31062df
fix(site): align hook denial fixtures with the not-executed wording
ibetitsmike Jul 24, 2026
36451f3
fix(site): make dimmed timeline messages inert during edits
ibetitsmike Jul 25, 2026
49c77c4
fix(site/src/pages/AgentsPage): ignore stale queue snapshots after a …
ibetitsmike Jul 25, 2026
b3add47
test(site/src/pages/AgentsPage): scope promotion story assertions and…
ibetitsmike Jul 25, 2026
bfe35e8
style(site/src/pages/AgentsPage): tighten hook queue and story comments
ibetitsmike Jul 25, 2026
4bf3f89
fix(site/src/pages/AgentsPage): keep post-send reconciliation from cl…
ibetitsmike Jul 25, 2026
140b907
fix(site/src/pages/AgentsPage): give hook dispatch failures their own…
ibetitsmike Jul 25, 2026
30090d9
refactor(site/src/pages/AgentsPage): narrow the hook response guard w…
ibetitsmike Jul 25, 2026
9f395bd
fix(site/src/pages/AgentsPage): apply the refetched status after a fa…
ibetitsmike Jul 25, 2026
5b7265e
fix(site/src/pages/AgentsPage): key the queued tail on server observa…
ibetitsmike Jul 25, 2026
282d218
test(site/src/pages/AgentsPage): type the status fixture without a cast
ibetitsmike Jul 25, 2026
ad89579
fix(site/src/pages/AgentsPage): count repeated server status reports …
ibetitsmike Jul 25, 2026
5b4b58e
test(site/src/api/queries): clarify why the reconciled page order inv…
ibetitsmike Jul 25, 2026
346c4e9
fix(site/src/pages/AgentsPage): hydrate an unchanged status after a r…
ibetitsmike Jul 25, 2026
050babe
fix(site/src/pages/AgentsPage): guard send responses by chat
ibetitsmike Jul 25, 2026
5487758
fix(site/src/pages/AgentsPage): resync status only after fresh chat data
ibetitsmike Jul 25, 2026
ce9c26d
fix(site/src/pages/AgentsPage): keep websocket status through a resync
ibetitsmike Jul 25, 2026
586c55a
fix(site/src/pages/AgentsPage): scope the status resync to its own chat
ibetitsmike Jul 25, 2026
6910bf5
fix(site/src/pages/AgentsPage): converge the queue after a promoted send
ibetitsmike Jul 27, 2026
1a3e838
fix(site/src/pages/AgentsPage): surface hook denials as policy decisions
ibetitsmike Jul 27, 2026
43f8b55
style(site/src): clean up chat hook comments
ibetitsmike Jul 29, 2026
decf230
fix(site/src/pages/UserSettingsPage): de-flake the quiet hours schedu…
ibetitsmike Jul 29, 2026
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
53 changes: 52 additions & 1 deletion site/src/api/queries/chatMessageEdits.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { InfiniteData } from "react-query";
import { describe, expect, it } from "vitest";
import type * as TypesGen from "#/api/typesGenerated";
import { buildOptimisticEditedMessage } from "./chatMessageEdits";
import {
buildOptimisticEditedMessage,
reconcileEditedMessageInCache,
} from "./chatMessageEdits";

const makeUserMessage = (
content: readonly TypesGen.ChatMessagePart[] = [
Expand Down Expand Up @@ -42,3 +46,50 @@ describe("buildOptimisticEditedMessage", () => {
expect(message.content).toEqual([existingFilePart]);
});
});

describe("reconcileEditedMessageInCache", () => {
it("drops messages the edit deleted, such as stale hook notices", () => {
const staleNotice: TypesGen.ChatMessage = {
id: 2,
chat_id: "chat-1",
created_at: "2025-01-01T00:00:00.000Z",
role: "system",
content: [{ type: "text", text: "old hook notice" }],
};
const newNotice: TypesGen.ChatMessage = {
id: 5,
chat_id: "chat-1",
created_at: "2025-01-01T00:01:00.000Z",
role: "system",
content: [{ type: "text", text: "new hook notice" }],
};
const replacement: TypesGen.ChatMessage = {
id: 6,
chat_id: "chat-1",
created_at: "2025-01-01T00:01:00.000Z",
role: "user",
content: [{ type: "text", text: "edited prompt" }],
};
const currentData: InfiniteData<TypesGen.ChatMessagesResponse> = {
pages: [
{
messages: [staleNotice, makeUserMessage()],
queued_messages: [],
has_more: false,
},
],
pageParams: [undefined],
};

const reconciled = reconcileEditedMessageInCache({
currentData,
optimisticMessageId: 1,
responseMessages: [newNotice, replacement],
deletedMessageIds: [staleNotice.id, 1],
});

const ids = reconciled?.pages[0]?.messages.map((message) => message.id);
// Reversed from responseMessages: the first page is newest first.
expect(ids).toEqual([replacement.id, newNotice.id]);
});
});
23 changes: 15 additions & 8 deletions site/src/api/queries/chatMessageEdits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,35 @@ export const projectEditedConversationIntoCache = ({
export const reconcileEditedMessageInCache = ({
currentData,
optimisticMessageId,
responseMessage,
responseMessages,
deletedMessageIds,
}: {
currentData: InfiniteData<TypesGen.ChatMessagesResponse> | undefined;
optimisticMessageId: number;
responseMessage: TypesGen.ChatMessage;
responseMessages: readonly TypesGen.ChatMessage[];
deletedMessageIds?: readonly number[];
}): InfiniteData<TypesGen.ChatMessagesResponse> | undefined => {
if (!currentData?.pages?.length) {
if (!currentData?.pages?.length || responseMessages.length === 0) {
return currentData;
}

const responseIDs = new Set(responseMessages.map((message) => message.id));
const deletedIDs = new Set(deletedMessageIds ?? []);
const replacedPages = currentData.pages.map((page, pageIndex) => {
const preservedMessages = page.messages.filter(
(message) =>
message.id !== optimisticMessageId && message.id !== responseMessage.id,
message.id !== optimisticMessageId &&
!responseIDs.has(message.id) &&
!deletedIDs.has(message.id),
);
Comment thread
ibetitsmike marked this conversation as resolved.
if (pageIndex !== 0) {
return { ...page, messages: preservedMessages };
}
return {
...page,
messages: upsertFirstPageMessage(preservedMessages, responseMessage),
};
let messages = preservedMessages;
for (const responseMessage of responseMessages) {
messages = upsertFirstPageMessage(messages, responseMessage);
}
return { ...page, messages };
});

return {
Expand Down
15 changes: 14 additions & 1 deletion site/src/api/queries/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export const chatMessagesKey = (chatId: string) =>
export const chatPromptsKey = (chatId: string) =>
["chats", chatId, "prompts"] as const;

const chatQueueConvergenceKey = (chatId: string) =>
["chats", chatId, "queue-convergence"] as const;

export const chatACLKey = (chatId: string) => ["chats", chatId, "acl"] as const;

export type ChatListPRStatusFilter = "draft" | "open" | "merged" | "closed";
Expand Down Expand Up @@ -748,6 +751,15 @@ export const chatACL = (chatId: string) => ({

const MESSAGES_PAGE_SIZE = 50;

// The queued messages ride on the uncursored page of the messages endpoint,
// so settling the queue after a promote needs its own request. Refetching
// chatMessagesForInfiniteScroll would reload every page already scrolled.
export const chatQueueConvergence = (chatId: string) => ({
queryKey: chatQueueConvergenceKey(chatId),
queryFn: () => API.experimental.getChatMessages(chatId),
gcTime: 0,
});

export const chatMessagesForInfiniteScroll = (chatId: string) => ({
queryKey: chatMessagesKey(chatId),
initialPageParam: undefined as number | undefined,
Expand Down Expand Up @@ -1427,7 +1439,8 @@ export const editChatMessage = (queryClient: QueryClient, chatId: string) => ({
reconcileEditedMessageInCache({
currentData: current,
optimisticMessageId: variables.messageId,
responseMessage: response.message,
responseMessages: response.messages ?? [response.message],
deletedMessageIds: response.deleted_message_ids,
}),
);
},
Expand Down
Loading
Loading