Skip to content
Closed
126 changes: 84 additions & 42 deletions packages/ui/src/features/sidebar/components/items/TaskIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,61 +184,84 @@ function CloudStatusIcon({
);
}

type PrStateMeta = { Icon: typeof GitMerge; color: string; label: string };
const PR_STATE_META: Record<Exclude<SidebarPrState, null>, 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",
};
const CLOUD_BADGE_META: OriginProductMeta = { Icon: CloudIcon, label: "Cloud" };

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 (
<Tooltip content="PR merged" side="right">
<span className="flex items-center justify-center">
<GitMerge size={size} weight="bold" color="var(--purple-11)" />
</span>
</Tooltip>
);
}
if (prState === "open") {
return (
<Tooltip content="PR open" side="right">
<span className="flex items-center justify-center">
<GitPullRequest size={size} weight="bold" color="var(--green-11)" />
</span>
</Tooltip>
);
}
if (prState === "draft") {
return (
<Tooltip content="Draft PR" side="right">
<span className="flex items-center justify-center">
<GitPullRequest size={size} weight="bold" color="var(--gray-9)" />
</span>
</Tooltip>
);
}
if (prState === "closed") {
return (
<Tooltip content="PR closed" side="right">
<span className="flex items-center justify-center">
<GitPullRequest size={size} weight="bold" color="var(--red-11)" />
</span>
</Tooltip>
);
}
if (hasDiff) {
const meta = prState ? PR_STATE_META[prState] : hasDiff ? DIFF_META : null;
if (!meta) return null;

if (!provenanceBadge) {
return (
<Tooltip content="Has changes" side="right">
<Tooltip content={meta.label} side="right">
<span className="flex items-center justify-center">
<GitBranch size={size} weight="bold" color="var(--amber-11)" />
<meta.Icon size={size} weight="bold" color={meta.color} />
</span>
</Tooltip>
);
}
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 mask =
"radial-gradient(ellipse 37% 37% at 76.5% 76.5%, transparent 97%, black 100%)";
const icon = (
<span className="relative flex items-center justify-center">
<meta.Icon
size={size}
weight="bold"
color={meta.color}
style={{ maskImage: mask, WebkitMaskImage: mask }}
/>
<provenanceBadge.Icon
weight="fill"
color="var(--gray-10)"
className="absolute"
style={{ width: "65%", height: "65%", right: "-9%", bottom: "-9%" }}
/>
Comment thread
gantoine marked this conversation as resolved.
</span>
);
return (
<Tooltip content={`${meta.label} · ${provenanceBadge.label}`} side="right">
{renderIconSpan({
icon,
link: threadUrl,
ariaLabel: threadUrl
? `Open ${provenanceBadge.label} thread`
: undefined,
})}
</Tooltip>
);
}

export interface TaskIconProps {
Expand Down Expand Up @@ -280,6 +303,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) {
Expand Down Expand Up @@ -308,7 +333,12 @@ export function TaskIcon({
</Tooltip>
);
}
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 (
<CloudStatusIcon
taskRunStatus={taskRunStatus}
Expand All @@ -328,7 +358,19 @@ export function TaskIcon({
);
}
if (prState || hasDiff) {
return <PrStatusIcon prState={prState} hasDiff={hasDiff} size={size} />;
return (
<PrStatusIcon
prState={prState}
hasDiff={hasDiff}
size={size}
provenanceBadge={
isCloudTask ? (originProductMeta ?? CLOUD_BADGE_META) : undefined
}
Comment on lines +366 to +368

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inline fallback object creates a new reference on every render

originProductMeta ?? { Icon: CloudIcon, label: "Cloud" } allocates a fresh object each render when originProductMeta is undefined. While PrStatusIcon isn't memoized today, extracting this to a module-level constant (e.g. const CLOUD_BADGE_META: OriginProductMeta = { Icon: CloudIcon, label: "Cloud" }) keeps the intent explicit and pairs naturally with the existing PR_STATE_META and DIFF_META pattern established in this PR.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

threadUrl={
isCloudTask && originProductMeta ? slackThreadUrl : undefined
}
/>
);
}
if (isPinned) {
return <PushPin size={size} color="var(--accent-11)" />;
Expand Down
Loading