Skip to content

Commit 5e22a7b

Browse files
fix(agent-manager): color PR badge by state, show checks as icon
Badge color now reflects only the PR lifecycle state (open/draft/merged/closed), so a failing check is no longer mistaken for a closed PR. CI and review results are shown as a separate icon (failing checks, changes requested, approved), and an open PR whose checks are still running keeps its pulsing amber badge.
1 parent 833531f commit 5e22a7b

4 files changed

Lines changed: 145 additions & 12 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": patch
3+
---
4+
5+
Color Agent Manager PR badges by pull request state (open, draft, merged, closed) and show CI and review status as a separate icon, so a failing check is no longer mistaken for a closed PR.

packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Sidebar worktree item with inline delete confirmation, HoverCard, rename, and stats.
33
* Extracted from AgentManagerApp for reuse and visual-regression testing via Storybook.
44
*/
5-
import { Component, For, Show, createSignal } from "solid-js"
5+
import { Component, For, Match, Show, Switch, createSignal } from "solid-js"
66
import { Icon } from "@kilocode/kilo-ui/icon"
77
import { IconButton } from "@kilocode/kilo-ui/icon-button"
88
import { Spinner } from "@kilocode/kilo-ui/spinner"
@@ -84,17 +84,43 @@ const MAX_SHORTCUT = 9
8484
const hasStats = (s: WorktreeGitStats | undefined): s is WorktreeGitStats =>
8585
!!s && (s.files > 0 || s.additions > 0 || s.deletions > 0 || s.ahead > 0 || s.behind > 0)
8686

87-
/** Returns the accent color for a PR badge based on state priority. */
87+
/**
88+
* Accent color for a PR badge, derived from the PR's lifecycle state
89+
* (open/draft/merged/closed). The one exception is an open PR with checks still
90+
* running, which uses amber and pulses its background (see prChecksRunning) — a
91+
* transient, unambiguous signal since no lifecycle state uses amber. Terminal CI
92+
* and review results are conveyed by a separate status icon (see prBadgeIndicator)
93+
* so a failing check is not mistaken for a closed PR.
94+
*/
8895
export function prAccentColor(pr: PRStatus): string {
8996
if (pr.state === "draft") return "var(--text-weaker)"
9097
if (pr.state === "merged") return "#a78bfa"
9198
if (pr.state === "closed") return "#f87171"
92-
if (pr.checks.status === "failure") return "#ef4444"
93-
if (pr.review === "changes_requested") return "#fbbf24"
9499
if (pr.checks.status === "pending") return "#fbbf24"
95100
return "#34d399"
96101
}
97102

