diff --git a/src/renderer/src/components/WorktreeJumpPalette.test.tsx b/src/renderer/src/components/WorktreeJumpPalette.test.tsx index 6d55be6728d..9e52a9ffc5d 100644 --- a/src/renderer/src/components/WorktreeJumpPalette.test.tsx +++ b/src/renderer/src/components/WorktreeJumpPalette.test.tsx @@ -230,6 +230,10 @@ describe('WorktreeJumpPalette', () => { it('renders the owning host badge on worktree and tab rows', async () => { await renderPalette({ ...makeRecentTabState(), + worktreesByRepo: { + ...makeRecentTabState().worktreesByRepo, + folder: [makeWorktree('folder', 'Folder', { repoId: 'folder', hostId: 'ssh:ssh-1' })] + }, repos: [makeRepo(), { ...makeRepo(), id: 'ssh-repo', connectionId: 'ssh-1' }], sshTargetLabels: new Map([['ssh-1', 'Builder']]), sshConnectionStates: new Map([ @@ -238,7 +242,9 @@ describe('WorktreeJumpPalette', () => { }) const badge = `[aria-label="Host: ${getExecutionHostLabel('local')}"]` - expect(testContainer.querySelector(`[data-command-item*="worktree:"] ${badge}`)).not.toBeNull() + expect( + testContainer.querySelector('[data-command-item*="folder"] [aria-label="Host: Builder"]') + ).not.toBeNull() expect( testContainer.querySelector(`[data-command-item*="workspace-tab:"] ${badge}`) ).not.toBeNull() diff --git a/src/renderer/src/components/WorktreeJumpPalette.tsx b/src/renderer/src/components/WorktreeJumpPalette.tsx index 4f0b4a6bff4..db80a694f31 100644 --- a/src/renderer/src/components/WorktreeJumpPalette.tsx +++ b/src/renderer/src/components/WorktreeJumpPalette.tsx @@ -154,7 +154,11 @@ import { buildPaletteListEntryRenderKeys } from '@/components/cmd-j/palette-list import { formatPaletteSessionAge } from '@/components/cmd-j/palette-session-age' import PaletteFilterMenu from '@/components/cmd-j/PaletteFilterMenu' import PaletteFilterChips from '@/components/cmd-j/PaletteFilterChips' -import { buildPaletteFilterModel } from '@/components/cmd-j/palette-filter-options' +import { + buildPaletteFilterModel, + resolveRepoFilterHostId, + resolveWorktreeFilterHostId +} from '@/components/cmd-j/palette-filter-options' import { getProjectGroupExecutionHostIdForRows } from '@/components/sidebar/worktree-list/listing/host-filtering' import { buildPaletteFilterPredicate, @@ -895,6 +899,17 @@ function WorktreeJumpPaletteContent({ }, [filterModel]) const filterActive = isPaletteFilterActive(filter) const hostFilterActive = filter.hostIds.length > 0 + const getWorktreeHostBadge = useCallback( + (worktree: Worktree | undefined, fallbackHostId?: ExecutionHostId) => + getPaletteHostBadge( + worktree + ? resolveWorktreeFilterHostId(worktree, filterModel.hostIdByRepoId, defaultHostId) + : fallbackHostId, + hostOptions, + hostFilterActive + ), + [defaultHostId, filterModel.hostIdByRepoId, hostFilterActive, hostOptions] + ) const filterPredicate = useMemo( () => buildPaletteFilterPredicate(filter, filterModel), [filter, filterModel] @@ -1102,17 +1117,13 @@ function WorktreeJumpPaletteContent({ const hostLabelByWorktreeId = useMemo(() => { const labels = new Map() for (const worktree of allWorktrees) { - const badge = getPaletteHostBadge( - resolveRepoForWorktree(worktree), - hostOptions, - hostFilterActive - ) + const badge = getWorktreeHostBadge(worktree) if (badge) { labels.set(getWorktreeHostIdentity(worktree), badge.label) } } return labels - }, [allWorktrees, hostFilterActive, hostOptions, resolveRepoForWorktree]) + }, [allWorktrees, getWorktreeHostBadge]) // Why keyed on the unsorted list: normalized documents depend only on text // inputs, so re-sorting for recency re-ranks without re-normalizing anything. @@ -3222,7 +3233,7 @@ function WorktreeJumpPaletteContent({ : null const isSshDisconnected = sshStatus != null && sshStatus !== 'connected' const sessionAge = formatPaletteSessionAge(worktree.lastActivityAt, paletteNowMs) - const hostBadge = getPaletteHostBadge(repo, hostOptions, hostFilterActive) + const hostBadge = getWorktreeHostBadge(worktree) return ( { settings: { activeRuntimeEnvironmentId: null } }) - expect(getPaletteHostBadge({ connectionId: null }, hosts)).toBeNull() + expect(getPaletteHostBadge('local', hosts)).toBeNull() }) it('returns null when the only non-local host is configured but disconnected', () => { @@ -32,7 +32,7 @@ describe('getPaletteHostBadge', () => { // No connection state -> the SSH host is 'disconnected', so there's nothing // live to disambiguate from and rows stay badge-free. - expect(getPaletteHostBadge({ connectionId: null }, hosts)).toBeNull() + expect(getPaletteHostBadge('local', hosts)).toBeNull() }) it('badges a disconnected host anyway when a host filter is applied', () => { @@ -44,7 +44,7 @@ describe('getPaletteHostBadge', () => { // Why: with a host filter on, the badge is the only thing explaining which // rows survived, so liveness must not suppress it. - expect(getPaletteHostBadge({ connectionId: 'ssh-1' }, hosts, true)).toEqual({ + expect(getPaletteHostBadge('ssh:ssh-1', hosts, true)).toEqual({ hostId: 'ssh:ssh-1', label: 'Builder' }) @@ -58,7 +58,7 @@ describe('getPaletteHostBadge', () => { settings: { activeRuntimeEnvironmentId: null } }) - expect(getPaletteHostBadge({ connectionId: null }, hosts)).toEqual({ + expect(getPaletteHostBadge('local', hosts)).toEqual({ hostId: 'local', label: LOCAL_HOST_LABEL }) @@ -72,7 +72,7 @@ describe('getPaletteHostBadge', () => { settings: { activeRuntimeEnvironmentId: null } }) - expect(getPaletteHostBadge({ connectionId: 'ssh-1' }, hosts)).toEqual({ + expect(getPaletteHostBadge('ssh:ssh-1', hosts)).toEqual({ hostId: 'ssh:ssh-1', label: 'Builder' }) @@ -104,7 +104,7 @@ describe('getPaletteHostBadge', () => { ]) }) - expect(getPaletteHostBadge({ executionHostId: 'runtime:env-1' }, hosts)).toEqual({ + expect(getPaletteHostBadge('runtime:env-1', hosts)).toEqual({ hostId: 'runtime:env-1', label: 'env-1' }) @@ -117,10 +117,10 @@ describe('getPaletteHostBadge', () => { settings: { activeRuntimeEnvironmentId: null } }) - expect(getPaletteHostBadge({ connectionId: null }, hosts)).toBeNull() + expect(getPaletteHostBadge('local', hosts)).toBeNull() }) - it('maps repos with no executionHostId/connectionId to local', () => { + it('uses the local host label for local identities', () => { const hosts = buildSidebarHostOptions({ repos: [{ connectionId: 'ssh-1' }], sshTargetLabels: new Map([['ssh-1', 'Builder']]), @@ -128,13 +128,13 @@ describe('getPaletteHostBadge', () => { settings: { activeRuntimeEnvironmentId: null } }) - expect(getPaletteHostBadge({}, hosts)).toEqual({ + expect(getPaletteHostBadge('local', hosts)).toEqual({ hostId: 'local', label: LOCAL_HOST_LABEL }) }) - it('returns null when the repo is missing', () => { + it('returns null when the host identity is missing', () => { const hosts = buildSidebarHostOptions({ repos: [{ connectionId: 'ssh-1' }], sshTargetLabels: new Map([['ssh-1', 'Builder']]), diff --git a/src/renderer/src/components/cmd-j/palette-host-badge.ts b/src/renderer/src/components/cmd-j/palette-host-badge.ts index 8ac94adeae4..ea9e199088e 100644 --- a/src/renderer/src/components/cmd-j/palette-host-badge.ts +++ b/src/renderer/src/components/cmd-j/palette-host-badge.ts @@ -1,9 +1,4 @@ -import type { Repo } from '../../../../shared/repo-types' -import { - getRepoExecutionHostId, - LOCAL_EXECUTION_HOST_ID, - type ExecutionHostId -} from '../../../../shared/execution-host' +import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../../../shared/execution-host' import type { SidebarHostOption } from '../sidebar/sidebar-host-options' export type PaletteHostBadge = { @@ -22,16 +17,15 @@ function hasActiveRemoteHost(hostOptions: readonly SidebarHostOption[]): boolean } export function getPaletteHostBadge( - repo: Pick | null | undefined, + hostId: ExecutionHostId | null | undefined, hostOptions: readonly SidebarHostOption[], // Why: with a host filter applied the badge is the only thing explaining which // rows survived, so it must show even when every remote is disconnected. alwaysShowHostLabel = false ): PaletteHostBadge | null { - if (!repo || (!alwaysShowHostLabel && !hasActiveRemoteHost(hostOptions))) { + if (!hostId || (!alwaysShowHostLabel && !hasActiveRemoteHost(hostOptions))) { return null } - const hostId = getRepoExecutionHostId(repo) const host = hostOptions.find((option) => option.id === hostId) if (!host) { return null