From cd4ea2c08e5aaef5f79df55227ab2f18c8b539b8 Mon Sep 17 00:00:00 2001 From: TheIcarusWings <10465470+TheIcarusWings@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:42:21 +0100 Subject: [PATCH] feat(composer): show clickable PR pill next to branch selector Render a small PR pill (icon + #number) to the left of the branch selector in the bottom composer bar when the active branch has a pull request. The pill is colored by PR state (open=emerald, merged=violet, closed=zinc) via the shared prStatusIndicator, and clicking it opens the PR in the system browser without opening the branch dropdown. The tooltip is action-oriented ("Open pull request #N (state) in browser") and uses the provider's terminology, distinct from the sidebar's state-description tooltip. Co-Authored-By: Claude Opus 4.8 --- .../BranchToolbarBranchSelector.tsx | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/BranchToolbarBranchSelector.tsx b/apps/web/src/components/BranchToolbarBranchSelector.tsx index 72391f714fc..cd1114f6753 100644 --- a/apps/web/src/components/BranchToolbarBranchSelector.tsx +++ b/apps/web/src/components/BranchToolbarBranchSelector.tsx @@ -3,6 +3,7 @@ import type { EnvironmentId, VcsRef, ThreadId } from "@t3tools/contracts"; import { LegendList, type LegendListRef } from "@legendapp/list/react"; import { ChevronDownIcon, GitBranchIcon, SearchIcon } from "lucide-react"; import { + type MouseEvent, useCallback, useDeferredValue, useEffect, @@ -16,6 +17,7 @@ import { import { useComposerDraftStore, type DraftId } from "../composerDraftStore"; import { readEnvironmentApi } from "../environmentApi"; +import { readLocalApi } from "../localApi"; import { useVcsStatus } from "../lib/vcsStatusState"; import { useVcsRefs, vcsRefManager } from "../lib/vcsRefState"; import { newCommandId } from "../lib/utils"; @@ -32,6 +34,11 @@ import { resolveEffectiveEnvMode, shouldIncludeBranchPickerItem, } from "./BranchToolbar.logic"; +import { + ChangeRequestStatusIcon, + prStatusIndicator, + resolveThreadPr, +} from "./ThreadStatusIndicators"; import { Button } from "./ui/button"; import { Combobox, @@ -44,6 +51,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 +522,41 @@ 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 = 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.", + }), + ); + }); + }, []); + function renderPickerItem(itemValue: string, index: number) { if (checkoutPullRequestItemValue && itemValue === checkoutPullRequestItemValue) { return ( @@ -610,15 +653,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} + + +