fix: hide empty archived section and folders (#872)

* fix: hide archived section and empty folders when no sessions remain

- Only push archived group in useSessionGrouping when there are archived
  sessions, preventing an empty archived section from rendering
- Hide empty folders in archived bucket via shouldKeepFolder check in
  SessionGroupSection (folders with no sessions and no content in
  children are filtered out)
- Always filter folders through shouldKeepFolder, not just during search

* perf: memoize archived folder filtering
This commit is contained in:
jwcrystal
2026-04-11 23:25:33 +03:00
committed by GitHub
parent 36b2bcf4f9
commit 5ea2fed0d3
2 changed files with 67 additions and 26 deletions
@@ -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<string, string[]>();
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<string, boolean>();
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',
@@ -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;
},