fix(desktop): keep the Sessions label when the grouped sidebar has no projects

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>
This commit is contained in:
Hermes Agent
2026-09-25 12:30:28 -05:00
committed by brooklyn!
parent b93c915fab
commit 3cc05c239c
2 changed files with 81 additions and 4 deletions

View File

@@ -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 })

View File

@@ -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.