diff --git a/src/renderer/src/store/selectors.test.ts b/src/renderer/src/store/selectors.test.ts index ba77ae902d0..345a25c0eec 100644 --- a/src/renderer/src/store/selectors.test.ts +++ b/src/renderer/src/store/selectors.test.ts @@ -297,27 +297,47 @@ describe('store selectors', () => { displayName: 'hub', executionHostId: toRuntimeExecutionHostId('hub-a') }) + const local = makeRepo({ + id: 'hub-repo', + path: '/local/repo', + displayName: 'local', + executionHostId: 'local' + }) + const activeState = { + activeRepoId: 'hub-repo', + activeWorkspaceExecutionHostId: toSshExecutionHostId('hub-private-target') + } + + for (const repos of [[repo], [local, repo], [repo, local]]) { + expect(selectRepoByIdForActiveWorkspace({ ...activeState, repos }, 'hub-repo')).toBe(repo) + } + // Why: useGitStatusPolling gates every lane on this exact expression, so a null repo silently stops polling. + const activeRepo = selectRepoByIdForActiveWorkspace( + { ...activeState, repos: [repo] }, + 'hub-repo' + ) + expect(activeRepo ? isGitRepoKind(activeRepo) : false).toBe(true) + }) + + it('fails a paired-hub repo fallback closed when rival HUB rows share the repo ID', () => { + const repoForHub = (environmentId: string) => + makeRepo({ + id: 'hub-repo', + path: `/hub/${environmentId}/repo`, + displayName: environmentId, + executionHostId: toRuntimeExecutionHostId(environmentId) + }) expect( selectRepoByIdForActiveWorkspace( { - repos: [repo], + repos: [repoForHub('hub-a'), repoForHub('hub-b')], activeRepoId: 'hub-repo', - activeWorkspaceExecutionHostId: toSshExecutionHostId('hub-private-target') + activeWorkspaceExecutionHostId: toSshExecutionHostId('shared-private-target') }, 'hub-repo' ) - ).toBe(repo) - // Why: useGitStatusPolling gates every lane on this exact expression, so a null repo silently stops polling. - const activeRepo = selectRepoByIdForActiveWorkspace( - { - repos: [repo], - activeRepoId: 'hub-repo', - activeWorkspaceExecutionHostId: toSshExecutionHostId('hub-private-target') - }, - 'hub-repo' - ) - expect(activeRepo ? isGitRepoKind(activeRepo) : false).toBe(true) + ).toBeNull() }) it('still prefers the host-matching row when one repo ID spans hosts', () => { diff --git a/src/renderer/src/store/selectors.ts b/src/renderer/src/store/selectors.ts index fec5a2481ae..3e41f905875 100644 --- a/src/renderer/src/store/selectors.ts +++ b/src/renderer/src/store/selectors.ts @@ -183,22 +183,22 @@ export function selectRepoByIdForActiveWorkspace( } const repo = getCachedRepoMap(state.repos).get(repoId) ?? null if (repoId === state.activeRepoId && state.activeWorkspaceExecutionHostId) { - const hostMatch = state.repos.find( - (candidate) => - candidate.id === repoId && - getRepoExecutionHostId(candidate) === state.activeWorkspaceExecutionHostId + const repoCandidates = state.repos.filter((candidate) => candidate.id === repoId) + const hostMatch = repoCandidates.find( + (candidate) => getRepoExecutionHostId(candidate) === state.activeWorkspaceExecutionHostId ) if (hostMatch) { return hostMatch } // Why: withRepoHostOwnership keeps a paired-hub worktree on its own SSH host while the repo // stays hub-owned, so that one mismatch still names the right repo; every other stays closed. - const isPairedHubRepo = - parseExecutionHostId(state.activeWorkspaceExecutionHostId)?.kind === 'ssh' && - parseExecutionHostId(repo ? getRepoExecutionHostId(repo) : undefined)?.kind === 'runtime' - if (!isPairedHubRepo) { + if (parseExecutionHostId(state.activeWorkspaceExecutionHostId)?.kind !== 'ssh') { return null } + const pairedHubRepos = repoCandidates.filter( + (candidate) => parseExecutionHostId(getRepoExecutionHostId(candidate))?.kind === 'runtime' + ) + return pairedHubRepos.length === 1 ? pairedHubRepos[0] : null } return repo }