From a6160e818bffb4fc2ec0d6598735dc3b93a7f00c Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:06:49 -0700 Subject: [PATCH] Carry the project host on GitHub item dialog origins Co-authored-by: Orca --- src/main/github/client.test.ts | 12 ++++++--- src/main/github/issues.ts | 16 ++++++++++++ .../src/components/GitHubItemDialog.tsx | 2 ++ .../github-project/ProjectViewWrapper.tsx | 3 +++ .../github-project/project-dialog-state.ts | 13 +++++++--- .../src/hooks/useGitHubSlugMetadata.ts | 26 ++++++++++++------- .../store/slices/github-project-row-owner.ts | 8 +++++- src/renderer/src/store/slices/github.ts | 2 ++ 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/main/github/client.test.ts b/src/main/github/client.test.ts index 860e499d7cf..c810b053451 100644 --- a/src/main/github/client.test.ts +++ b/src/main/github/client.test.ts @@ -134,13 +134,19 @@ vi.mock('./github-api-repository', async (importOriginal) => { // Mirror production: dotcom origin slugs come back pinned to github.com. return slug ? { host: 'github.com', ...slug } : slug }, - getIssueGitHubApiRepository: (repoPath: string, connectionId?: string | null) => - getIssueOwnerRepoMock(repoPath, connectionId), + getIssueGitHubApiRepository: async (repoPath: string, connectionId?: string | null) => { + const slug = await getIssueOwnerRepoMock(repoPath, connectionId) + // Mirror production: issue slugs come back host-qualified to github.com. + return slug ? { host: 'github.com', ...slug } : slug + }, resolveIssueGitHubApiRepositorySource: async ( repoPath: string, _preference: unknown, connectionId?: string | null - ) => ({ source: await getIssueOwnerRepoMock(repoPath, connectionId), fellBack: false }) + ) => { + const slug = await getIssueOwnerRepoMock(repoPath, connectionId) + return { source: slug ? { host: 'github.com', ...slug } : slug, fellBack: false } + } } }) diff --git a/src/main/github/issues.ts b/src/main/github/issues.ts index 44b29625105..45ed0b1a99e 100644 --- a/src/main/github/issues.ts +++ b/src/main/github/issues.ts @@ -65,6 +65,11 @@ export async function getIssue( connectionId, localGitOptions ) + // Why: a connection-backed request has no local cwd, so the non-GitHub + // fallback below would let gh target its default repository. Refuse instead. + if (connectionId && !ownerRepo) { + return null + } await acquire() try { if (ownerRepo) { @@ -126,6 +131,17 @@ export async function listIssues( connectionId, localGitOptions ) + // Why: a connection-backed request has no local cwd, so the non-GitHub + // fallback below would let gh list its default repository. Refuse instead. + if (connectionId && !ownerRepo) { + return { + items: [], + error: { + type: 'not_found', + message: 'Could not resolve GitHub owner/repo for this repository' + } + } + } await acquire() try { if (ownerRepo) { diff --git a/src/renderer/src/components/GitHubItemDialog.tsx b/src/renderer/src/components/GitHubItemDialog.tsx index 30c4a491bd4..6043aea13aa 100644 --- a/src/renderer/src/components/GitHubItemDialog.tsx +++ b/src/renderer/src/components/GitHubItemDialog.tsx @@ -297,6 +297,8 @@ function normalizeItemDialogTab( export type GitHubItemDialogProjectOrigin = { owner: string repo: string + /** GitHub host (e.g. GHES); absent means github.com. */ + host?: string number: number type: 'issue' | 'pr' projectId: string diff --git a/src/renderer/src/components/github-project/ProjectViewWrapper.tsx b/src/renderer/src/components/github-project/ProjectViewWrapper.tsx index 74a2d974db4..7d7af9bcd95 100644 --- a/src/renderer/src/components/github-project/ProjectViewWrapper.tsx +++ b/src/renderer/src/components/github-project/ProjectViewWrapper.tsx @@ -399,6 +399,7 @@ export default function ProjectViewWrapper({ selectedRepoIds }: Props): React.JS const [repoNotInOrca, setRepoNotInOrca] = useState<{ owner: string repo: string + host?: string url: string | null } | null>(null) const liveRepoIds = useMemo(() => new Set(repos.map((repo) => repo.id)), [repos]) @@ -597,6 +598,7 @@ export default function ProjectViewWrapper({ selectedRepoIds }: Props): React.JS const resolution = resolveSelectedProjectRowRepo({ row, lookupSlug, + host: table.project.host, slugIndexReady, selectedRepoIds }) @@ -614,6 +616,7 @@ export default function ProjectViewWrapper({ selectedRepoIds }: Props): React.JS setRepoNotInOrca({ owner: origin.owner, repo: origin.repo, + host: origin.host, url: row.content.url ?? null }) return diff --git a/src/renderer/src/components/github-project/project-dialog-state.ts b/src/renderer/src/components/github-project/project-dialog-state.ts index 88f71c9fcb1..58ba51d0451 100644 --- a/src/renderer/src/components/github-project/project-dialog-state.ts +++ b/src/renderer/src/components/github-project/project-dialog-state.ts @@ -6,27 +6,30 @@ type SlugProjectDialogState = { origin: { owner: string repo: string + host?: string } } type RepoNotInOrcaDialogState = { owner: string repo: string + host?: string } type RepoMatch = { id: string } -type LookupSlug = (slug: string) => readonly RepoMatch[] +type LookupSlug = (slug: string, host?: string) => readonly RepoMatch[] function shouldCloseFallbackDialog(args: { lookupSlug: LookupSlug selectedRepoIds: ReadonlySet owner: string repo: string + host?: string }): boolean { - const matches = args.lookupSlug(`${args.owner}/${args.repo}`) + const matches = args.lookupSlug(`${args.owner}/${args.repo}`, args.host) const selectedMatchCount = matches.filter((match) => args.selectedRepoIds.has(match.id)).length const unselectedMatchCount = matches.length - selectedMatchCount return selectedMatchCount > 0 || unselectedMatchCount > 0 @@ -67,7 +70,8 @@ export function resolveMissingRepoProjectDialogState< lookupSlug, selectedRepoIds, owner: slugDialog.origin.owner, - repo: slugDialog.origin.repo + repo: slugDialog.origin.repo, + host: slugDialog.origin.host }) ? null : slugDialog, @@ -77,7 +81,8 @@ export function resolveMissingRepoProjectDialogState< lookupSlug, selectedRepoIds, owner: repoNotInOrca.owner, - repo: repoNotInOrca.repo + repo: repoNotInOrca.repo, + host: repoNotInOrca.host }) ? null : repoNotInOrca diff --git a/src/renderer/src/hooks/useGitHubSlugMetadata.ts b/src/renderer/src/hooks/useGitHubSlugMetadata.ts index 4b3c0769640..90965be0d52 100644 --- a/src/renderer/src/hooks/useGitHubSlugMetadata.ts +++ b/src/renderer/src/hooks/useGitHubSlugMetadata.ts @@ -18,6 +18,7 @@ import { getFreshMetadata, loadMetadata } from './metadata-request-cache' +import { githubRepoIdentityKey } from '../../../shared/github-repository-identity-key' type MetadataState = { data: T @@ -36,7 +37,8 @@ export function clearGitHubSlugMetadataCache(): void { export function useRepoLabelsBySlug( owner: string | null, repo: string | null, - settings?: Pick | null + settings?: Pick | null, + host?: string ): MetadataState { const [state, setState] = useState>({ data: [], @@ -55,10 +57,11 @@ export function useRepoLabelsBySlug( return } const target = getActiveRuntimeTarget({ activeRuntimeEnvironmentId }) + const repositoryKey = githubRepoIdentityKey({ owner, repo, host }) const key = target.kind === 'environment' - ? `runtime:${target.environmentId}:${owner}/${repo}` - : `${owner}/${repo}` + ? `runtime:${target.environmentId}:${repositoryKey}` + : repositoryKey const cached = getFreshMetadata(slugLabelStore, key) if (cached) { @@ -85,10 +88,10 @@ export function useRepoLabelsBySlug( ? callRuntimeRpc( target, 'github.project.listLabelsBySlug', - { owner, repo }, + { owner, repo, ...(host ? { host } : {}) }, { timeoutMs: 30_000 } ) - : window.api.gh.listLabelsBySlug({ owner, repo }) + : window.api.gh.listLabelsBySlug({ owner, repo, ...(host ? { host } : {}) }) ).then((res) => { if (!res.ok) { throw new Error(res.error.message) @@ -113,7 +116,7 @@ export function useRepoLabelsBySlug( error: err instanceof Error ? err.message : 'Failed to load labels' })) }) - }, [owner, repo, activeRuntimeEnvironmentId]) + }, [owner, repo, host, activeRuntimeEnvironmentId]) return state } @@ -122,7 +125,8 @@ export function useRepoAssigneesBySlug( owner: string | null, repo: string | null, seedLogins?: string[], - settings?: Pick | null + settings?: Pick | null, + host?: string ): MetadataState { const [state, setState] = useState>({ data: [], @@ -143,10 +147,11 @@ export function useRepoAssigneesBySlug( return } const target = getActiveRuntimeTarget({ activeRuntimeEnvironmentId }) + const repositoryKey = githubRepoIdentityKey({ owner, repo, host }) const key = target.kind === 'environment' - ? `runtime:${target.environmentId}:${owner}/${repo}#${seedKey}` - : `${owner}/${repo}#${seedKey}` + ? `runtime:${target.environmentId}:${repositoryKey}#${seedKey}` + : `${repositoryKey}#${seedKey}` const cached = getFreshMetadata(slugAssigneeStore, key) if (cached) { @@ -173,6 +178,7 @@ export function useRepoAssigneesBySlug( const args = { owner, repo, + ...(host ? { host } : {}), ...(seedKey ? { seedLogins: seedKey.split(',') } : {}) } loadMetadata(slugAssigneeStore, key, () => @@ -208,7 +214,7 @@ export function useRepoAssigneesBySlug( error: err instanceof Error ? err.message : 'Failed to load assignees' })) }) - }, [owner, repo, seedKey, activeRuntimeEnvironmentId]) + }, [owner, repo, host, seedKey, activeRuntimeEnvironmentId]) return state } diff --git a/src/renderer/src/store/slices/github-project-row-owner.ts b/src/renderer/src/store/slices/github-project-row-owner.ts index 936818c8df2..db3a4b3333e 100644 --- a/src/renderer/src/store/slices/github-project-row-owner.ts +++ b/src/renderer/src/store/slices/github-project-row-owner.ts @@ -15,11 +15,17 @@ export function settingsForProjectRowOwner( state: RepoOwnerState, owner: string, repo: string, + host?: string, fallbackSettings: | Pick | null | undefined = state.settings ): Pick | null | undefined { - const matchedRepo = lookupReposBySlugFromCache(state.repos, state.settings, `${owner}/${repo}`)[0] + const matchedRepo = lookupReposBySlugFromCache( + state.repos, + state.settings, + `${owner}/${repo}`, + host + )[0] return matchedRepo ? getSettingsForRepoRuntimeOwner(state, matchedRepo.id) : fallbackSettings } diff --git a/src/renderer/src/store/slices/github.ts b/src/renderer/src/store/slices/github.ts index 7b16e952720..c6a84c201b8 100644 --- a/src/renderer/src/store/slices/github.ts +++ b/src/renderer/src/store/slices/github.ts @@ -2377,6 +2377,7 @@ export const createGitHubSlice: StateCreator = (s get(), owner, repo, + table.project.host, settingsForProjectViewCacheKey(get().settings, cacheKey) ) ) @@ -2506,6 +2507,7 @@ export const createGitHubSlice: StateCreator = (s get(), owner, repo, + table.project.host, settingsForProjectViewCacheKey(get().settings, cacheKey) ) )