fix: always show all workspace folders in grouped view regardless of pagination (#16444)

This commit is contained in:
Mohit Singh
2026-08-17 23:56:55 +02:00
committed by GitHub
parent e9ca71d138
commit 2ed792bc77
3 changed files with 149 additions and 1 deletions
@@ -628,4 +628,90 @@ describe("conversation-panel-list-helpers", () => {
).map((c) => c.id),
).toEqual(["audit", "unnamed"]);
});
it("pre-seeds workspace groups from knownWorkspaces even when no conversations are loaded for them", () => {
const knownWorkspaces = [
{ id: "/workspace/alpha", name: "alpha", path: "/workspace/alpha" },
{ id: "/workspace/beta", name: "beta", path: "/workspace/beta" },
];
const groups = groupConversations(
[],
"local",
"updated",
{ emptyWorkspace: "No workspace", emptyRepository: "No repository" },
knownWorkspaces,
);
expect(groups.map((g) => ({ id: g.id, label: g.label }))).toEqual([
{ id: "ws:/workspace/alpha", label: "alpha" },
{ id: "ws:/workspace/beta", label: "beta" },
]);
expect(groups.every((g) => g.conversations.length === 0)).toBe(true);
});
it("merges known workspaces with conversations from paginated pages into one unified group list", () => {
const knownWorkspaces = [
{ id: "/workspace/alpha", name: "alpha", path: "/workspace/alpha" },
];
const pageTwoConversation: AppConversation = {
...base,
id: "deep",
title: "deep",
selected_workspace: "/workspace/beta",
updated_at: "2024-01-05T00:00:00.000Z",
};
const groups = groupConversations(
[pageTwoConversation],
"local",
"updated",
{ emptyWorkspace: "No workspace", emptyRepository: "No repository" },
knownWorkspaces,
);
const ids = groups.map((g) => g.id);
expect(ids).toContain("ws:/workspace/alpha");
expect(ids).toContain("ws:/workspace/beta");
const alpha = groups.find((g) => g.id === "ws:/workspace/alpha");
expect(alpha?.conversations).toHaveLength(0);
const beta = groups.find((g) => g.id === "ws:/workspace/beta");
expect(beta?.conversations.map((c) => c.id)).toEqual(["deep"]);
});
it("uses the known workspace name for a group whose path is in knownWorkspaces", () => {
const knownWorkspaces = [
{
id: "/workspace/my-project",
name: "My Project",
path: "/workspace/my-project",
},
];
const convo: AppConversation = {
...base,
id: "c1",
title: "c1",
selected_workspace: "/workspace/my-project",
updated_at: "2024-01-02T00:00:00.000Z",
};
const groups = groupConversations(
[convo],
"local",
"updated",
{ emptyWorkspace: "No workspace", emptyRepository: "No repository" },
knownWorkspaces,
);
const group = groups.find((g) => g.id === "ws:/workspace/my-project");
expect(group?.label).toBe("My Project");
});
it("ignores knownWorkspaces for cloud backend grouping", () => {
const knownWorkspaces = [
{ id: "/workspace/alpha", name: "alpha", path: "/workspace/alpha" },
];
const groups = groupConversations(
[],
"cloud",
"updated",
{ emptyWorkspace: "No workspace", emptyRepository: "No repository" },
knownWorkspaces,
);
expect(groups).toHaveLength(0);
});
});
@@ -1,5 +1,6 @@
import type { AppConversation } from "#/api/conversation-service/agent-server-conversation-service.types";
import type { BackendKind } from "#/api/backend-registry/types";
import type { LocalWorkspace } from "#/types/workspace";
import type { Provider } from "#/types/settings";
import {
AUTOMATION_NAME_TAG_KEY,
@@ -419,6 +420,7 @@ export function groupConversations(
backendKind: BackendKind,
sortField: ConversationSortField,
labels: { emptyWorkspace: string; emptyRepository: string },
knownWorkspaces?: readonly LocalWorkspace[],
): {
id: string;
label: string;
@@ -430,6 +432,15 @@ export function groupConversations(
{ label: string; conversations: AppConversation[] }
>();
if (backendKind === "local" && knownWorkspaces) {
for (const ws of knownWorkspaces) {
const normalized = ws.path.trim().replace(/\/+$/, "");
if (normalized) {
byId.set(`ws:${normalized}`, { label: ws.name, conversations: [] });
}
}
}
for (const c of items) {
const { id, label: rawLabel } = getConversationGroupIdentity(
c,
@@ -6,6 +6,7 @@ import { useNavigation } from "#/context/navigation-context";
import { useActiveBackend } from "#/contexts/active-backend-context";
import { useBackendScopedPath } from "#/hooks/use-backend-scoped-path";
import { usePaginatedConversations } from "#/hooks/query/use-paginated-conversations";
import { useResolvedWorkspaces } from "#/hooks/query/use-resolved-workspaces";
import { useStartTasks } from "#/hooks/query/use-start-tasks";
import { useDeleteConversation } from "#/hooks/mutation/use-delete-conversation";
import { useUnifiedPauseConversation } from "#/hooks/mutation/use-unified-stop-conversation";
@@ -16,6 +17,7 @@ import { NavigationLink } from "#/components/shared/navigation-link";
import { ExitConversationModal } from "./exit-conversation-modal";
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
import { Provider } from "#/types/settings";
import type { LocalWorkspace } from "#/types/workspace";
import { useUpdateConversation } from "#/hooks/mutation/use-update-conversation";
import {
displayErrorToast,
@@ -278,6 +280,7 @@ export function ConversationPanel({
isFetchingNextPage,
fetchNextPage,
} = usePaginatedConversations();
const { workspaces: knownWorkspaces } = useResolvedWorkspaces();
// Fetch in-progress start tasks
const { data: startTasks } = useStartTasks();
@@ -342,6 +345,46 @@ export function ConversationPanel({
[conversations],
);
const allWorkspacesForGrouping = React.useMemo<
readonly LocalWorkspace[]
>(() => {
if (
compact ||
organizeMode !== "grouped" ||
activeBackend.kind !== "local"
) {
return [];
}
const normalize = (p: string) => p.trim().replace(/\/+$/, "");
const byPath = new Map<string, LocalWorkspace>();
for (const ws of knownWorkspaces) {
const key = normalize(ws.path);
if (key) {
byPath.set(key, ws);
}
}
for (const c of conversations) {
const normalized = c.selected_workspace
? normalize(c.selected_workspace)
: "";
if (normalized && !byPath.has(normalized)) {
const label = normalized.split("/").filter(Boolean).pop() ?? normalized;
byPath.set(normalized, {
id: normalized,
name: label,
path: normalized,
});
}
}
return Array.from(byPath.values());
}, [
activeBackend.kind,
compact,
conversations,
knownWorkspaces,
organizeMode,
]);
const automationFilteredConversations = React.useMemo(
() =>
applyAutomationConversationFilter(
@@ -455,12 +498,14 @@ export function ConversationPanel({
activeBackend.kind,
conversationSort,
groupLabels,
allWorkspacesForGrouping,
);
}, [
activeBackend.kind,
conversationSort,
groupLabels,
groupedSourceConversations,
allWorkspacesForGrouping,
]);
const groupDiscoveryConversationIds = React.useMemo(() => {
@@ -957,13 +1002,19 @@ export function ConversationPanel({
const showInitialSkeleton = isLoading || !isFetched;
const showPinnedSection =
!compact && !showInitialSkeleton && pinnedConversations.length > 0;
const hasVisibleGroups =
organizeMode === "grouped" &&
!compact &&
orderedConversationGroups != null &&
orderedConversationGroups.length > 0;
const showEmptyState =
isFetched &&
!isLoading &&
!compact &&
listIsEffectivelyEmpty &&
!showPinnedSection &&
!startTasks?.length;
!startTasks?.length &&
!hasVisibleGroups;
const showConversationHeader = !compact;