From 467430d8fa22d89c449b380623379a9ef0d4c2f7 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 20 Apr 2026 13:43:31 +0300 Subject: [PATCH] fix: sort child chats newest-first and prepend on creation (#24524) GetChildChatsByParentIDs sorted created_at ASC, but the cache helper appended new children to the end. On refetch the API and cache agreed on oldest-first, putting the just-created child at the bottom. Users expect newest first, matching the root-chat sidebar convention. - SQL: change child sort to created_at DESC, id DESC. - Cache: prepend instead of append in addChildToParentInCache (renamed from appendChildToParentInCache to avoid leaking position semantics). - Test: update ordering assertion to expect newest-first. Refs #24404 --- coderd/database/queries.sql.go | 4 ++-- coderd/database/queries/chats.sql | 4 ++-- coderd/exp_chats_test.go | 6 +++--- site/src/api/queries/chats.test.ts | 12 ++++++------ site/src/api/queries/chats.ts | 6 +++--- site/src/pages/AgentsPage/AgentsPage.tsx | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b8fa04b95a..631832fb1e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6881,8 +6881,8 @@ WHERE ELSE chats.archived = $2 :: boolean END ORDER BY - chats.created_at ASC, - chats.id ASC + chats.created_at DESC, + chats.id DESC ` type GetChildChatsByParentIDsParams struct { diff --git a/coderd/database/queries/chats.sql b/coderd/database/queries/chats.sql index 6f27ee3b11..112b72858f 100644 --- a/coderd/database/queries/chats.sql +++ b/coderd/database/queries/chats.sql @@ -418,8 +418,8 @@ WHERE ELSE chats.archived = sqlc.narg('archived') :: boolean END ORDER BY - chats.created_at ASC, - chats.id ASC; + chats.created_at DESC, + chats.id DESC; -- name: InsertChat :one INSERT INTO chats ( diff --git a/coderd/exp_chats_test.go b/coderd/exp_chats_test.go index a861581976..c53defb087 100644 --- a/coderd/exp_chats_test.go +++ b/coderd/exp_chats_test.go @@ -1333,10 +1333,10 @@ func TestListChats(t *testing.T) { } require.Len(t, parent.Children, 2, "parent should embed 2 children") - // Children should be ordered by created_at ASC. + // Children are ordered by created_at DESC (newest first). childIDs := []uuid.UUID{parent.Children[0].ID, parent.Children[1].ID} - require.Equal(t, child1.ID, childIDs[0]) - require.Equal(t, child2.ID, childIDs[1]) + require.Equal(t, child2.ID, childIDs[0]) + require.Equal(t, child1.ID, childIDs[1]) // Verify each child has correct parent/root references. for _, child := range parent.Children { diff --git a/site/src/api/queries/chats.test.ts b/site/src/api/queries/chats.test.ts index 1dc78d276f..68b13b8c0c 100644 --- a/site/src/api/queries/chats.test.ts +++ b/site/src/api/queries/chats.test.ts @@ -4,7 +4,7 @@ import { API } from "#/api/api"; import type * as TypesGen from "#/api/typesGenerated"; import { buildOptimisticEditedMessage } from "./chatMessageEdits"; import { - appendChildToParentInCache, + addChildToParentInCache, archiveChat, cancelChatListRefetches, chatCostSummary, @@ -1703,8 +1703,8 @@ describe("mutation onMutate cancels pagination fetches", () => { }); }); -describe("appendChildToParentInCache", () => { - it("appends the child to the matching parent's children array", () => { +describe("addChildToParentInCache", () => { + it("prepends new child to the parent's children array", () => { const queryClient = createTestQueryClient(); const parent = makeChat("parent-1"); seedInfiniteChats(queryClient, [parent]); @@ -1713,7 +1713,7 @@ describe("appendChildToParentInCache", () => { parent_chat_id: "parent-1", root_chat_id: "parent-1", }); - appendChildToParentInCache(queryClient, child, "parent-1"); + addChildToParentInCache(queryClient, child, "parent-1"); const result = readInfiniteChats(queryClient); expect(result).toHaveLength(1); @@ -1730,7 +1730,7 @@ describe("appendChildToParentInCache", () => { parent_chat_id: "missing-parent", root_chat_id: "missing-parent", }); - appendChildToParentInCache(queryClient, child, "missing-parent"); + addChildToParentInCache(queryClient, child, "missing-parent"); const result = readInfiniteChats(queryClient); expect(result).toHaveLength(1); @@ -1747,7 +1747,7 @@ describe("appendChildToParentInCache", () => { const parent = makeChat("parent-1", { children: [existingChild] }); seedInfiniteChats(queryClient, [parent]); - appendChildToParentInCache(queryClient, existingChild, "parent-1"); + addChildToParentInCache(queryClient, existingChild, "parent-1"); const result = readInfiniteChats(queryClient); expect(result?.[0].children).toHaveLength(1); diff --git a/site/src/api/queries/chats.ts b/site/src/api/queries/chats.ts index e0dc822c61..b706c61db1 100644 --- a/site/src/api/queries/chats.ts +++ b/site/src/api/queries/chats.ts @@ -103,11 +103,11 @@ export const readInfiniteChatsCache = ( }; /** - * Appends a child chat to its parent's `children` array across all + * Adds a child chat to its parent's `children` array across all * infinite chat query caches. If the parent is not in any loaded page, * the child is silently dropped (it will appear when the parent loads). */ -export const appendChildToParentInCache = ( +export const addChildToParentInCache = ( queryClient: QueryClient, child: TypesGen.Chat, parentId: string, @@ -119,7 +119,7 @@ export const appendChildToParentInCache = ( // Avoid duplicates. if (c.children?.some((ch) => ch.id === child.id)) return c; changed = true; - return { ...c, children: [...(c.children ?? []), child] }; + return { ...c, children: [child, ...(c.children ?? [])] }; }); return changed ? next : chats; }); diff --git a/site/src/pages/AgentsPage/AgentsPage.tsx b/site/src/pages/AgentsPage/AgentsPage.tsx index 3b7be1b739..412843114e 100644 --- a/site/src/pages/AgentsPage/AgentsPage.tsx +++ b/site/src/pages/AgentsPage/AgentsPage.tsx @@ -10,7 +10,7 @@ import { toast } from "sonner"; import { API, watchChats } from "#/api/api"; import { getErrorMessage } from "#/api/errors"; import { - appendChildToParentInCache, + addChildToParentInCache, archiveChat, cancelChatListRefetches, chatDiffContentsKey, @@ -567,10 +567,10 @@ const AgentsPage: FC = () => { // chat into every loaded page. if (chatEvent.kind === "created") { if (updatedChat.parent_chat_id) { - // Child chat: append to its parent's children + // Child chat: add to its parent's children // array. If the parent is not in any loaded // page, the child is silently dropped. - appendChildToParentInCache( + addChildToParentInCache( queryClient, updatedChat, updatedChat.parent_chat_id,