From 8e27ef68418cc36f5beb1b7a32fb7741f61930dd Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Thu, 24 Sep 2026 03:29:31 -0600 Subject: [PATCH] fix(web): keep PR checkout command during detail loading --- .../pullRequest/PullRequestDetailPanel.tsx | 17 +++---- .../pullRequestDetail.logic.test.ts | 46 +++++++++++++++++++ .../pullRequest/pullRequestDetail.logic.ts | 17 +++++++ 3 files changed, 70 insertions(+), 10 deletions(-) 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,