From fa09d6fd8e009ec0c52899a377c076684b07b8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E8=BE=89?= <42717106+OnlyYu1996@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:46:01 +0800 Subject: [PATCH] fix(tasks): hide repos without remote identity (#9898) --- src/renderer/src/components/TaskPage.tsx | 4 +- .../task-page-default-repo-selection.test.ts | 46 +++++++++++++++++++ .../task-page-default-repo-selection.ts | 10 +++- src/shared/project-host-setup-projection.ts | 6 +++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 2430505d6..89162d72c 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -179,7 +179,6 @@ import { readLinearBoardIssueDragData, writeLinearBoardIssueDragData } from '@/lib/linear-board-drag-payload' -import { isGitRepoKind } from '../../../shared/repo-kind' import { getRepoExecutionHostId } from '../../../shared/execution-host' import { projectHostSetupProjectionFromRepos } from '../../../shared/project-host-setup-projection' import { TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' @@ -238,6 +237,7 @@ import { findTaskPageJiraIssue } from '@/components/task-page-jira-cache-selecto import { getRepoBackedTaskEmptyState } from '@/components/task-page-empty-state' import { getDefaultTaskRepoSelection, + getTaskEligibleRepos, getTaskProjectPickerGroups, normalizeTaskRepoSelection } from '@/components/task-page-default-repo-selection' @@ -3141,7 +3141,7 @@ export default function TaskPage(): React.JSX.Element { const linearConnected = linearStatusCurrent && linearStatus.connected const jiraConnected = jiraStatusCurrent && jiraStatus.connected const submitShortcutLabel = getScreenSubmitShortcutLabel() - const eligibleRepos = useMemo(() => repos.filter((repo) => isGitRepoKind(repo)), [repos]) + const eligibleRepos = useMemo(() => getTaskEligibleRepos(repos), [repos]) // Why: initial selection precedence — explicit preselection > persisted defaultRepoSelection > all eligible; preselection wins so "open tasks for this repo" lands single-repo. const resolvedInitialSelection = useMemo>(() => { diff --git a/src/renderer/src/components/task-page-default-repo-selection.test.ts b/src/renderer/src/components/task-page-default-repo-selection.test.ts index 80d57bcfa..c12df0bb4 100644 --- a/src/renderer/src/components/task-page-default-repo-selection.test.ts +++ b/src/renderer/src/components/task-page-default-repo-selection.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import type { Repo } from '../../../shared/types' import { getDefaultTaskRepoSelection, + getTaskEligibleRepos, getTaskProjectPickerGroups, getTaskProjectPickerRepos, normalizeTaskRepoSelection @@ -18,6 +19,51 @@ function repo(overrides: Partial & Pick): Repo { } } +describe('getTaskEligibleRepos', () => { + it('keeps only Git repos with a resolvable remote identity', () => { + const eligible = getTaskEligibleRepos([ + repo({ id: 'github-upstream', upstream: { owner: 'stablyai', repo: 'orca' } }), + repo({ + id: 'github-icon', + repoIcon: { + type: 'image', + src: 'https://github.com/stablyai.png?size=64', + source: 'github', + label: 'stablyai/orca' + } + }), + repo({ + id: 'gitlab-remote', + gitRemoteIdentity: { + canonicalKey: 'gitlab.example.com/team/orca', + remoteName: 'origin', + remoteUrl: 'git@gitlab.example.com:team/orca.git' + } + }), + repo({ id: 'local-only' }), + repo({ + id: 'incomplete-remote', + gitRemoteIdentity: { + canonicalKey: 'gitlab.example.com/team/incomplete', + remoteName: '', + remoteUrl: 'git@gitlab.example.com:team/incomplete.git' + } + }), + repo({ + id: 'folder-with-remote', + kind: 'folder', + upstream: { owner: 'stablyai', repo: 'docs' } + }) + ]) + + expect(eligible.map((candidate) => candidate.id)).toEqual([ + 'github-upstream', + 'github-icon', + 'gitlab-remote' + ]) + }) +}) + describe('getDefaultTaskRepoSelection', () => { it('selects one source per logical GitHub project', () => { const selection = getDefaultTaskRepoSelection([ diff --git a/src/renderer/src/components/task-page-default-repo-selection.ts b/src/renderer/src/components/task-page-default-repo-selection.ts index 1c10ae637..ac471c2ea 100644 --- a/src/renderer/src/components/task-page-default-repo-selection.ts +++ b/src/renderer/src/components/task-page-default-repo-selection.ts @@ -1,5 +1,9 @@ import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host' -import { getProjectIdentityKey } from '../../../shared/project-host-setup-projection' +import { + getProjectIdentityKey, + hasProjectRemoteIdentity +} from '../../../shared/project-host-setup-projection' +import { isGitRepoKind } from '../../../shared/repo-kind' import type { Repo } from '../../../shared/types' export type TaskProjectPickerGroup = { @@ -8,6 +12,10 @@ export type TaskProjectPickerGroup = { sources: Repo[] } +export function getTaskEligibleRepos(repos: readonly Repo[]): Repo[] { + return repos.filter((repo) => isGitRepoKind(repo) && hasProjectRemoteIdentity(repo)) +} + export function getDefaultTaskRepoSelection(repos: readonly Repo[]): Set { const selectedByProject = new Map() for (const repo of repos) { diff --git a/src/shared/project-host-setup-projection.ts b/src/shared/project-host-setup-projection.ts index d12e8e4eb..d92d214fb 100644 --- a/src/shared/project-host-setup-projection.ts +++ b/src/shared/project-host-setup-projection.ts @@ -80,6 +80,12 @@ export function isGitHubBackedRepo( return getProjectProviderIdentity(repo) !== null } +export function hasProjectRemoteIdentity( + repo: Pick +): boolean { + return getProjectProviderIdentity(repo) !== null || getProjectGitRemoteIdentity(repo) !== null +} + export function getProjectIdentityKey( repo: Pick ): string {