authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-24 14:08:39+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-24 14:08:39+02:00
loga7ee3092c3c4383a65a16a51de7e0328d6e027c9
treed1c079654560c866ff9e5296a4089da326864b65
parentf7cc7764887a3c0616973043fa77e12671e3f9ef
signature Signed by PGP key B5690EEEBB952194

Color settled PR labels on hover (#4451)

Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 43 insertions(+), 4 deletions(-)

apps/web/src/components/SidebarV2.tsx+7-2
...@@ -117,7 +117,11 @@ import {...@@ -117,7 +117,11 @@ import {
117 sortThreadsForSidebarV2,117 sortThreadsForSidebarV2,
118} from "./Sidebar.logic";118} from "./Sidebar.logic";
119import { resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic";119import { resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic";
120import { prStatusIndicator, resolveThreadPr } from "./ThreadStatusIndicators";120import {
121 prStatusIndicator,
122 resolveThreadPr,
123 settledPrHoverColorClass,
124} from "./ThreadStatusIndicators";
121import {125import {
122 resolveSnoozePresets,126 resolveSnoozePresets,
123 snoozeWakeDescription,127 snoozeWakeDescription,
...@@ -498,6 +502,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -498,6 +502,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
498 hasDedicatedWorktree: thread.worktreePath !== null,502 hasDedicatedWorktree: thread.worktreePath !== null,
499 });503 });
500 const prStatus = prStatusIndicator(pr, gitStatus.data?.sourceControlProvider);504 const prStatus = prStatusIndicator(pr, gitStatus.data?.sourceControlProvider);
505 const settledPrHoverClass = pr ? settledPrHoverColorClass(pr.state) : undefined;
501 // Report the PR state up: the parent partitions rows with effectiveSettled,506 // Report the PR state up: the parent partitions rows with effectiveSettled,
502 // and a merged/closed PR auto-settles a thread — data only rows have.507 // and a merged/closed PR auto-settles a thread — data only rows have.
503 const prState = pr?.state ?? null;508 const prState = pr?.state ?? null;
...@@ -714,7 +719,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {...@@ -714,7 +719,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: {
714 variant === "slim" && variantAction === "unsettle"719 variant === "slim" && variantAction === "unsettle"
715 ? props.isActive720 ? props.isActive
716 ? "text-muted-foreground/70"721 ? "text-muted-foreground/70"
717 : "text-muted-foreground/35 transition-colors group-hover/v2-row:text-muted-foreground/65"722 : cn("text-muted-foreground/35 transition-colors", settledPrHoverClass)
718 : prStatus.colorClass,723 : prStatus.colorClass,
719 )}724 )}
720 aria-label={prStatus.tooltip}725 aria-label={prStatus.tooltip}
apps/web/src/components/ThreadStatusIndicators.test.ts+24-1
...@@ -1,7 +1,11 @@...@@ -1,7 +1,11 @@
1import type { VcsStatusResult } from "@t3tools/contracts";1import type { VcsStatusResult } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";2import { describe, expect, it } from "vite-plus/test";
33
4import { prStatusIndicator, resolveThreadPr } from "./ThreadStatusIndicators";4import {
5 prStatusIndicator,
6 resolveThreadPr,
7 settledPrHoverColorClass,
8} from "./ThreadStatusIndicators";
59
6function status(overrides: Partial<VcsStatusResult> = {}): VcsStatusResult {10function status(overrides: Partial<VcsStatusResult> = {}): VcsStatusResult {
7 return {11 return {
...@@ -70,4 +74,23 @@ describe("prStatusIndicator", () => {...@@ -70,4 +74,23 @@ describe("prStatusIndicator", () => {
70 tooltipTitle: "PR branch",74 tooltipTitle: "PR branch",
71 });75 });
72 });76 });
77
78 it("uses red for closed pull requests", () => {
79 const closedPr = status().pr;
80 if (!closedPr) throw new Error("Expected pull request fixture");
81
82 expect(prStatusIndicator({ ...closedPr, state: "closed" }, undefined)?.colorClass).toContain(
83 "text-red-600",
84 );
85 });
86});
87
88describe("settledPrHoverColorClass", () => {
89 it.each([
90 ["open", "text-emerald-600"],
91 ["merged", "text-violet-600"],
92 ["closed", "text-red-600"],
93 ] as const)("restores the %s pull request color on row hover", (state, colorClass) => {
94 expect(settledPrHoverColorClass(state)).toContain(`group-hover/v2-row:${colorClass}`);
95 });
73});96});
apps/web/src/components/ThreadStatusIndicators.tsx+12-1
...@@ -35,6 +35,17 @@ export interface TerminalStatusIndicator {...@@ -35,6 +35,17 @@ export interface TerminalStatusIndicator {
3535
36export type ThreadPr = VcsStatusResult["pr"];36export type ThreadPr = VcsStatusResult["pr"];
3737
38export function settledPrHoverColorClass(state: NonNullable<ThreadPr>["state"]): string {
39 switch (state) {
40 case "open":
41 return "group-hover/v2-row:text-emerald-600 dark:group-hover/v2-row:text-emerald-300/90";
42 case "merged":
43 return "group-hover/v2-row:text-violet-600 dark:group-hover/v2-row:text-violet-300/90";
44 case "closed":
45 return "group-hover/v2-row:text-red-600 dark:group-hover/v2-row:text-red-300/90";
46 }
47}
48
38export function prStatusIndicator(49export function prStatusIndicator(
39 pr: ThreadPr,50 pr: ThreadPr,
40 provider: VcsStatusResult["sourceControlProvider"] | null | undefined,51 provider: VcsStatusResult["sourceControlProvider"] | null | undefined,
...@@ -65,7 +76,7 @@ export function prStatusIndicator(...@@ -65,7 +76,7 @@ export function prStatusIndicator(
65 if (pr.state === "closed") {76 if (pr.state === "closed") {
66 return {77 return {
67 label: `${presentation.shortName} closed`,78 label: `${presentation.shortName} closed`,
68 colorClass: "text-zinc-500 dark:text-zinc-400/80",79 colorClass: "text-red-600 dark:text-red-300/90",
69 tooltip,80 tooltip,
70 tooltipLead,81 tooltipLead,
71 tooltipTitle: pr.title,82 tooltipTitle: pr.title,