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