diff --git a/apps/web/src/components/BranchToolbarBranchSelector.tsx b/apps/web/src/components/BranchToolbarBranchSelector.tsx
index 3bab1315ee8b..9ca02389b192 100644
--- a/apps/web/src/components/BranchToolbarBranchSelector.tsx
+++ b/apps/web/src/components/BranchToolbarBranchSelector.tsx
@@ -59,6 +59,7 @@ import {
useLinkedThreadPullRequest,
} from "./ThreadStatusIndicators";
import { Button } from "./ui/button";
+import { ComposerControl } from "./chat/ComposerControl";
import { Switch } from "./ui/switch";
import { getVirtualizedScrollFadeClassName } from "./ui/scroll-area";
import {
@@ -790,7 +791,7 @@ export function BranchToolbarBranchSelector({
data-composer-context-control
>
}
badge={prBadge}
number={prNumber}
url={prUrl}
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index 437ae73a0ce5..060b7c86a7a9 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -221,7 +221,7 @@ import {
} from "../providerInstances";
import { useThreadRunningTerminalIds } from "../state/terminalSessions";
import { stackedThreadToast, toastManager } from "./ui/toast";
-import { Button } from "./ui/button";
+import { Button, InlineButton } from "./ui/button";
import {
Combobox,
ComboboxEmpty,
@@ -1517,7 +1517,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
const prBadge =
prBadgeShape?.kind === "stack" || pr || currentLinkedPr ? (
}
badge={prBadgeShape}
number={pr?.number ?? currentLinkedPr?.number}
url={pr?.url ?? currentLinkedPr?.url}
diff --git a/apps/web/src/components/ThreadStatusIndicators.test.tsx b/apps/web/src/components/ThreadStatusIndicators.test.tsx
index 2ad3ef64448e..48066bc97e80 100644
--- a/apps/web/src/components/ThreadStatusIndicators.test.tsx
+++ b/apps/web/src/components/ThreadStatusIndicators.test.tsx
@@ -1,8 +1,16 @@
import { ThreadId, type ThreadPullRequestLink } from "@t3tools/contracts";
+import type { ReactElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vite-plus/test";
-import { ThreadWorktreeIndicator, linkedPullRequestSnapshotStatus } from "./ThreadStatusIndicators";
+import {
+ ThreadPullRequestBadgeControl,
+ ThreadWorktreeIndicator,
+ linkedPullRequestSnapshotStatus,
+ prStatusIndicator,
+} from "./ThreadStatusIndicators";
+import { ComposerControl } from "./chat/ComposerControl";
+import { InlineButton } from "./ui/button";
describe("ThreadWorktreeIndicator", () => {
it("renders the worktree folder and branch in an accessible label", () => {
@@ -80,3 +88,62 @@ describe("linked pull request snapshots", () => {
});
});
});
+
+describe("ThreadPullRequestBadgeControl", () => {
+ const merged = prStatusIndicator(
+ {
+ number: 370,
+ url: "https://github.com/example/orchard/pull/370",
+ title: "Change",
+ state: "merged",
+ isDraft: false,
+ headRef: "feature",
+ baseRef: "main",
+ updatedAt: "2026-01-02T00:00:00Z",
+ },
+ null,
+ );
+
+ function renderBadge(render: ReactElement) {
+ const html = renderToStaticMarkup(
+ {}}
+ onOpenPullRequest={() => {}}
+ />,
+ );
+ const link = html.match(/]*class="([^"]*)"/)?.[1]?.split(" ") ?? [];
+ return { html, link };
+ }
+
+ it("sets the meta size and normal weight on the control itself, over the InlineButton's", () => {
+ const { link } = renderBadge();
+
+ expect(link).toContain("text-xs");
+ expect(link).toContain("font-normal");
+ expect(link).not.toContain("font-medium");
+ });
+
+ it.each([
+ ["an InlineButton", () => ],
+ ["a ComposerControl", () => ],
+ ])(
+ "keeps the state tone on the icon and number inside %s, whatever its hover colour",
+ (_, control) => {
+ const tone = merged!.colorClass;
+ const { html, link } = renderBadge(control());
+ // The glyph and number sit in a wrapper that carries the tone and no hover or focus
+ // colour, so the control's own hover:text-* can never repaint them.
+ const wrapper = html.match(/