diff --git a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx index 0080f4348d..665f085d27 100644 --- a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx +++ b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx @@ -149,12 +149,11 @@ import { handoffPrompt, handoffReviewComments, latestPullRequestReviewOutcomes, - loadingPullRequestCheckoutCommand, isStackedPullRequestBase, pullRequestActionMenuHasGroup, pullRequestActionNeedsHostRefresh, pullRequestComposerTarget, - pullRequestCheckoutCommand, + panelPullRequestCheckoutCommand, pullRequestFindingKey, pullRequestHandoffLabels, PULL_REQUEST_MERGE_METHOD_LABELS, @@ -765,14 +764,12 @@ export function PullRequestDetailPanel({ repositoryUrl !== null ? new URL(`/${encodeURIComponent(detail.author.login)}`, repositoryUrl).toString() : null; - const checkoutCommand = detail - ? pullRequestCheckoutCommand( - detail.provider, - detail.number, - detail.headBranch, - detail.headRepositoryNameWithOwner, - ) - : null; + const checkoutCommand = panelPullRequestCheckoutCommand({ + reference, + identity: repositoryIdentity, + summary: handoffSummary, + headRepositoryNameWithOwner: detail?.headRepositoryNameWithOwner, + }); const onCheckoutCommandError = useCallback((error: Error) => { toastManager.add({ type: "error", diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts index 57affb3076..9c0a93ea0d 100644 --- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts +++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts @@ -29,6 +29,7 @@ import { isPullRequestVerdictStale, isStackedPullRequestBase, loadingPullRequestCheckoutCommand, + panelPullRequestCheckoutCommand, isThreadOwnPullRequest, latestPullRequestReviewOutcomes, newestPullRequestCommitAt, @@ -114,6 +115,51 @@ describe("pull request checkout commands", () => { ), ).toBeNull(); }); + + it("keeps a checkout command available from the list summary while detail loads", () => { + const pullRequest = reference("github.com"); + expect( + panelPullRequestCheckoutCommand({ + reference: pullRequest, + identity: undefined, + summary: { provider: "github", number: 42, headBranch: "topic" }, + }), + ).toBe("gh pr checkout 42"); + expect( + panelPullRequestCheckoutCommand({ + reference: pullRequest, + identity: undefined, + summary: null, + }), + ).toBe("gh pr checkout 42"); + }); + + it("does not invent a checkout command for an unknown host or incomplete Bitbucket detail", () => { + expect( + panelPullRequestCheckoutCommand({ + reference: reference("forge.example"), + identity: undefined, + summary: null, + }), + ).toBeNull(); + expect( + panelPullRequestCheckoutCommand({ + reference: reference("bitbucket.org"), + identity: undefined, + summary: { provider: "bitbucket", number: 42, headBranch: "topic" }, + }), + ).toBeNull(); + expect( + panelPullRequestCheckoutCommand({ + reference: reference("bitbucket.org"), + identity: undefined, + summary: { provider: "bitbucket", number: 42, headBranch: "topic" }, + headRepositoryNameWithOwner: "acme/web", + }), + ).toBe( + "git clone --single-branch --branch topic https://bitbucket.org/acme/web.git t3code-pr-42", + ); + }); }); const TIMELINE_SOURCE: Pick< diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts index b34cab9b5c..70b161c9c7 100644 --- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts +++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts @@ -145,6 +145,23 @@ export function loadingPullRequestCheckoutCommand( return pullRequestCheckoutCommand(provider, reference.number, ""); } +/** Keep the checkout affordance stable while richer PR detail is loading. */ +export function panelPullRequestCheckoutCommand(input: { + readonly reference: PullRequestRef; + readonly identity: RepositoryIdentity | null | undefined; + readonly summary: Pick | null; + readonly headRepositoryNameWithOwner?: string | null | undefined; +}): string | null { + return input.summary + ? pullRequestCheckoutCommand( + input.summary.provider, + input.summary.number, + input.summary.headBranch, + input.headRepositoryNameWithOwner, + ) + : loadingPullRequestCheckoutCommand(input.reference, input.identity); +} + /** Activity changes only when the same host resource reports a newer revision. */ export function shouldRefreshPullRequestActivity( previous: { readonly key: string; readonly updatedAt: string } | null,