diff --git a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx index c4deb621..15d1dcff 100644 --- a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx +++ b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx @@ -184,21 +184,57 @@ export function SessionGroupSection(props: Props): React.ReactNode { return { folder, nodes }; }); - const folderMapById = new Map(allFoldersForGroupBase.map((entry) => [entry.folder.id, entry])); - const shouldKeepFolder = (folderId: string): boolean => { - const entry = folderMapById.get(folderId); - if (!entry) return false; - if (!hasSessionSearchQuery) return true; - const folderMatches = entry.folder.name.toLowerCase().includes(normalizedSessionSearchQuery); - if (folderMatches || entry.nodes.length > 0) return true; - return allFoldersForGroupBase - .filter(({ folder }) => folder.parentId === folderId) - .some(({ folder }) => shouldKeepFolder(folder.id)); - }; + const allFoldersForGroup = React.useMemo(() => { + const folderMapById = new Map(allFoldersForGroupBase.map((entry) => [entry.folder.id, entry])); + const childFolderIdsByParentId = new Map(); + for (const { folder } of allFoldersForGroupBase) { + if (!folder.parentId) continue; + const existing = childFolderIdsByParentId.get(folder.parentId); + if (existing) { + existing.push(folder.id); + } else { + childFolderIdsByParentId.set(folder.parentId, [folder.id]); + } + } - const allFoldersForGroup = hasSessionSearchQuery - ? allFoldersForGroupBase.filter(({ folder }) => shouldKeepFolder(folder.id)) - : allFoldersForGroupBase; + const keepByFolderId = new Map(); + const shouldKeepFolder = (folderId: string): boolean => { + const cached = keepByFolderId.get(folderId); + if (cached !== undefined) return cached; + + const entry = folderMapById.get(folderId); + if (!entry) { + keepByFolderId.set(folderId, false); + return false; + } + + const childFolderIds = childFolderIdsByParentId.get(folderId) ?? []; + + // For archived buckets, hide folders with no sessions unless descendants have content. + if (group.isArchivedBucket && entry.nodes.length === 0) { + const hasContentInChildren = childFolderIds.some((childId) => shouldKeepFolder(childId)); + keepByFolderId.set(folderId, hasContentInChildren); + return hasContentInChildren; + } + + if (!hasSessionSearchQuery) { + keepByFolderId.set(folderId, true); + return true; + } + + const folderMatches = entry.folder.name.toLowerCase().includes(normalizedSessionSearchQuery); + if (folderMatches || entry.nodes.length > 0) { + keepByFolderId.set(folderId, true); + return true; + } + + const hasMatchingChildren = childFolderIds.some((childId) => shouldKeepFolder(childId)); + keepByFolderId.set(folderId, hasMatchingChildren); + return hasMatchingChildren; + }; + + return allFoldersForGroupBase.filter(({ folder }) => shouldKeepFolder(folder.id)); + }, [allFoldersForGroupBase, group.isArchivedBucket, hasSessionSearchQuery, normalizedSessionSearchQuery]); const sessionIdsInFolders = new Set(allFoldersForGroup.flatMap((f) => f.folder.sessionIds)); const ungroupedSessions = sourceGroupNodes.filter((node) => !sessionIdsInFolders.has(node.session.id)); @@ -325,6 +361,8 @@ export function SessionGroupSection(props: Props): React.ReactNode { }} onDelete={() => { if (group.isArchivedBucket) { + // Delete sessions in the folder + // Empty folders are auto-hidden by useArchivedAutoFolders sessionEvents.requestDelete({ sessions: folderSessionsForDelete, mode: 'session', diff --git a/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts b/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts index 29bdb78a..2cbf4520 100644 --- a/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts +++ b/packages/ui/src/components/session/sidebar/hooks/useSessionGrouping.ts @@ -219,18 +219,21 @@ export const useSessionGrouping = (args: Args) => { }); }); - groups.push({ - id: 'archived', - label: 'archived', - branch: null, - description: 'Archived and unassigned sessions', - isMain: false, - isArchivedBucket: true, - worktree: null, - directory: null, - folderScopeKey: !args.isVSCode && normalizedProjectRoot ? getArchivedScopeKey(normalizedProjectRoot) : null, - sessions: groupedNodes.get(archivedKey) ?? [], - }); + const archivedSessions = groupedNodes.get(archivedKey) ?? []; + if (archivedSessions.length > 0) { + groups.push({ + id: 'archived', + label: 'archived', + branch: null, + description: 'Archived and unassigned sessions', + isMain: false, + isArchivedBucket: true, + worktree: null, + directory: null, + folderScopeKey: !args.isVSCode && normalizedProjectRoot ? getArchivedScopeKey(normalizedProjectRoot) : null, + sessions: archivedSessions, + }); + } return groups; },