From a5ca71c3ba0fcdaeaae39ceccff184bde6a54da8 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 6 Nov 2026 11:31:29 -0800 Subject: [PATCH] fix(sidebar): include Pinned in collapse all projects (#39145) * fix(sidebar): include Pinned in collapse all projects * fix(sidebar): keep pinned toggles working outside project grouping --- .../worktree-list/listing/WorktreeList.tsx | 2 +- .../listing/project-collapse-all.test.ts | 24 ++++++++++++++++--- .../listing/project-collapse-all.ts | 8 +++---- .../worktree-list/rows/header-event-guards.ts | 6 ++--- src/shared/sidebar-list/group-keys.ts | 4 ++++ 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx b/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx index d400a65bc31..5a79fd7982d 100644 --- a/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx +++ b/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx @@ -332,7 +332,7 @@ const WorktreeList = React.memo(function WorktreeList({ pinnedDisplayPolicy={pinnedDisplayPolicy} projectOrderBy={projectOrderBy} toggleGroup={handleToggleGroup} - onToggleAllProjects={handleToggleAllProjects} + onToggleAllProjects={projectCollapseKeys.length > 0 ? handleToggleAllProjects : undefined} collapsedGroups={effectiveCollapsedGroups} handleCreateForRepo={openHeaderCreateComposer} handleOpenRepoSettings={handleOpenRepoSettings} diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.test.ts b/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.test.ts index ec83f7f1aee..d54ec0f2c24 100644 --- a/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.test.ts +++ b/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.test.ts @@ -1,7 +1,11 @@ import { expect, it } from 'vitest' -import { getRenderedProjectCollapseKeys } from './project-collapse-all' +import { + areAllProjectsCollapsed, + getRenderedProjectCollapseKeys, + setAllProjectsCollapsed +} from './project-collapse-all' -it('includes project headers without counted workspaces and uses their collapse identities', () => { +it('includes pinned and empty project headers using their collapse identities', () => { expect( getRenderedProjectCollapseKeys([ { type: 'header', key: 'repo:imports', count: 0, icon: { kind: 'project' } }, @@ -23,5 +27,19 @@ it('includes project headers without counted workspaces and uses their collapse }, { type: 'header', key: 'pinned', count: 1, icon: { kind: 'pinned' } } ]) - ).toEqual(['repo:imports', 'repo:inbox', 'repo:pending', 'project:one']) + ).toEqual(['repo:imports', 'repo:inbox', 'repo:pending', 'project:one', 'pinned']) +}) + +it('collapses and expands pinned with projects while preserving unrelated sections', () => { + const keys = getRenderedProjectCollapseKeys([ + { type: 'header', key: 'project:one', count: 1, icon: { kind: 'project' } }, + { type: 'header', key: 'pinned', count: 1, icon: { kind: 'pinned' } } + ]) + const initial = new Set(['project:one', 'lineage:child', 'status:done']) + expect(areAllProjectsCollapsed(keys, initial)).toBe(false) + const collapsed = setAllProjectsCollapsed(initial, keys, true) + expect(collapsed).toEqual(new Set(['project:one', 'lineage:child', 'status:done', 'pinned'])) + expect(areAllProjectsCollapsed(keys, collapsed)).toBe(true) + const expanded = setAllProjectsCollapsed(new Set([...collapsed, 'project:hidden']), keys, false) + expect(expanded).toEqual(new Set(['lineage:child', 'status:done'])) }) diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.ts b/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.ts index 71db96fbbfb..e0b1207575a 100644 --- a/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.ts +++ b/src/renderer/src/components/sidebar/worktree-list/listing/project-collapse-all.ts @@ -1,4 +1,4 @@ -import { isProjectSectionGroupKey } from '../../../../../../shared/sidebar-list/group-keys' +import { isProjectOrPinnedSectionGroupKey } from '../../../../../../shared/sidebar-list/group-keys' import type { HostSectionRow } from '../../../../../../shared/sidebar-list/host-section-rows' export function getRenderedProjectCollapseKeys(rows: readonly HostSectionRow[]): string[] { @@ -8,7 +8,7 @@ export function getRenderedProjectCollapseKeys(rows: readonly HostSectionRow[]): continue } const key = row.collapseKey ?? row.key - if (isProjectSectionGroupKey(key)) { + if (isProjectOrPinnedSectionGroupKey(key)) { keys.add(key) } } @@ -22,7 +22,7 @@ export function areAllProjectsCollapsed( return projectKeys.length > 0 && projectKeys.every((key) => collapsedGroups.has(key)) } -// Expanding clears every project key, including nested ones hidden under a collapsed parent. +// Expanding clears pinned and every project key, including hidden nested projects. export function setAllProjectsCollapsed( collapsedGroups: ReadonlySet, projectKeys: readonly string[], @@ -30,5 +30,5 @@ export function setAllProjectsCollapsed( ): Set { return collapsed ? new Set([...collapsedGroups, ...projectKeys]) - : new Set([...collapsedGroups].filter((key) => !isProjectSectionGroupKey(key))) + : new Set([...collapsedGroups].filter((key) => !isProjectOrPinnedSectionGroupKey(key))) } diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/header-event-guards.ts b/src/renderer/src/components/sidebar/worktree-list/rows/header-event-guards.ts index ddbff58754e..fd7aaacdd0a 100644 --- a/src/renderer/src/components/sidebar/worktree-list/rows/header-event-guards.ts +++ b/src/renderer/src/components/sidebar/worktree-list/rows/header-event-guards.ts @@ -1,7 +1,7 @@ import type React from 'react' import '../drag/project-header-drag' import { isRepoHeaderActionTarget } from '../drag/project-header-drag-contract' -import { isProjectSectionGroupKey } from '../../../../../../shared/sidebar-list/group-keys' +import { isProjectOrPinnedSectionGroupKey } from '../../../../../../shared/sidebar-list/group-keys' export function stopRepoHeaderKeyboardToggle(event: React.KeyboardEvent): void { if (event.key === 'Enter' || event.key === ' ') { @@ -31,13 +31,13 @@ export function shouldIgnoreRepoHeaderToggle(event: React.SyntheticEvent void; toggleAllProjects?: () => void } ): void { - if (event.altKey && ctx.toggleAllProjects && isProjectSectionGroupKey(collapseKey)) { + if (event.altKey && ctx.toggleAllProjects && isProjectOrPinnedSectionGroupKey(collapseKey)) { ctx.toggleAllProjects() return } diff --git a/src/shared/sidebar-list/group-keys.ts b/src/shared/sidebar-list/group-keys.ts index 021f6a339a3..b1df1d30882 100644 --- a/src/shared/sidebar-list/group-keys.ts +++ b/src/shared/sidebar-list/group-keys.ts @@ -19,6 +19,10 @@ export function isProjectSectionGroupKey(key: string): boolean { return key.startsWith('repo:') || key.startsWith('project:') || key.startsWith('project-group:') } +export function isProjectOrPinnedSectionGroupKey(key: string): boolean { + return key === PINNED_GROUP_KEY || isProjectSectionGroupKey(key) +} + export const ALL_GROUP_KEY = 'all' export const LINEAGE_GROUP_PREFIX = 'lineage:'