fix(web): open each linked PR in its own tab from the "+" menu (#1636)

This commit is contained in:
Clément Fiere
2026-07-09 20:23:17 +02:00
committed by GitHub
parent e7aa5de634
commit 7e2a460f59
12 changed files with 537 additions and 67 deletions
@@ -5,6 +5,7 @@ import { IconGitPullRequest } from "@tabler/icons-react";
import { cn } from "@/lib/utils";
import { PRCIPopover } from "@/components/github/pr-ci-popover";
import { getPRStatusColor, pickDefaultPR } from "@/components/github/pr-task-icon";
import { prIdentitySlug } from "@/components/github/pr-utils";
import { usePRFeedbackBackgroundSync } from "@/hooks/domains/github/use-pr-ci-popover";
import type { TaskPR } from "@/lib/types/github";
@@ -18,8 +19,16 @@ function PRFeedbackWarmer({ pr }: { pr: TaskPR }) {
return null;
}
/**
* DOM id for a PR tab's roving-tabindex button.
*
* @param uid - The popover instance's `useId()` value, so ids stay unique
* across multiple popovers mounted at once.
* @param pr - The PR the tab renders; identity-scoped so two PRs never
* collide within one popover.
*/
function prTabDomId(uid: string, pr: TaskPR): string {
return `${uid}-pr-tab-${pr.repo}-${pr.pr_number}`;
return `${uid}-pr-tab-${prIdentitySlug(pr)}`;
}
function PRTab({
@@ -45,7 +54,7 @@ function PRTab({
tabIndex={active ? 0 : -1}
aria-selected={active}
aria-controls={panelId}
data-testid={`pr-popover-tab-${pr.repo}-${pr.pr_number}`}
data-testid={`pr-popover-tab-${prIdentitySlug(pr)}`}
data-active={active ? "true" : "false"}
onClick={() => onSelect(pr)}
className={cn(
@@ -624,10 +624,10 @@ describe("PRStatusChip — multiple PRs", () => {
});
expect(document.querySelector(DRAWER_SELECTOR)).not.toBeNull();
expect(document.querySelector("[data-testid='pr-multi-popover']")).not.toBeNull();
// One tab per PR (testid is repo-scoped so same-number PRs on
// different repos stay unique).
expect(document.querySelector("[data-testid='pr-popover-tab-demo-1']")).not.toBeNull();
expect(document.querySelector("[data-testid='pr-popover-tab-demo-2']")).not.toBeNull();
// One tab per PR (testid is owner+repo-scoped so same-number PRs on
// different repos, or repos sharing a name across owners, stay unique).
expect(document.querySelector("[data-testid='pr-popover-tab-acme-demo-1']")).not.toBeNull();
expect(document.querySelector("[data-testid='pr-popover-tab-acme-demo-2']")).not.toBeNull();
});
});
});
@@ -34,6 +34,7 @@ import {
isPRWaitingOnBranchProtection,
} from "@/components/github/pr-task-icon";
import { prTaskKey } from "@/components/github/pr-detail-panel";
import { prIdentitySlug } from "@/components/github/pr-utils";
import { PR_CI_DESKTOP_POPOVER_SCROLL_CLASS, PRCIPopover } from "@/components/github/pr-ci-popover";
import { MultiPRCIPopover } from "@/components/github/multi-pr-ci-popover";
import { useAppStore } from "@/components/state-provider";
@@ -307,7 +308,7 @@ function MultiPRMenuContent({ prs }: { prs: TaskPR[] }) {
key={pr.id}
onClick={() => addPRPanel(prTaskKey(pr), activeSessionId)}
className="cursor-pointer gap-2"
data-testid={`pr-topbar-menu-item-${pr.pr_number}`}
data-testid={`pr-topbar-menu-item-${prIdentitySlug(pr)}`}
>
<IconGitPullRequest className={`h-4 w-4 shrink-0 ${getPRStatusColor(pr)}`} />
<div className="flex flex-col min-w-0 flex-1">
+11
View File
@@ -1,4 +1,15 @@
import type { TaskPR } from "@/lib/types/github";
/** Shared label for the PR dockview panel tab and dropdown items. */
export function prPanelLabel(prNumber: number): string {
return `PR #${prNumber}`;
}
/**
* Stable per-PR identifier for React keys, DOM ids, and e2e test ids.
* Includes owner + repo + PR number so two repos that share a name (or two
* owners that share a repo name) never collide on PR number alone.
*/
export function prIdentitySlug(pr: TaskPR): string {
return `${pr.owner}-${pr.repo}-${pr.pr_number}`;
}