From a70871679c1be203b7999b4cd7e3e31cbda83c5f Mon Sep 17 00:00:00 2001 From: chengyongru Date: Tue, 2 Jun 2026 11:57:33 +0800 Subject: [PATCH] fix(webui): sort Chats group among projects by recency In project-based sidebar grouping, the "Chats" section (non-project conversations) was always appended at the end regardless of its most recent updated_at. This meant the newest conversation could appear below older project groups. Move Chats group insertion before the global sort, compute its updatedAt from its most recently updated session, and sort all groups together by updatedAt descending. --- webui/src/lib/chat-groups.ts | 26 +++++++++----- webui/src/tests/chat-list.test.tsx | 56 ++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/webui/src/lib/chat-groups.ts b/webui/src/lib/chat-groups.ts index 2fda9f8f0..f4b4b9a1a 100644 --- a/webui/src/lib/chat-groups.ts +++ b/webui/src/lib/chat-groups.ts @@ -281,19 +281,18 @@ function groupSessionsByProject( ), })); - groups.sort((a, b) => { - const timeOrder = dateToTime(b.updatedAt) - dateToTime(a.updatedAt); - if (timeOrder !== 0) return timeOrder; - return a.label.localeCompare(b.label, "en", { - numeric: true, - sensitivity: "base", - }); - }); - if (conversations.length) { + const chatsUpdatedAt = conversations.reduce( + (best, s) => { + const candidate = s.updatedAt ?? s.createdAt ?? null; + return isNewerDate(candidate, best) ? candidate : best; + }, + null, + ); groups.push({ id: "workspace:chats", label: labels.all, + updatedAt: chatsUpdatedAt, sessions: sortProjectSessions( conversations, options.sort, @@ -304,6 +303,15 @@ function groupSessionsByProject( }); } + groups.sort((a, b) => { + const timeOrder = dateToTime(b.updatedAt) - dateToTime(a.updatedAt); + if (timeOrder !== 0) return timeOrder; + return a.label.localeCompare(b.label, "en", { + numeric: true, + sensitivity: "base", + }); + }); + return groups; } diff --git a/webui/src/tests/chat-list.test.tsx b/webui/src/tests/chat-list.test.tsx index eb3ae5c8b..f678d3c06 100644 --- a/webui/src/tests/chat-list.test.tsx +++ b/webui/src/tests/chat-list.test.tsx @@ -256,4 +256,60 @@ describe("ChatList", () => { expect(within(chatsSection).getByText("Chat 0")).toBeInTheDocument(); expect(within(chatsSection).getByRole("button", { name: "Show less" })).toBeInTheDocument(); }); + + it("sorts Chats section among project groups by recency, not always last", () => { + const sessions = [ + session({ + chatId: "recent-chat", + title: "Recent chat", + updatedAt: "2026-05-21T12:00:00Z", + }), + session({ + chatId: "project-a", + title: "Project A task", + updatedAt: "2026-05-21T10:00:00Z", + workspaceScope: { + project_path: "/Users/me/project-a", + project_name: "project-a", + access_mode: "restricted", + }, + }), + session({ + chatId: "project-b", + title: "Project B task", + updatedAt: "2026-05-21T11:00:00Z", + workspaceScope: { + project_path: "/Users/me/project-b", + project_name: "project-b", + access_mode: "restricted", + }, + }), + ]; + + render( + , + ); + + const allRegions = screen.getAllByRole("region"); + const regionNames = allRegions.map((r) => r.getAttribute("aria-label") ?? r.textContent); + + // The most recently updated conversation ("Recent chat" at 12:00) must be + // in the first group — Chats should come before both projects. + const chatsIdx = regionNames.findIndex((n) => n?.includes("Chats")); + const projAIdx = regionNames.findIndex((n) => n?.includes("project-a")); + const projBIdx = regionNames.findIndex((n) => n?.includes("project-b")); + + expect(chatsIdx).toBeLessThan(projAIdx); + expect(chatsIdx).toBeLessThan(projBIdx); + expect(within(allRegions[chatsIdx]).getByText("Recent chat")).toBeInTheDocument(); + }); });