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 }; return { folder, nodes };
}); });
const folderMapById = new Map(allFoldersForGroupBase.map((entry) => [entry.folder.id, entry])); const allFoldersForGroup = React.useMemo(() => {
const shouldKeepFolder = (folderId: string): boolean => { const folderMapById = new Map(allFoldersForGroupBase.map((entry) => [entry.folder.id, entry]));
const entry = folderMapById.get(folderId); const childFolderIdsByParentId = new Map<string, string[]>();
if (!entry) return false; for (const { folder } of allFoldersForGroupBase) {
if (!hasSessionSearchQuery) return true; if (!folder.parentId) continue;
const folderMatches = entry.folder.name.toLowerCase().includes(normalizedSessionSearchQuery); const existing = childFolderIdsByParentId.get(folder.parentId);
if (folderMatches || entry.nodes.length > 0) return true; if (existing) {
return allFoldersForGroupBase existing.push(folder.id);
.filter(({ folder }) => folder.parentId === folderId) } else {
.some(({ folder }) => shouldKeepFolder(folder.id)); childFolderIdsByParentId.set(folder.parentId, [folder.id]);
}; }
}
const allFoldersForGroup = hasSessionSearchQuery const keepByFolderId = new Map<string, boolean>();
? allFoldersForGroupBase.filter(({ folder }) => shouldKeepFolder(folder.id)) const shouldKeepFolder = (folderId: string): boolean => {
: allFoldersForGroupBase; 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 sessionIdsInFolders = new Set(allFoldersForGroup.flatMap((f) => f.folder.sessionIds));
const ungroupedSessions = sourceGroupNodes.filter((node) => !sessionIdsInFolders.has(node.session.id)); const ungroupedSessions = sourceGroupNodes.filter((node) => !sessionIdsInFolders.has(node.session.id));
@@ -325,6 +361,8 @@ export function SessionGroupSection(props: Props): React.ReactNode {
}} }}
onDelete={() => { onDelete={() => {
if (group.isArchivedBucket) { if (group.isArchivedBucket) {
// Delete sessions in the folder
// Empty folders are auto-hidden by useArchivedAutoFolders
sessionEvents.requestDelete({ sessionEvents.requestDelete({
sessions: folderSessionsForDelete, sessions: folderSessionsForDelete,
mode: 'session', mode: 'session',
@@ -219,18 +219,21 @@ export const useSessionGrouping = (args: Args) => {
}); });
}); });
groups.push({ const archivedSessions = groupedNodes.get(archivedKey) ?? [];
id: 'archived', if (archivedSessions.length > 0) {
label: 'archived', groups.push({
branch: null, id: 'archived',
description: 'Archived and unassigned sessions', label: 'archived',
isMain: false, branch: null,
isArchivedBucket: true, description: 'Archived and unassigned sessions',
worktree: null, isMain: false,
directory: null, isArchivedBucket: true,
folderScopeKey: !args.isVSCode && normalizedProjectRoot ? getArchivedScopeKey(normalizedProjectRoot) : null, worktree: null,
sessions: groupedNodes.get(archivedKey) ?? [], directory: null,
}); folderScopeKey: !args.isVSCode && normalizedProjectRoot ? getArchivedScopeKey(normalizedProjectRoot) : null,
sessions: archivedSessions,
});
}
return groups; return groups;
}, },