103+
/** True while an open PR's checks are still running — drives the pulsing amber badge. */
104+
export function prChecksRunning(pr: PRStatus): boolean {
105+
return pr.state === "open" && pr.checks.status === "pending"
106+
}
107+
108+
export type PRBadgeIndicator = "failure" | "changes" | "approved" | "none"
109+
110+
/**
111+
* Terminal CI/review status shown as an icon overlaid on the PR badge, independent
112+
* of the badge's state-based accent color. Running checks are not represented here —
113+
* they are shown by the pulsing amber background instead. Terminal PRs (merged/closed)
114+
* show no indicator since their checks are no longer actionable.
115+
*/
116+
export function prBadgeIndicator(pr: PRStatus): PRBadgeIndicator {
117+
if (pr.state === "merged" || pr.state === "closed") return "none"
118+
if (pr.checks.status === "failure") return "failure"
119+
if (pr.review === "changes_requested") return "changes"
120+
if (pr.review === "approved") return "approved"
121+
return "none"
122+
}
123+
98124
function prStateLabel(state: PRStatus["state"]): string {
99125
if (state === "draft") return "Draft"
100126
if (state === "merged") return "Merged"
@@ -315,14 +341,40 @@ export const WorktreeItem: Component<WorktreeItemProps> = (props) => {
315341
>
316342
{(pr) => {
317343
const accent = () => prAccentColor(pr())
344+
const indicator = () => prBadgeIndicator(pr())
318345
return (
319346
<span
320347
class="am-pr-badge"
321348
style={{ "--pr-accent": accent() }}
322-
data-pending={pr().state === "open" && pr().checks.status === "pending" ? "" : undefined}
349+
data-pending={prChecksRunning(pr()) ? "" : undefined}
323350
onClick={handleOpenPR}
324351
>
325-
<Icon name={pr().review === "approved" ? "check-small" : "branch"} size="small" />
352+
<Switch fallback={<Icon name="branch" size="small" />}>
353+
<Match when={indicator() === "failure"}>
354+
<Icon
355+
name="circle-x"
356+
size="small"
357+
class="am-pr-badge-status"
358+
style={{ color: "#ef4444" }}
359+
/>
360+
</Match>
361+
<Match when={indicator() === "changes"}>
362+
<Icon
363+
name="warning"
364+
size="small"
365+
class="am-pr-badge-status"
366+
style={{ color: "#fbbf24" }}
367+
/>
368+
</Match>
369+
<Match when={indicator() === "approved"}>
370+
<Icon
371+
name="circle-check"
372+
size="small"
373+
class="am-pr-badge-status"
374+
style={{ color: "#34d399" }}
375+
/>
376+
</Match>
377+
</Switch>
326378
<span class="am-pr-badge-number">#{pr().number}</span>
327379
</span>
328380
)

packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ button.am-section-toggle:hover .am-section-label {
710710
.am-pr-badge:hover .am-pr-badge-number {
711711
color: var(--pr-accent);
712712
}
713+
/* Checks still running — pulse the whole amber badge. */
713714
.am-pr-badge[data-pending] {
714715
animation: am-pr-pulse 1.5s ease-in-out infinite;
715716
}

packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,10 +573,11 @@ export const WithPRBadges: Story = {
573573
name: "Section — worktrees with PR badges",
574574
render: () => (
575575
<StoryProviders noPadding>
576-
<div style={{ "max-height": "400px", overflow: "auto" }}>
576+
<div style={{ "max-height": "560px", overflow: "auto" }}>
577577
<DndWrap>
578-
<SectionHeader section={sec("s1", 0, { name: "In Review", color: "Blue" })} count={3} {...sectionProps}>
578+
<SectionHeader section={sec("s1", 0, { name: "In Review", color: "Blue" })} count={8} {...sectionProps}>
579579
<div class="am-section-group-body">
580+
{/* Open + passing + approved → green badge, green check */}
580581
<WorktreeItem
581582
{...wtProps}
582583
worktree={wt("wt-1", "feat/api-v2")}
@@ -595,6 +596,7 @@ export const WithPRBadges: Story = {
595596
checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] },
596597
}}
597598
/>
599+
{/* Open + failing checks → green badge, red ✗ (no longer confusable with closed) */}
598600
<WorktreeItem
599601
{...wtProps}
600602
worktree={wt("wt-2", "fix/race-cond")}
@@ -605,28 +607,101 @@ export const WithPRBadges: Story = {
605607
title: "fix: race condition",
606608
url: "#",
607609
state: "open",
608-
review: "changes_requested",
610+
review: null,
609611
additions: 15,
610612
deletions: 8,
611613
files: 3,
612614
checks: { status: "failure", total: 5, passed: 3, failed: 2, pending: 0, items: [] },
613615
}}
614616
/>
617+
{/* Open + changes requested → green badge, amber warning */}
615618
<WorktreeItem
616619
{...wtProps}
617-
worktree={wt("wt-3", "feat/cache")}
620+
worktree={wt("wt-3", "feat/search")}
621+
label="feat/search"
622+
subtitle="feat/search"
623+
pr={{
624+
number: 91,
625+
title: "feat: search",
626+
url: "#",
627+
state: "open",
628+
review: "changes_requested",
629+
additions: 60,
630+
deletions: 12,
631+
files: 4,
632+
checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] },
633+
}}
634+
/>
635+
{/* Open + checks running → pulsing amber badge (animation disabled in snapshots) */}
636+
<WorktreeItem
637+
{...wtProps}
638+
worktree={wt("wt-4", "feat/cache")}
618639
label="feat/cache"
619640
subtitle="feat/cache"
620641
pr={{
621642
number: 103,
622643
title: "feat: cache layer",
623644
url: "#",
624-
state: "draft",
645+
state: "open",
625646
review: null,
626647
additions: 200,
627648
deletions: 0,
628649
files: 8,
629-
checks: { status: "pending", total: 5, passed: 0, failed: 0, pending: 5, items: [] },
650+
checks: { status: "pending", total: 5, passed: 2, failed: 0, pending: 3, items: [] },
651+
}}
652+
/>
653+
{/* Draft → gray badge */}
654+
<WorktreeItem
655+
{...wtProps}
656+
worktree={wt("wt-5", "wip/refactor")}
657+
label="wip/refactor"
658+
subtitle="wip/refactor"
659+
pr={{
660+
number: 110,
661+
title: "wip: refactor",
662+
url: "#",
663+
state: "draft",
664+
review: null,
665+
additions: 30,
666+
deletions: 5,
667+
files: 2,
668+
checks: { status: "none", total: 0, passed: 0, failed: 0, pending: 0, items: [] },
669+
}}
670+
/>
671+
{/* Merged → purple badge, no status icon */}
672+
<WorktreeItem
673+
{...wtProps}
674+
worktree={wt("wt-6", "feat/done")}
675+
label="feat/done"
676+
subtitle="feat/done"
677+
pr={{
678+
number: 70,
679+
title: "feat: done",
680+
url: "#",
681+
state: "merged",
682+
review: "approved",
683+
additions: 90,
684+
deletions: 20,
685+
files: 6,
686+
checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] },
687+
}}
688+
/>
689+
{/* Closed → red badge, no status icon (distinct from a failing open PR) */}
690+
<WorktreeItem
691+
{...wtProps}
692+
worktree={wt("wt-7", "spike/idea")}
693+
label="spike/idea"
694+
subtitle="spike/idea"
695+
pr={{
696+
number: 65,
697+
title: "spike: idea",
698+
url: "#",
699+
state: "closed",
700+
review: null,
701+
additions: 10,
702+
deletions: 4,
703+
files: 1,
704+
checks: { status: "failure", total: 5, passed: 1, failed: 4, pending: 0, items: [] },
630705
}}
631706
/>
632707
</div>

0 commit comments

Comments
 (0)