From ed5fabb2498747863d730bf6a7bcf6dad7ae2a32 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:28:39 -0700 Subject: [PATCH] Fix pinned host and inbox placement --- .../sidebar/host-section-rows.test.ts | 41 +++++++++++++++++ .../components/sidebar/host-section-rows.ts | 4 +- .../sidebar/worktree-list-groups.test.ts | 45 +++++++++++++++++++ .../sidebar/worktree-list-groups.ts | 9 ++-- 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/sidebar/host-section-rows.test.ts b/src/renderer/src/components/sidebar/host-section-rows.test.ts index dfd77219b12..702356e26a6 100644 --- a/src/renderer/src/components/sidebar/host-section-rows.test.ts +++ b/src/renderer/src/components/sidebar/host-section-rows.test.ts @@ -403,6 +403,47 @@ describe('addHostSectionRows', () => { ]) }) + it('keeps a pinned row with its explicit worktree host', () => { + const local = repo('local') + const pinned = pinnedItem('ssh-pinned', local, PINNED_GROUP_KEY) + pinned.worktree.hostId = 'ssh:ssh-1' + const rows = [ + pinnedHeader(new Map([['ssh:ssh-1', 1]]), new Map([['ssh:ssh-1', ['ssh-pinned']]])), + pinned, + repoHeader(local), + item('local-wt', local) + ] + + const sectioned = addHostSectionRows({ + rows, + hostOptions: [ + { + id: 'local', + kind: 'local', + label: 'Local Mac', + detail: 'This computer', + health: 'local' + }, + { id: 'ssh:ssh-1', kind: 'ssh', label: 'Builder', detail: 'SSH', health: 'available' } + ], + workspaceHostScope: 'all', + defaultHostId: 'local' + }) + + expect(sectioned.map(rowKey)).toEqual([ + 'host:local', + 'repo:local', + 'local-wt', + 'host:ssh:ssh-1', + PINNED_GROUP_KEY, + 'ssh-pinned' + ]) + expect(sectioned.filter((row) => row.type === 'host-header')).toMatchObject([ + { hostId: 'local', count: 1 }, + { hostId: 'ssh:ssh-1', count: 1 } + ]) + }) + it('does not double-count a collapsed pinned header and its natural duplicate row', () => { const local = repo('local') const ssh = repo('ssh', 'ssh-1') diff --git a/src/renderer/src/components/sidebar/host-section-rows.ts b/src/renderer/src/components/sidebar/host-section-rows.ts index 0a706699095..34022238a89 100644 --- a/src/renderer/src/components/sidebar/host-section-rows.ts +++ b/src/renderer/src/components/sidebar/host-section-rows.ts @@ -69,7 +69,9 @@ function getFolderWorkspaceHostId( function getRowHostId(row: Row, defaultHostId: ExecutionHostId): ExecutionHostId | null { switch (row.type) { case 'item': - return getRepoHostId(row.repo, defaultHostId) + // Why: runtime and SSH snapshots can carry a more precise owner than the + // repo fallback; header localization already uses this worktree identity. + return row.worktree.hostId ?? getRepoHostId(row.repo, defaultHostId) case 'pending-creation': case 'imported-worktrees-card': case 'new-external-worktrees-inbox': diff --git a/src/renderer/src/components/sidebar/worktree-list-groups.test.ts b/src/renderer/src/components/sidebar/worktree-list-groups.test.ts index 7dd94a55ae7..6261443c3e6 100644 --- a/src/renderer/src/components/sidebar/worktree-list-groups.test.ts +++ b/src/renderer/src/components/sidebar/worktree-list-groups.test.ts @@ -1569,6 +1569,51 @@ describe('buildRows with pinned worktrees', () => { ]) }) + it('keeps the inbox group when the repo only has a pinned worktree', () => { + const pinnedWorktree = { ...worktree, id: 'wt-pinned', isPinned: true } + const rows = buildRows( + 'repo', + [pinnedWorktree], + repoMap, + null, + new Set(), + undefined, + undefined, + undefined, + {}, + new Map([[pinnedWorktree.id, pinnedWorktree]]), + false, + undefined, + [], + new Set(), + new Map(), + new Map([[repo.id, { repo, inboxWorktrees: [makeDetectedWorktree()] }]]) + ) + + expect(rows).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'item', + sectionKey: PINNED_GROUP_KEY, + worktree: expect.objectContaining({ id: pinnedWorktree.id }) + }), + expect.objectContaining({ type: 'header', key: `repo:${repo.id}` }), + expect.objectContaining({ + type: 'new-external-worktrees-inbox', + key: `new-external-worktrees-inbox:${repo.id}` + }) + ]) + ) + expect( + rows.some( + (row) => + row.type === 'item' && + row.sectionKey === `repo:${repo.id}` && + row.worktree.id === pinnedWorktree.id + ) + ).toBe(false) + }) + it('does not emit new external worktrees inbox rows outside repo grouping', () => { const rows = buildRows( 'workspace-status', diff --git a/src/renderer/src/components/sidebar/worktree-list-groups.ts b/src/renderer/src/components/sidebar/worktree-list-groups.ts index 7a61827cce8..f384293fd7f 100644 --- a/src/renderer/src/components/sidebar/worktree-list-groups.ts +++ b/src/renderer/src/components/sidebar/worktree-list-groups.ts @@ -1043,11 +1043,6 @@ export function buildRows( settings, projectGrouping }) - // Repos whose only visible worktrees are pinned are surfaced via the pinned - // section, so the repo-grouped inbox loop below must not re-create a group for them. - const visiblePinnedRepoIds = new Set( - worktrees.filter((worktree) => worktree.isPinned).map((worktree) => worktree.repoId) - ) emitPinnedGroup( worktrees, repoMap, @@ -1151,7 +1146,9 @@ export function buildRows( for (const [repoId, candidate] of newExternalWorktreesInboxByRepo) { const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) const key = grouping.key - if (!grouped.has(key) && !visiblePinnedRepoIds.has(repoId)) { + if (!grouped.has(key)) { + // Why: the default policy removes pinned worktrees from natural groups, + // but actionable inbox rows still need a project section to render in. grouped.set(key, { label: grouping.label, items: [],