Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions apps/desktop/src/renderer/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,60 @@ describe('Shell split panes', () => {
expect(screen.getByRole('region', { name: 'Beta' })).toHaveClass('border-accent/40')
})

it('prefixes each pane header with the session\'s project title', async () => {
invokeMock.mockImplementation(async (method) => {
switch (method) {
case 'ping':
return 'pong'
case 'app.info':
return { homeDir: 'C:\\Users\\tester' }
case 'session.list':
return [
{
id: 'sess-alpha',
projectId: 'proj-ari',
title: 'New session',
updatedAt: NOW - 60_000,
messageCount: 1,
},
{
id: 'sess-beta',
projectId: 'proj-docs',
title: 'Draft notes',
updatedAt: NOW - 120_000,
messageCount: 1,
},
]
case 'project.list':
return [
{ id: 'proj-ari', name: 'Ari', path: '/projects/ari', status: 'ok', open: true },
{ id: 'proj-docs', name: 'Docs', path: '/projects/docs', status: 'ok', open: true },
]
case 'providers.detect':
return []
case 'providers.models':
return []
case 'files.index':
return { paths: [] }
case 'endpoints.list':
return []
case 'session.load':
return { session: null, activeTurnId: null }
default:
throw new Error(`unexpected method: ${String(method)}`)
}
})

openTwoPanes()
render(<App />)

expect(
await screen.findByRole('region', { name: 'Ari - New session' }, { timeout: 10_000 }),
).toBeInTheDocument()
expect(screen.getByRole('region', { name: 'Docs - Draft notes' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Close Ari - New session' })).toBeInTheDocument()
})

it('keeps the newest session list when an older one answers last', async () => {
// Lists overlap at boot and on the event feed, so answers can arrive out of
// order. A stale one — started before Beta existed — would prune Beta's pane.
Expand Down
12 changes: 11 additions & 1 deletion apps/desktop/src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Titlebar } from './shell/Titlebar'
import { GalleryView } from './views'
import { SessionView } from './features/session/SessionView'
import {
UNFILED_GROUP_ID,
moveProjectInList,
projectMoveForDelta,
shellRootFor,
Expand All @@ -39,6 +40,7 @@ import { useCommands } from './features/palette/useCommands'
import { ContentSearchOverlay } from './features/search'
import { AwakenSplash, AWAKEN_MAX_MS } from './features/moment'
import { useSessionActivity } from './features/session/use-session-activity'
import { formatPaneTitle } from './features/split/pane-title'
import { SplitView } from './features/split/SplitView'
import { splitLayoutActions, useSplitLayout } from './features/split/use-split-layout'
import { focusNeighbour } from './features/split/split-geometry'
Expand Down Expand Up @@ -981,7 +983,15 @@ function Shell() {
<ErrorBoundary label="Session">
<SplitView
layout={layout}
titleOf={(id) => sessions.find((s) => s.id === id)?.title ?? null}
titleOf={(id) => {
const session = sessions.find((s) => s.id === id)
if (session === undefined) return null
const projectName =
session.projectId === UNFILED_GROUP_ID
? null
: (projects.find((p) => p.id === session.projectId)?.name ?? null)
Comment on lines +989 to +992

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Closed projects remain in pane titles

When a project is closed, titleOf still prefixes its sessions with that project's name. Closed projects remain in projects, although their sessions are Unfiled.

Learn more

A closed project stays in the project registry with open: false. Its sessions are grouped under Unfiled by sidebarGroups, but this lookup matches every registry entry. The pane therefore retains a project prefix after the user closes that project.

Example: Closing the “Docs” project moves “Draft notes” beneath Unfiled. Its pane still reads “Docs - Draft notes” instead of “Draft notes”.

Recommended fix: Restrict the name lookup to open projects, matching the sidebar's filing rule.

Suggested change
const projectName =
session.projectId === UNFILED_GROUP_ID
? null
: (projects.find((p) => p.id === session.projectId)?.name ?? null)
const projectName =
session.projectId === UNFILED_GROUP_ID
? null
: (projects.find((p) => p.open && p.id === session.projectId)?.name ??
null)

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

return formatPaneTitle(session.title, projectName)
}}
onFocus={splitLayoutActions.focus}
onClose={splitLayoutActions.close}
onSplit={splitLayoutActions.split}
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/renderer/src/features/split/PaneFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export function PaneFrame({
>
<GripVertical size={10} aria-hidden className="shrink-0 text-fg-subtle/60" />
<span
title={label}
className={`min-w-0 flex-1 truncate text-2xs ${
focused ? 'font-medium text-fg' : 'text-fg-subtle'
}`}
Expand Down
20 changes: 20 additions & 0 deletions apps/desktop/src/renderer/src/features/split/pane-title.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest'
import { formatPaneTitle } from './pane-title'

describe('formatPaneTitle', () => {
it('joins a project name and session title with a hyphen', () => {
expect(formatPaneTitle('New session', 'Ari')).toBe('Ari - New session')
})

it('falls back to the session title when there is no project', () => {
expect(formatPaneTitle('Alpha', null)).toBe('Alpha')
expect(formatPaneTitle('Alpha', undefined)).toBe('Alpha')
expect(formatPaneTitle('Alpha', '')).toBe('Alpha')
})

it('stays null while the session itself has no name yet', () => {
expect(formatPaneTitle(null, 'Ari')).toBe(null)
expect(formatPaneTitle(undefined, 'Ari')).toBe(null)
expect(formatPaneTitle('', 'Ari')).toBe(null)
})
})
12 changes: 12 additions & 0 deletions apps/desktop/src/renderer/src/features/split/pane-title.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* The pane header's on-screen name: the session, prefixed with its project
* when one is known. Ad-hoc / unfiled sessions keep the session title alone.
*/
export function formatPaneTitle(
sessionTitle: string | null | undefined,
projectName: string | null | undefined,
): string | null {
if (sessionTitle == null || sessionTitle === '') return null
if (projectName == null || projectName === '') return sessionTitle
return `${projectName} - ${sessionTitle}`
}
Loading