fix(desktop): anchor project trunk + at first repo root when primary path unset

Clicking the project-group trunk + passed the null wire path through, which
downstream treats as the reserved Home/detached signal, silently creating a
global session. Fall back to the first repo root for path-less explicit
projects; Home keeps null. Covers click and new-session drag cwd.
This commit is contained in:
finn763
2026-09-27 16:37:18 +08:00
committed by brooklyn!
parent f9f153ab18
commit 793517a7d4
2 changed files with 35 additions and 3 deletions

View File

@@ -180,4 +180,26 @@ describe('ProjectOverviewRow', () => {
expect(onNewSession).toHaveBeenCalledWith(null)
})
// #124808: a real project whose primary_path was never set (multi-folder /
// path-less explicit project) still carries repo roots. Its trunk "+" must
// anchor the new session at the first repo root, not pass the null wire
// path through — null is the reserved Home/detached signal downstream, so
// the click silently created a global detached session.
it('anchors the trunk "+" at the first repo root when the project has no primary path', () => {
const multi = {
id: 'p_multi',
label: 'Multi',
path: null,
repos: [{ id: 'r1', label: 'app', path: '/work/app', groups: [], sessionCount: 0 }],
sessionCount: 0
} as unknown as SidebarProjectTree
const onNewSession = vi.fn()
render(<ProjectOverviewRow onNewSession={onNewSession} project={multi} />)
fireEvent.click(screen.getByRole('button', { name: 'New session in Multi' }))
expect(onNewSession).toHaveBeenCalledWith('/work/app')
})
})

View File

@@ -147,6 +147,16 @@ export function ProjectOverviewRow({
const hiddenCount = total - preview.length
const offerShowAll = !showAllSessions && !expanded && preview.length > 0 && hiddenCount > 0
// #124808: a path-less explicit project (multi-folder, never assigned a
// primary_path) still carries repo roots. Its trunk "+" must anchor at
// the first repo root — passing the null wire path through would take the
// reserved Home/detached branch downstream and silently create a global
// session. Home itself keeps null ("no folder" is its contract).
const newSessionPath =
!project.isNoProject && !(project.path ?? '').trim()
? ((project.repos ?? []).map(repo => repo.path).find(root => (root ?? '').trim()) ?? project.path)
: project.path
const showAll = () => {
// All-profiles view has no single backend to ask for one project's lanes;
// drilling in is the reach there.
@@ -204,7 +214,7 @@ export function ProjectOverviewRow({
{onNewSession && (
<WorkspaceAddButton
label={s.newSessionIn(project.label)}
onClick={() => onNewSession(project.path)}
onClick={() => onNewSession(newSessionPath)}
onPointerDown={
onNewSessionSplit
? event => {
@@ -217,11 +227,11 @@ export function ProjectOverviewRow({
onNewSessionSplit(placement.dir, {
anchor: placement.anchor,
before: placement.before,
cwd: project.path
cwd: newSessionPath
})
},
event,
{ cwd: project.path, label: s.newSessionIn(project.label) }
{ cwd: newSessionPath, label: s.newSessionIn(project.label) }
)
}
: undefined