From 3cc05c239ccf289d26e046310352be0e6658480e Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 25 Sep 2026 12:30:28 -0500 Subject: [PATCH] fix(desktop): keep the Sessions label when the grouped sidebar has no projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The source-filter (grouped) toggle headed the sessions section "Projects" whenever it was active, even when the project tree held no real project: with no projects at all the section listed plain chat sessions under a "Projects" header, and with only unclaimed sessions it showed the synthetic Home bucket — the flat session list wearing a project costume — under the same header. Read the label from what the section actually shows: "Projects" while a real project row exists or the tree is still resolving, "Sessions" otherwise. Entering a project keeps showing the project's name. Fixes #62537 Co-authored-by: giggling-ginger <110955495+giggling-ginger@users.noreply.github.com> --- .../sidebar/chat-sidebar.integration.test.tsx | 67 +++++++++++++++++++ apps/desktop/src/app/chat/sidebar/index.tsx | 18 +++-- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/chat-sidebar.integration.test.tsx b/apps/desktop/src/app/chat/sidebar/chat-sidebar.integration.test.tsx index 6a86df027f..5277809d56 100644 --- a/apps/desktop/src/app/chat/sidebar/chat-sidebar.integration.test.tsx +++ b/apps/desktop/src/app/chat/sidebar/chat-sidebar.integration.test.tsx @@ -278,6 +278,73 @@ describe('ChatSidebar project entry', () => { // Messaging platforms group rows by owner the same way recents does once every // profile is on screen, so a Telegram thread is attributable to its profile // (#87715). The platform's row cap and load-more stay the section's. +describe('ChatSidebar section labels', () => { + const realProject = { + id: '/repos/new-project', + label: 'new-project', + path: '/repos/new-project', + repos: [], + sessionCount: 0 + } + + // The synthetic Home bucket the backend emits when no project claimed a + // session (no cwd / deleted workspace): one lane, no repo structure. + const homeOnly = [ + { + ...realProject, + id: '__no_project__', + label: 'Home', + path: null, + isNoProject: true, + sessionCount: 2 + } + ] + + afterEach(() => { + cleanup() + $projectScope.set(ALL_PROJECTS) + $projectTree.set([]) + setSidebarAgentsGrouped(false) + $sessions.set([]) + }) + + it('labels the section "Projects" when the grouped tree has a real project', () => { + setSidebarAgentsGrouped(true) + $projectTree.set([realProject]) + + renderSidebar('/', 'chat') + + expect(screen.getByText('Projects')).toBeTruthy() + expect(screen.queryByText('Sessions')).toBeNull() + }) + + it('keeps the "Sessions" label when grouping is on but no projects exist', () => { + // The source-filter toggle with an empty tree: the section lists plain + // chat sessions, so heading it "Projects" mislabeled them (#62537). + setSidebarAgentsGrouped(true) + $projectTree.set([]) + $sessions.set(sessionRows) + + renderSidebar('/', 'chat') + + expect(screen.getByText('Sessions')).toBeTruthy() + expect(screen.queryByText('Projects')).toBeNull() + }) + + it('keeps the "Sessions" label when the grouped tree is only the synthetic Home bucket', () => { + // No real projects — every session fell into Home. A lone Home bucket is + // the flat session list wearing a project costume, not a switcher. + setSidebarAgentsGrouped(true) + $projectTree.set(homeOnly) + $sessions.set(sessionRows) + + renderSidebar('/', 'chat') + + expect(screen.getByText('Sessions')).toBeTruthy() + expect(screen.queryByText('Projects')).toBeNull() + }) +}) + describe('ChatSidebar messaging owners', () => { const telegram = (id: string, profile: string, last_active: number) => makeSessionInfo({ connection_id: 'local', id, last_active, profile, source: 'telegram', title: id }) diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index 4d8369e4cd..80d282e7b9 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -1266,11 +1266,21 @@ export function ChatSidebar({ [projectModel, syncProjectCwd] ) - // The Sessions section is a project switcher in grouped mode: its label reads - // "Sessions" when flat, "Projects" at the overview, and the project's name - // once you've entered one. + // The section header must name what the section is showing. Grouped mode + // reads "Projects" only while there IS a project switcher to show: a real + // project row, or a tree still resolving (the loading state keeps the label + // stable instead of flapping to "Sessions" and back). The synthetic Home + // bucket alone is just the flat session list wearing a project costume — + // with no real projects the section lists plain chat sessions, so it keeps + // the "Sessions" label (#62537). + const hasProjectRows = projectModel.some(node => !node.isNoProject) + const sessionsLabel = - inProject && enteredProject ? enteredProject.label : worktreeGroupingActive ? s.projects.sectionLabel : s.sessions + inProject && enteredProject + ? enteredProject.label + : worktreeGroupingActive && (hasProjectRows || projectTreeLoading) + ? s.projects.sectionLabel + : s.sessions // Mirror the section's skeleton gate (projectsLoading + nothing to show yet): // while the skeleton is up there's no point also spinning the header count.