From bfc1d3ca8fd7844fb9419933b93d213216fbae66 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 7 Jul 2026 15:01:54 -0400 Subject: [PATCH 1/9] feat(sidebar): show PR state for cloud runs with a provenance badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloud runs never surfaced their branch lifecycle (draft/open/merged/closed PR) in the sidebar: the terminal-cloud branch of TaskIcon's waterfall won before the PR-state branch, so a completed cloud run always showed the green cloud icon even when its PR state was already resolved via the run's pr_url. Now a cloud run that finished cleanly and has a known PR state renders the same PR-state glyphs as local runs (merged purple, open green, draft gray, closed red), with a small filled cloud — or the origin-product icon (Slack, Signals, …) — stacked on the glyph's bottom-right corner so cloud provenance stays visible at a glance. A radial-gradient mask punches a hole in the PR glyph under the badge, keeping both shapes legible on any row background without hardcoding a cutout ring color. Origin-branded tasks keep their clickable thread link on the stacked icon. Failed/cancelled cloud runs keep the red cloud icon: the failure remains the actionable signal there. Queued/running runs and completed runs without a PR are unchanged. Applies to every TaskIcon consumer (sidebar, tabs, command palette, command center). Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../sidebar/components/items/TaskIcon.tsx | 134 ++++++++++++------ 1 file changed, 92 insertions(+), 42 deletions(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index fbe1adcfb5..5d586864ab 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -184,61 +184,90 @@ function CloudStatusIcon({ ); } +type PrStateMeta = { Icon: typeof GitMerge; color: string; label: string }; +const PR_STATE_META: Record, PrStateMeta> = { + merged: { Icon: GitMerge, color: "var(--purple-11)", label: "PR merged" }, + open: { Icon: GitPullRequest, color: "var(--green-11)", label: "PR open" }, + draft: { Icon: GitPullRequest, color: "var(--gray-9)", label: "Draft PR" }, + closed: { Icon: GitPullRequest, color: "var(--red-11)", label: "PR closed" }, +}; +const DIFF_META: PrStateMeta = { + Icon: GitBranch, + color: "var(--amber-11)", + label: "Has changes", +}; + function PrStatusIcon({ prState, hasDiff, size, + provenanceBadge, + threadUrl, }: { prState?: SidebarPrState; hasDiff?: boolean; size: number; + /** When set (cloud tasks), a small provenance glyph (cloud or origin + * product) is stacked on the icon's bottom-right corner so "where this ran" + * stays visible alongside the PR state. */ + provenanceBadge?: OriginProductMeta; + /** Originating thread URL; keeps the badge state clickable like + * `CloudStatusIcon` does for origin-branded tasks. */ + threadUrl?: string; }) { - if (prState === "merged") { - return ( - - - - - - ); - } - if (prState === "open") { - return ( - - - - - - ); - } - if (prState === "draft") { - return ( - - - - - - ); - } - if (prState === "closed") { - return ( - - - - - - ); - } - if (hasDiff) { + const meta = prState ? PR_STATE_META[prState] : hasDiff ? DIFF_META : null; + if (!meta) return null; + + if (!provenanceBadge) { return ( - + - + ); } - return null; + + // Stack the provenance glyph over the PR icon's bottom-right corner. A + // radial-gradient mask punches a hole in the PR glyph under the badge so + // both shapes stay legible on any row background (hover, selected, command + // palette highlight) without hardcoding a cutout ring color. + const badgeSize = Math.round(size * 0.66); + const overflow = 2; + const holeCenter = size + overflow - badgeSize / 2; + const holeRadius = badgeSize / 2 + 1; + const mask = `radial-gradient(circle at ${holeCenter}px ${holeCenter}px, transparent ${holeRadius}px, black ${holeRadius + 0.5}px)`; + const icon = ( + + + + + ); + return ( + + {renderIconSpan({ + icon, + link: threadUrl, + ariaLabel: threadUrl + ? `Open ${provenanceBadge.label} thread` + : undefined, + })} + + ); } export interface TaskIconProps { @@ -280,6 +309,8 @@ export function TaskIcon({ }: TaskIconProps) { const isCloudTask = workspaceMode === "cloud"; const isTerminalCloud = isCloudTask && isTerminalStatus(taskRunStatus); + const cloudRunFailed = + taskRunStatus === "failed" || taskRunStatus === "cancelled"; const originProductMeta = getOriginProductMeta(originProduct); if (needsPermission) { @@ -308,7 +339,12 @@ export function TaskIcon({ ); } - if (isTerminalCloud) { + // A failed/cancelled cloud run keeps the red cloud icon — the failure is + // the actionable signal there. A cloud run that finished cleanly and has a + // PR falls through to the PR-state icon (with a provenance badge), because + // once the run is done the branch's lifecycle is the state worth scanning + // for — the same one local runs show. + if (isTerminalCloud && (cloudRunFailed || !prState)) { return ( ; + return ( + + ); } if (isPinned) { return ; From 25c86b472bcf827d168c19a3d9d3a52ec2afeba1 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 8 Jul 2026 15:10:07 -0400 Subject: [PATCH 2/9] fix(sidebar): shrink provenance badge to half the icon size Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- packages/ui/src/features/sidebar/components/items/TaskIcon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index 5d586864ab..136fc833ca 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -232,7 +232,7 @@ function PrStatusIcon({ // radial-gradient mask punches a hole in the PR glyph under the badge so // both shapes stay legible on any row background (hover, selected, command // palette highlight) without hardcoding a cutout ring color. - const badgeSize = Math.round(size * 0.66); + const badgeSize = Math.round(size * 0.5); const overflow = 2; const holeCenter = size + overflow - badgeSize / 2; const holeRadius = badgeSize / 2 + 1; From 5db3e393439025e83ccf7295f13f506dd99b4742 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 8 Jul 2026 15:34:15 -0400 Subject: [PATCH 3/9] fix(sidebar): pin provenance badge svg size against quill button override quill's `.quill-button svg:not([class*=size-])` rule forces every descendant svg inside a sidebar row (a quill Button) to the button's icon size, overriding phosphor's width/height attributes. The provenance badge therefore rendered at 14px instead of 6px, covering the PR glyph entirely and reading as a full-size cloud. Inline width/height win over the rule and keep the badge at half the icon size. Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../src/features/sidebar/components/items/TaskIcon.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index 136fc833ca..508b0e8073 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -253,7 +253,15 @@ function PrStatusIcon({ weight="fill" color="var(--gray-10)" className="absolute" - style={{ right: -overflow, bottom: -overflow }} + style={{ + // quill's `.quill-button svg:not([class*=size-])` rule forces + // descendant svgs to the button's icon size, overriding phosphor's + // width/height attributes — inline dimensions keep the badge small. + width: badgeSize, + height: badgeSize, + right: -overflow, + bottom: -overflow, + }} /> ); From 201ca1d29e94c16e1303ccdd8e6d16bf82d039be Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 8 Jul 2026 15:42:28 -0400 Subject: [PATCH 4/9] fix(sidebar): tighten the mask cutout around the provenance badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The primary PR glyph was still being inflated to the quill button icon size while the mask geometry assumed the passed size, so the punched hole landed offset and oversized — eating the glyph's bottom-right corner and leaving a wide empty ring around the badge. Pin the glyph's dimensions inline like the badge's, shrink the badge bleed to 1px, and cut the hole at badge radius + 0.5px so the ring reads as a snug outline instead of padding. Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../sidebar/components/items/TaskIcon.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index 508b0e8073..e3d64dfcd5 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -232,10 +232,15 @@ function PrStatusIcon({ // radial-gradient mask punches a hole in the PR glyph under the badge so // both shapes stay legible on any row background (hover, selected, command // palette highlight) without hardcoding a cutout ring color. + // + // Both svgs get inline width/height: quill's + // `.quill-button svg:not([class*=size-])` rule otherwise forces descendant + // svgs to the button's icon size, which would inflate the badge and shift + // the glyph out of the mask's coordinate system. const badgeSize = Math.round(size * 0.5); - const overflow = 2; + const overflow = 1; const holeCenter = size + overflow - badgeSize / 2; - const holeRadius = badgeSize / 2 + 1; + const holeRadius = badgeSize / 2 + 0.5; const mask = `radial-gradient(circle at ${holeCenter}px ${holeCenter}px, transparent ${holeRadius}px, black ${holeRadius + 0.5}px)`; const icon = ( Date: Wed, 8 Jul 2026 15:47:14 -0400 Subject: [PATCH 5/9] fix(sidebar): size the badged PR icon like local-run PR icons The stacked composite pinned its glyph to the `size` prop (12px) while plain PR icons on local-run rows get inflated to the quill button icon size (14px), so badged cloud rows looked smaller than their local siblings. Drop the inline pinning on the glyph and express the badge and mask-hole geometry as percentages of the rendered box instead: the glyph now always matches plain PR icons in the same context, and the badge scales with it (7px in the sidebar). Percentage inline dimensions still exempt the badge from quill's svg sizing rule. Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../sidebar/components/items/TaskIcon.tsx | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index e3d64dfcd5..a9bdb9ec27 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -233,42 +233,30 @@ function PrStatusIcon({ // both shapes stay legible on any row background (hover, selected, command // palette highlight) without hardcoding a cutout ring color. // - // Both svgs get inline width/height: quill's - // `.quill-button svg:not([class*=size-])` rule otherwise forces descendant - // svgs to the button's icon size, which would inflate the badge and shift - // the glyph out of the mask's coordinate system. - const badgeSize = Math.round(size * 0.5); - const overflow = 1; - const holeCenter = size + overflow - badgeSize / 2; - const holeRadius = badgeSize / 2 + 0.5; - const mask = `radial-gradient(circle at ${holeCenter}px ${holeCenter}px, transparent ${holeRadius}px, black ${holeRadius + 0.5}px)`; + // All geometry is relative to the glyph's rendered box, not the `size` + // prop: quill's `.quill-button svg:not([class*=size-])` rule resizes the + // glyph to the button's icon size (14px in the sidebar), and the badge and + // mask must track whatever actually renders so the composite stays the + // same size as the plain PR icons on local-run rows. The badge's inline + // percentage dimensions also exempt it from that quill rule. Badge: 50% of + // the glyph, bleeding 7% past the corner, centered at 82%; the mask hole's + // 30% radius leaves a thin ring around it. + const mask = + "radial-gradient(ellipse 30% 30% at 82% 82%, transparent 97%, black 100%)"; const icon = ( - + ); From 891ff3c966de3f6d0c1a281e9c1a897cddc7ab3d Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 8 Jul 2026 15:55:51 -0400 Subject: [PATCH 6/9] fix(sidebar): grow the provenance badge to 65% of the glyph Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../features/sidebar/components/items/TaskIcon.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index a9bdb9ec27..f9dad6c5f3 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -238,11 +238,11 @@ function PrStatusIcon({ // glyph to the button's icon size (14px in the sidebar), and the badge and // mask must track whatever actually renders so the composite stays the // same size as the plain PR icons on local-run rows. The badge's inline - // percentage dimensions also exempt it from that quill rule. Badge: 50% of - // the glyph, bleeding 7% past the corner, centered at 82%; the mask hole's - // 30% radius leaves a thin ring around it. + // percentage dimensions also exempt it from that quill rule. Badge: 65% of + // the glyph, bleeding 9% past the corner, centered at 76.5%; the mask + // hole's 37% radius leaves a thin ring around it. const mask = - "radial-gradient(ellipse 30% 30% at 82% 82%, transparent 97%, black 100%)"; + "radial-gradient(ellipse 37% 37% at 76.5% 76.5%, transparent 97%, black 100%)"; const icon = ( ); From 61bf4e66a4128d66644707443da48f0434a6a370 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 8 Jul 2026 16:00:51 -0400 Subject: [PATCH 7/9] chore(sidebar): drop geometry explainer comment from PrStatusIcon Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../src/features/sidebar/components/items/TaskIcon.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index f9dad6c5f3..bd53ba7b8f 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -232,15 +232,6 @@ function PrStatusIcon({ // radial-gradient mask punches a hole in the PR glyph under the badge so // both shapes stay legible on any row background (hover, selected, command // palette highlight) without hardcoding a cutout ring color. - // - // All geometry is relative to the glyph's rendered box, not the `size` - // prop: quill's `.quill-button svg:not([class*=size-])` rule resizes the - // glyph to the button's icon size (14px in the sidebar), and the badge and - // mask must track whatever actually renders so the composite stays the - // same size as the plain PR icons on local-run rows. The badge's inline - // percentage dimensions also exempt it from that quill rule. Badge: 65% of - // the glyph, bleeding 9% past the corner, centered at 76.5%; the mask - // hole's 37% radius leaves a thin ring around it. const mask = "radial-gradient(ellipse 37% 37% at 76.5% 76.5%, transparent 97%, black 100%)"; const icon = ( From 04e39d6689dab14693da35fcd2fd7afc22a752ca Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Wed, 8 Jul 2026 16:16:05 -0400 Subject: [PATCH 8/9] fix(sidebar): drop dead size computation on the provenance badge The inline percentage width/height always override the svg attributes phosphor derives from `size`, so the `Math.round(size * 0.65)` value was never used. Omit the prop and let the inline style be the single source of truth. Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- packages/ui/src/features/sidebar/components/items/TaskIcon.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index bd53ba7b8f..f581606080 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -243,7 +243,6 @@ function PrStatusIcon({ style={{ maskImage: mask, WebkitMaskImage: mask }} /> Date: Wed, 8 Jul 2026 16:17:25 -0400 Subject: [PATCH 9/9] refactor(sidebar): hoist the cloud badge meta to a module constant The inline `{ Icon: CloudIcon, label: "Cloud" }` fallback allocated a new object on every TaskIcon render; a module-level CLOUD_BADGE_META matches the PR_STATE_META / DIFF_META pattern. Generated-By: PostHog Code Task-Id: 3c36b344-ed49-424a-9218-703049ad89db --- .../ui/src/features/sidebar/components/items/TaskIcon.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx index f581606080..ae78f4ee8a 100644 --- a/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx +++ b/packages/ui/src/features/sidebar/components/items/TaskIcon.tsx @@ -196,6 +196,7 @@ const DIFF_META: PrStateMeta = { color: "var(--amber-11)", label: "Has changes", }; +const CLOUD_BADGE_META: OriginProductMeta = { Icon: CloudIcon, label: "Cloud" }; function PrStatusIcon({ prState, @@ -363,9 +364,7 @@ export function TaskIcon({ hasDiff={hasDiff} size={size} provenanceBadge={ - isCloudTask - ? (originProductMeta ?? { Icon: CloudIcon, label: "Cloud" }) - : undefined + isCloudTask ? (originProductMeta ?? CLOUD_BADGE_META) : undefined } threadUrl={ isCloudTask && originProductMeta ? slackThreadUrl : undefined