diff --git a/apps/web/src/components/BranchToolbarBranchSelector.tsx b/apps/web/src/components/BranchToolbarBranchSelector.tsx
index 72391f714fc..b7c5bd1cab8 100644
--- a/apps/web/src/components/BranchToolbarBranchSelector.tsx
+++ b/apps/web/src/components/BranchToolbarBranchSelector.tsx
@@ -16,6 +16,7 @@ import {
import { useComposerDraftStore, type DraftId } from "../composerDraftStore";
import { readEnvironmentApi } from "../environmentApi";
+import { useOpenPrLink } from "../lib/openPullRequestLink";
import { useVcsStatus } from "../lib/vcsStatusState";
import { useVcsRefs, vcsRefManager } from "../lib/vcsRefState";
import { newCommandId } from "../lib/utils";
@@ -32,6 +33,11 @@ import {
resolveEffectiveEnvMode,
shouldIncludeBranchPickerItem,
} from "./BranchToolbar.logic";
+import {
+ ChangeRequestStatusIcon,
+ prStatusIndicator,
+ resolveThreadPr,
+} from "./ThreadStatusIndicators";
import { Button } from "./ui/button";
import {
Combobox,
@@ -44,6 +50,7 @@ import {
ComboboxTrigger,
} from "./ui/combobox";
import { stackedThreadToast, toastManager } from "./ui/toast";
+import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
interface BranchToolbarBranchSelectorProps {
className?: string;
@@ -514,6 +521,16 @@ export function BranchToolbarBranchSelector({
resolvedActiveBranch,
});
+ // PR pill shown next to the branch selector when the active branch has one.
+ const branchPr = resolveThreadPr(resolvedActiveBranch, branchStatusQuery.data ?? null);
+ const branchPrStatus = prStatusIndicator(branchPr, branchStatusQuery.data?.sourceControlProvider);
+ // Action-oriented tooltip (the pill opens the PR), distinct from the sidebar's
+ // state-description tooltip.
+ const branchPrTooltip = branchPr
+ ? `Open ${sourceControlPresentation.terminology.singular} #${branchPr.number} (${branchPr.state}) in browser`
+ : "";
+ const openPrLink = useOpenPrLink();
+
function renderPickerItem(itemValue: string, index: number) {
if (checkoutPullRequestItemValue && itemValue === checkoutPullRequestItemValue) {
return (
@@ -610,15 +627,38 @@ export function BranchToolbarBranchSelector({
open={isBranchMenuOpen}
value={resolvedActiveBranch}
>
- }
- className={cn("min-w-0 text-muted-foreground/70 hover:text-foreground/80", className)}
- disabled={isInitialBranchesLoadPending || isBranchActionPending}
- >
-
- {triggerLabel}
-
-
+
+ {branchPr && branchPrStatus ? (
+
+ openPrLink(event, branchPrStatus.url)}
+ className={cn(
+ "inline-flex shrink-0 items-center gap-0.5 rounded px-1 py-0.5 text-[11px] font-medium tabular-nums transition-colors hover:bg-muted/60",
+ branchPrStatus.colorClass,
+ )}
+ />
+ }
+ >
+
+ #{branchPr.number}
+
+ {branchPrTooltip}
+
+ ) : null}
+ }
+ className="min-w-0 text-muted-foreground/70 hover:text-foreground/80"
+ disabled={isInitialBranchesLoadPending || isBranchActionPending}
+ >
+
+ {triggerLabel}
+
+
+
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index dc5acaaadc7..e6471f4d763 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -63,6 +63,7 @@ import {
import { usePrimaryEnvironmentId } from "../environments/primary";
import { isElectron } from "../env";
import { APP_STAGE_LABEL, APP_VERSION } from "../branding";
+import { useOpenPrLink } from "../lib/openPullRequestLink";
import { isTerminalFocused } from "../lib/terminalFocus";
import { isMacPlatform, newCommandId } from "../lib/utils";
import {
@@ -1011,29 +1012,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
);
},
});
- const openPrLink = useCallback((event: React.MouseEvent, prUrl: string) => {
- event.preventDefault();
- event.stopPropagation();
-
- const api = readLocalApi();
- if (!api) {
- toastManager.add({
- type: "error",
- title: "Link opening is unavailable.",
- });
- return;
- }
-
- void api.shell.openExternal(prUrl).catch((error) => {
- toastManager.add(
- stackedThreadToast({
- type: "error",
- title: "Unable to open pull request link",
- description: error instanceof Error ? error.message : "An error occurred.",
- }),
- );
- });
- }, []);
+ const openPrLink = useOpenPrLink();
const sidebarThreads = useStore(
useShallow(
useMemo(
diff --git a/apps/web/src/lib/openPullRequestLink.ts b/apps/web/src/lib/openPullRequestLink.ts
new file mode 100644
index 00000000000..899e5c38c58
--- /dev/null
+++ b/apps/web/src/lib/openPullRequestLink.ts
@@ -0,0 +1,37 @@
+import { type MouseEvent, useCallback } from "react";
+
+import { stackedThreadToast, toastManager } from "../components/ui/toast";
+import { readLocalApi } from "../localApi";
+
+/**
+ * Returns a click handler that opens a pull request URL in the system browser.
+ *
+ * Stops event propagation/default so activating the link does not also trigger
+ * an enclosing row or trigger (e.g. opening the branch dropdown), and surfaces a
+ * toast when the local API is unavailable or the open fails.
+ */
+export function useOpenPrLink() {
+ return useCallback((event: MouseEvent, prUrl: string) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const api = readLocalApi();
+ if (!api) {
+ toastManager.add({
+ type: "error",
+ title: "Link opening is unavailable.",
+ });
+ return;
+ }
+
+ void api.shell.openExternal(prUrl).catch((error) => {
+ toastManager.add(
+ stackedThreadToast({
+ type: "error",
+ title: "Unable to open pull request link",
+ description: error instanceof Error ? error.message : "An error occurred.",
+ }),
+ );
+ });
+ }, []);
+}