From ce6ecf306e315f0b7d71a0f66692b0deaf5a1156 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Thu, 30 Jul 2026 07:09:40 -0500 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20hide=20dismissed=20projects=20f?= =?UTF-8?q?rom=20=E2=8C=98K=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove-from-sidebar only filtered the overview; the command palette still listed every auto project. Share one filter so both surfaces stay in sync. --- apps/desktop/src/app/chat/sidebar/index.tsx | 6 ++-- .../desktop/src/app/command-palette/index.tsx | 6 ++-- .../store/layout-dismissed-projects.test.ts | 29 +++++++++++++++++++ apps/desktop/src/store/layout.ts | 14 +++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 apps/desktop/src/store/layout-dismissed-projects.test.ts diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index 3609791ff36..8ddc72ad835 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -46,6 +46,7 @@ import { $sidebarSessionOrderManual, $sidebarWorkspaceOrderIds, $sidebarWorkspaceParentOrderIds, + filterVisibleProjects, pinSession, SESSION_SEARCH_FOCUS_EVENT, setPinnedSessionOrder, @@ -609,11 +610,8 @@ export function ChatSidebar({ return [] } - const dismissed = new Set(dismissedAutoProjects) - const sorted = sortProjectsForOverview( - projectTree - .filter(node => !(node.isAuto && dismissed.has(node.id))) + filterVisibleProjects(projectTree, dismissedAutoProjects) .map(project => excludeProjectSessions( { diff --git a/apps/desktop/src/app/command-palette/index.tsx b/apps/desktop/src/app/command-palette/index.tsx index d62e33b6ffe..e643efef513 100644 --- a/apps/desktop/src/app/command-palette/index.tsx +++ b/apps/desktop/src/app/command-palette/index.tsx @@ -68,6 +68,7 @@ import { setCommandPaletteOpen } from '@/store/command-palette' import { $bindings } from '@/store/keybinds' +import { $dismissedAutoProjectIds, filterVisibleProjects } from '@/store/layout' import { openPetGenerate } from '@/store/pet-generate' import { $projectTree, goToProject, openFolderAsProject, requestStartWorkSession } from '@/store/projects' import { $connection } from '@/store/session' @@ -519,6 +520,7 @@ function CommandPaletteBody({ onExited }: { onExited: () => void }) { const bindings = useStore($bindings) const worktrees = useStore($repoWorktrees) const projectTree = useStore($projectTree) + const dismissedAutoProjects = useStore($dismissedAutoProjectIds) const navigate = useNavigate() const { availableThemes, mode, resolvedMode, setMode, setTheme, themeName } = useTheme() const [search, setSearch] = useState('') @@ -719,7 +721,7 @@ function CommandPaletteBody({ onExited }: { onExited: () => void }) { label: cc.openFolder, run: () => void openFolderAsProject() }, - ...projectTree.map(project => ({ + ...filterVisibleProjects(projectTree, dismissedAutoProjects).map(project => ({ comboHint: 'mod+enter', icon: codiconIcon(project.icon || (project.isNoProject ? 'home' : 'folder-library')), id: `project-${project.id}`, @@ -940,7 +942,7 @@ function CommandPaletteBody({ onExited }: { onExited: () => void }) { // live state through `detail()`, so the groups must rebuild after a select // that kept the palette open — eslint only sees an unused dep. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [contributedItems, go, projectTree, selectTick, settingsSectionLabel, t, updateVersionLabel]) + }, [contributedItems, dismissedAutoProjects, go, projectTree, selectTick, settingsSectionLabel, t, updateVersionLabel]) // The long, granular lists (settings fields, API keys, MCP servers, archived // chats) only surface once the user types — otherwise they'd bury the diff --git a/apps/desktop/src/store/layout-dismissed-projects.test.ts b/apps/desktop/src/store/layout-dismissed-projects.test.ts new file mode 100644 index 00000000000..9aae9cd3eac --- /dev/null +++ b/apps/desktop/src/store/layout-dismissed-projects.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' + +import { filterVisibleProjects } from './layout' + +const auto = (id: string) => ({ id, isAuto: true as const }) +const explicit = (id: string) => ({ id, isAuto: false as const }) +const home = { id: '__home__' } + +describe('filterVisibleProjects', () => { + it('drops dismissed autos and keeps explicit + home + undismissed', () => { + const tree = [explicit('p_shop'), auto('/www/otl-theme'), auto('/www/keep'), home] + expect(filterVisibleProjects(tree, ['/www/otl-theme', '/www/other']).map(p => p.id)).toEqual([ + 'p_shop', + '/www/keep', + '__home__' + ]) + }) + + it('ignores dismiss ids that hit an explicit project', () => { + // List is auto-only by construction; still don't let a stale id hide a real row. + const tree = [explicit('p_real'), auto('/www/gone')] + expect(filterVisibleProjects(tree, ['p_real', '/www/gone']).map(p => p.id)).toEqual(['p_real']) + }) + + it('passes the list through when nothing is dismissed', () => { + const tree = [auto('/www/a'), explicit('p_b')] + expect(filterVisibleProjects(tree, [])).toBe(tree) + }) +}) diff --git a/apps/desktop/src/store/layout.ts b/apps/desktop/src/store/layout.ts index 5a28f6d5200..89a39d8d57d 100644 --- a/apps/desktop/src/store/layout.ts +++ b/apps/desktop/src/store/layout.ts @@ -221,6 +221,20 @@ export function dismissAutoProject(id: string): void { } } +// Auto projects dismissed from the overview stay out of every surface that +// lists projects (sidebar + ⌘K). Explicit rows never match. +export function filterVisibleProjects( + projects: readonly T[], + dismissedIds: readonly string[] = $dismissedAutoProjectIds.get() +): T[] { + if (!dismissedIds.length) { + return projects as T[] + } + + const dismissed = new Set(dismissedIds) + return projects.filter(project => !(project.isAuto && dismissed.has(project.id))) +} + // Hide a worktree row after it's been removed via git. export function dismissWorktree(id: string): void { const current = $dismissedWorktreeIds.get()