diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index 29c74507..9438bb57 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -176,7 +176,7 @@ export const SessionSidebar: React.FC = ({ const [collapsedProjects, setCollapsedProjects] = React.useState>(new Set()); const [projectRepoStatus, setProjectRepoStatus] = React.useState>(new Map()); - const [expandedSessionGroups, setExpandedSessionGroups] = React.useState>(new Set()); + const [visibleSessionCountByGroup, setVisibleSessionCountByGroup] = React.useState>(new Map()); const [newWorktreeDialogOpen, setNewWorktreeDialogOpen] = React.useState(false); const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); const [openSidebarMenuKey, setOpenSidebarMenuKey] = React.useState(null); @@ -681,20 +681,43 @@ export const SessionSidebar: React.FC = ({ [collapsedFolderIds, toggleFolderCollapse, createFolder, t], ); - const toggleGroupSessionLimit = React.useCallback((groupId: string) => { - setExpandedSessionGroups((prev) => { - const next = new Set(prev); - if (next.has(groupId)) { - next.delete(groupId); - } else { - next.add(groupId); - } + const showMoreGroupSessions = React.useCallback((groupId: string, currentVisibleCount: number) => { + setVisibleSessionCountByGroup((prev) => { + const next = new Map(prev); + next.set(groupId, currentVisibleCount + 7); return next; }); }, []); + const resetGroupSessionLimit = React.useCallback((groupId: string) => { + setVisibleSessionCountByGroup((prev) => { + if (!prev.has(groupId)) { + return prev; + } + const next = new Map(prev); + next.delete(groupId); + return next; + }); + }, []); + + const resetProjectSessionLimits = React.useCallback((projectId: string) => { + setVisibleSessionCountByGroup((prev) => { + let changed = false; + const next = new Map(prev); + const projectGroupPrefix = `${projectId}:`; + for (const groupId of next.keys()) { + if (groupId.startsWith(projectGroupPrefix)) { + next.delete(groupId); + changed = true; + } + } + return changed ? next : prev; + }); + }, []); + const collapseAllProjects = React.useCallback(() => { ignoreIntersectionUntil.current = Date.now() + 150; + setVisibleSessionCountByGroup(new Map()); setCollapsedProjects(() => { const allIds = new Set(projects.map((p) => p.id)); try { @@ -709,6 +732,7 @@ export const SessionSidebar: React.FC = ({ const expandAllProjects = React.useCallback(() => { ignoreIntersectionUntil.current = Date.now() + 150; + setVisibleSessionCountByGroup(new Map()); setCollapsedProjects(() => { const empty = new Set(); try { @@ -724,6 +748,7 @@ export const SessionSidebar: React.FC = ({ const toggleProject = React.useCallback((projectId: string) => { // Ignore intersection events for a short period after toggling ignoreIntersectionUntil.current = Date.now() + 150; + resetProjectSessionLimits(projectId); setCollapsedProjects((prev) => { const next = new Set(prev); if (next.has(projectId)) { @@ -741,7 +766,7 @@ export const SessionSidebar: React.FC = ({ } return next; }); - }, [isVSCode, safeStorage, scheduleCollapsedProjectsPersist]); + }, [isVSCode, resetProjectSessionLimits, safeStorage, scheduleCollapsedProjectsPersist]); const normalizedProjects = React.useMemo(() => { return projects @@ -933,6 +958,7 @@ export const SessionSidebar: React.FC = ({ }, [projectSections, homeDirectory]); const showRecentSection = useSessionDisplayStore((state) => state.showRecentSection); + const showArchivedSessions = useSessionDisplayStore((state) => state.showArchivedSessions); const activeNowSessions = React.useMemo(() => { if (!showRecentSection) { @@ -1010,8 +1036,15 @@ export const SessionSidebar: React.FC = ({ }); const sectionsForSidebarRender = React.useMemo(() => { + const archiveFilteredSections = showArchivedSessions + ? sectionsForRender + : sectionsForRender.map((section) => ({ + ...section, + groups: section.groups.filter((group) => !group.isArchivedBucket), + })); + if (isVSCode || hasSessionSearchQuery || recentSessionIds.size === 0) { - return sectionsForRender; + return archiveFilteredSections; } const filterNodes = (nodes: SessionNode[]): SessionNode[] => { @@ -1034,14 +1067,14 @@ export const SessionSidebar: React.FC = ({ }, []); }; - return sectionsForRender.map((section) => ({ + return archiveFilteredSections.map((section) => ({ ...section, groups: section.groups.map((group) => ({ ...group, sessions: filterNodes(group.sessions), })), })); - }, [isVSCode, hasSessionSearchQuery, recentSessionIds, sectionsForRender]); + }, [isVSCode, hasSessionSearchQuery, recentSessionIds, sectionsForRender, showArchivedSessions]); const prLookupKeys = React.useMemo(() => { const keys = new Set(); @@ -1247,13 +1280,14 @@ export const SessionSidebar: React.FC = ({ ); const toggleCollapsedGroup = React.useCallback((key: string) => { + resetGroupSessionLimit(key); setCollapsedGroups((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); - }, []); + }, [resetGroupSessionLimit]); const prVisualStateByDirectoryBranch = React.useMemo(() => { const result = new Map(); @@ -1287,7 +1321,7 @@ export const SessionSidebar: React.FC = ({ hasSessionSearchQuery={hasSessionSearchQuery} normalizedSessionSearchQuery={normalizedSessionSearchQuery} groupSearchDataByGroup={groupSearchDataByGroup} - expandedSessionGroups={expandedSessionGroups} + visibleSessionCount={visibleSessionCountByGroup.get(groupKey)} collapsedGroups={collapsedGroups} hideDirectoryControls={hideDirectoryControls} collapsedFolderIds={collapsedFolderIds} @@ -1300,7 +1334,8 @@ export const SessionSidebar: React.FC = ({ currentSessionDirectory={currentSessionDirectory} projectRepoStatus={projectRepoStatus} lastRepoStatus={lastRepoStatusRef.current} - toggleGroupSessionLimit={toggleGroupSessionLimit} + showMoreGroupSessions={showMoreGroupSessions} + resetGroupSessionLimit={resetGroupSessionLimit} mobileVariant={mobileVariant} alwaysShowActions={alwaysShowSidebarActions} activeProjectId={activeProjectId} @@ -1325,7 +1360,7 @@ export const SessionSidebar: React.FC = ({ hasSessionSearchQuery, normalizedSessionSearchQuery, groupSearchDataByGroup, - expandedSessionGroups, + visibleSessionCountByGroup, collapsedGroups, hideDirectoryControls, collapsedFolderIds, @@ -1336,7 +1371,8 @@ export const SessionSidebar: React.FC = ({ renderSessionNode, currentSessionDirectory, projectRepoStatus, - toggleGroupSessionLimit, + showMoreGroupSessions, + resetGroupSessionLimit, mobileVariant, alwaysShowSidebarActions, activeProjectId, diff --git a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx index df3472a1..2dee2656 100644 --- a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx +++ b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx @@ -40,7 +40,7 @@ type Props = { hasSessionSearchQuery: boolean; normalizedSessionSearchQuery: string; groupSearchDataByGroup: WeakMap; - expandedSessionGroups: Set; + visibleSessionCount?: number; collapsedGroups: Set; hideDirectoryControls: boolean; collapsedFolderIds: Set; @@ -53,7 +53,8 @@ type Props = { currentSessionDirectory: string | null; projectRepoStatus: Map; lastRepoStatus: boolean; - toggleGroupSessionLimit: (groupKey: string) => void; + showMoreGroupSessions: (groupKey: string, currentVisibleCount: number) => void; + resetGroupSessionLimit: (groupKey: string) => void; mobileVariant: boolean; alwaysShowActions: boolean; activeProjectId: string | null; @@ -107,7 +108,7 @@ export function SessionGroupSection(props: Props): React.ReactNode { hasSessionSearchQuery, normalizedSessionSearchQuery, groupSearchDataByGroup, - expandedSessionGroups, + visibleSessionCount, collapsedGroups, hideDirectoryControls, collapsedFolderIds, @@ -119,7 +120,8 @@ export function SessionGroupSection(props: Props): React.ReactNode { renderSessionNode, projectRepoStatus, lastRepoStatus, - toggleGroupSessionLimit, + showMoreGroupSessions, + resetGroupSessionLimit, mobileVariant, alwaysShowActions, activeProjectId, @@ -156,9 +158,9 @@ export function SessionGroupSection(props: Props): React.ReactNode { const displayMode = useSessionDisplayStore((state) => state.displayMode); const foldersMap = useSessionFoldersStore((state) => state.foldersMap); const isMinimalMode = displayMode === 'minimal'; - const isExpanded = expandedSessionGroups.has(groupKey); const isCollapsed = hasSessionSearchQuery ? false : collapsedGroups.has(groupKey); const maxVisible = hideDirectoryControls ? 10 : 5; + const nonArchivedVisibleCount = Math.max(maxVisible, visibleSessionCount ?? maxVisible); const groupMatchesSearch = hasSessionSearchQuery ? searchData?.groupMatches === true : false; const shouldFilterGroupContents = hasSessionSearchQuery; const sourceGroupNodes = React.useMemo( @@ -255,8 +257,9 @@ export function SessionGroupSection(props: Props): React.ReactNode { ? ungroupedSessions : hasSessionSearchQuery ? ungroupedSessions - : (isExpanded ? ungroupedSessions : ungroupedSessions.slice(0, maxVisible)); + : ungroupedSessions.slice(0, nonArchivedVisibleCount); const remainingCount = totalSessions - visibleSessions.length; + const canShowLess = !group.isArchivedBucket && !hasSessionSearchQuery && totalSessions > maxVisible && remainingCount === 0; // Virtualize the archived bucket once it grows past a threshold. The // archived list is the only group that can routinely hit hundreds or @@ -617,21 +620,19 @@ export function SessionGroupSection(props: Props): React.ReactNode { : t('sessions.sidebar.group.empty.noSessionsInWorkspace')} ) : null} - {remainingCount > 0 && !isExpanded ? ( + {remainingCount > 0 ? ( ) : null} - {isExpanded && totalSessions > maxVisible ? ( + {canShowLess ? (