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.
This commit is contained in:
chengyongru 2026-06-02 11:57:33 +08:00 committed by Xubin Ren
parent edf34d857a
commit a70871679c
2 changed files with 73 additions and 9 deletions

View File

@ -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<string | null>(
(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;
}

View File

@ -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(
<ChatList
sessions={sessions}
activeKey="websocket:recent-chat"
onSelect={vi.fn()}
onRequestDelete={vi.fn()}
onTogglePin={vi.fn()}
onRequestRename={vi.fn()}
onToggleArchive={vi.fn()}
showTimestamps
/>,
);
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();
});
});