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:
@@ -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;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user