From 73d73894d462ea075da4b615bc69e70db2f0be6a Mon Sep 17 00:00:00 2001
From: Rafael Audibert <32079912+rafaeelaudibert@users.noreply.github.com>
Date: Thu, 28 May 2026 17:48:03 -0300
Subject: [PATCH 1/2] Add PR badge to sidebar task list
Tasks in the sidebar with a linked pull request now show a small clickable badge on the right side of the row. The badge displays the PR number (e.g. `#123`) when it can be extracted from the URL and falls back to `PR` otherwise; clicking it opens the PR in the user's browser via `openUrlInBrowser`. The URL is sourced from the existing `TaskData.cloudPrUrl` field (populated from `task.latest_run.output.pr_url`), so no new data plumbing is needed.
Generated-By: PostHog Code
Task-Id: ba55cf1b-c69e-4b32-b689-9a00d3831b64
---
.../sidebar/components/TaskListView.tsx | 1 +
.../sidebar/components/items/TaskItem.tsx | 50 ++++++++++++++++++-
2 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx
index 9882e8bbf2..7a885f3b6b 100644
--- a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx
+++ b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx
@@ -136,6 +136,7 @@ function TaskRow({
slackThreadUrl={task.slackThreadUrl}
prState={prState}
hasDiff={hasDiff}
+ prUrl={task.cloudPrUrl}
timestamp={timestamp}
onClick={onClick}
onDoubleClick={onDoubleClick}
diff --git a/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx b/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx
index a5ee2a5b49..3c5b2c66db 100644
--- a/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx
+++ b/apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx
@@ -1,13 +1,54 @@
import { Tooltip } from "@components/ui/Tooltip";
import type { SidebarPrState } from "@features/sidebar/hooks/useTaskPrStatus";
import type { WorkspaceMode } from "@main/services/workspace/schemas";
-import { Archive, PushPin } from "@phosphor-icons/react";
+import { Archive, GitPullRequest, PushPin } from "@phosphor-icons/react";
import type { TaskRunStatus } from "@shared/types";
+import { openUrlInBrowser } from "@utils/browser";
import { formatRelativeTimeShort } from "@utils/time";
import { useCallback, useEffect, useRef, useState } from "react";
import { SidebarItem } from "../SidebarItem";
import { TaskIcon } from "./TaskIcon";
+function extractPrNumber(url: string): string | null {
+ const match = url.match(
+ /\/(?:pull|pulls|merge_requests|pull-requests)\/(\d+)/,
+ );
+ return match ? match[1] : null;
+}
+
+function PrBadge({ url }: { url: string }) {
+ const number = extractPrNumber(url);
+ const open = () => {
+ void openUrlInBrowser(url);
+ };
+ return (
+
+ {/* biome-ignore lint/a11y/useSemanticElements: nested clickable inside SidebarItem button */}
+ {
+ e.stopPropagation();
+ open();
+ }}
+ onDoubleClick={(e) => e.stopPropagation()}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ e.stopPropagation();
+ open();
+ }
+ }}
+ >
+
+ {number ? `#${number}` : "PR"}
+
+
+ );
+}
+
interface TaskItemProps {
depth?: number;
taskId: string;
@@ -27,6 +68,7 @@ interface TaskItemProps {
slackThreadUrl?: string;
prState?: SidebarPrState;
hasDiff?: boolean;
+ prUrl?: string | null;
timestamp?: number;
isEditing?: boolean;
onClick: (e: React.MouseEvent) => void;
@@ -123,6 +165,7 @@ export function TaskItem({
slackThreadUrl,
prState,
hasDiff,
+ prUrl,
timestamp,
isEditing = false,
onClick,
@@ -149,6 +192,8 @@ export function TaskItem({
/>
);
+ const prBadge = prUrl ? : null;
+
const timestampNode = timestamp ? (
{formatRelativeTimeShort(timestamp)}
@@ -165,8 +210,9 @@ export function TaskItem({
) : null;
const endContent =
- timestampNode || toolbar ? (
+ prBadge || timestampNode || toolbar ? (
<>
+ {prBadge}
{timestampNode}
{toolbar}
>
From 74e293d87921fe0ca6851d391ce8b9393dac62e0 Mon Sep 17 00:00:00 2001
From: Charles Vien
Date: Thu, 4 Jun 2026 17:01:28 -0700
Subject: [PATCH 2/2] Extract NestedButton and reuse shared PR parser
---
.../components/ui/NestedButton.test.tsx | 34 ++++++
.../renderer/components/ui/NestedButton.tsx | 50 +++++++++
.../sidebar/components/items/TaskIcon.tsx | 38 ++-----
.../sidebar/components/items/TaskItem.tsx | 103 ++++++------------
4 files changed, 125 insertions(+), 100 deletions(-)
create mode 100644 apps/code/src/renderer/components/ui/NestedButton.test.tsx
create mode 100644 apps/code/src/renderer/components/ui/NestedButton.tsx
diff --git a/apps/code/src/renderer/components/ui/NestedButton.test.tsx b/apps/code/src/renderer/components/ui/NestedButton.test.tsx
new file mode 100644
index 0000000000..d5cf6bc20c
--- /dev/null
+++ b/apps/code/src/renderer/components/ui/NestedButton.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { NestedButton } from "./NestedButton";
+
+describe("NestedButton", () => {
+ it("renders an accessible button", () => {
+ render( {}}>x);
+ expect(screen.getByRole("button")).toBeTruthy();
+ });
+
+ it("calls onActivate on click without bubbling to the parent", async () => {
+ const onActivate = vi.fn();
+ const onParentClick = vi.fn();
+ render(
+ // biome-ignore lint/a11y/useKeyWithClickEvents: test-only wrapper
+ // biome-ignore lint/a11y/noStaticElementInteractions: test-only wrapper
+
+ x
+
,
+ );
+ await userEvent.click(screen.getByRole("button"));
+ expect(onActivate).toHaveBeenCalledTimes(1);
+ expect(onParentClick).not.toHaveBeenCalled();
+ });
+
+ it.each(["{Enter}", " "])("activates with the %s key", async (key) => {
+ const onActivate = vi.fn();
+ render(x);
+ screen.getByRole("button").focus();
+ await userEvent.keyboard(key);
+ expect(onActivate).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/apps/code/src/renderer/components/ui/NestedButton.tsx b/apps/code/src/renderer/components/ui/NestedButton.tsx
new file mode 100644
index 0000000000..8c43854840
--- /dev/null
+++ b/apps/code/src/renderer/components/ui/NestedButton.tsx
@@ -0,0 +1,50 @@
+import type React from "react";
+import { forwardRef } from "react";
+
+interface NestedButtonProps extends React.HTMLAttributes {
+ onActivate: () => void;
+}
+
+/**
+ * A button nested inside another button. Rows like SidebarItem render as a real
+ * `