Skip to content

Commit ee59f86

Browse files
committed
Fix comment outdenting for reviews
1 parent 3c17416 commit ee59f86

File tree

4 files changed

+587
-108
lines changed

4 files changed

+587
-108
lines changed

src/browser/components/home.tsx

Lines changed: 198 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
XCircle,
2121
AlertCircle,
2222
MessageSquare,
23+
Clock,
2324
} from "lucide-react";
25+
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
2426
import { cn } from "../cn";
2527
import { Skeleton } from "../ui/skeleton";
2628
import { UserHoverCard } from "../ui/user-hover-card";
@@ -875,10 +877,8 @@ export function Home() {
875877
)}
876878
</span>
877879
<div className="flex items-center gap-2">
878-
{prList.lastFetchedAt && (
879-
<span className="text-[10px] text-muted-foreground">
880-
Updated {getTimeAgo(new Date(prList.lastFetchedAt))}
881-
</span>
880+
{prList.lastFetchedAt && !loadingPrs && (
881+
<RefreshCountdown lastFetchedAt={prList.lastFetchedAt} />
882882
)}
883883
<button
884884
onClick={refreshPRList}
@@ -887,7 +887,7 @@ export function Home() {
887887
"p-1 rounded hover:bg-muted transition-colors text-muted-foreground hover:text-foreground",
888888
loadingPrs && "opacity-50"
889889
)}
890-
title="Refresh (auto-refreshes every 60s)"
890+
title="Refresh"
891891
>
892892
<RefreshCw
893893
className={cn("w-3.5 h-3.5", loadingPrs && "animate-spin")}
@@ -1071,75 +1071,178 @@ function PRListItem({ pr, onSelect }: PRListItemProps) {
10711071
? "Approval needed"
10721072
: "Running");
10731073

1074+
// Group checks by state for tooltip display
1075+
const checks = pr.ciChecks || [];
1076+
const successChecks = checks.filter((c) => c.state === "success");
1077+
const failureChecks = checks.filter((c) => c.state === "failure");
1078+
const pendingChecks = checks.filter(
1079+
(c) => c.state !== "success" && c.state !== "failure"
1080+
);
1081+
1082+
const TooltipChecks = () => (
1083+
<div className="min-w-[200px] max-w-[300px]">
1084+
<div className="font-medium text-xs mb-2 pb-1.5 border-b border-border flex items-center gap-2">
1085+
{pr.ciStatus === "success" && (
1086+
<>
1087+
<CheckCircle2 className="w-3.5 h-3.5 text-green-500" />
1088+
<span>All checks passed</span>
1089+
</>
1090+
)}
1091+
{pr.ciStatus === "failure" && (
1092+
<>
1093+
<XCircle className="w-3.5 h-3.5 text-red-500" />
1094+
<span>Some checks failed</span>
1095+
</>
1096+
)}
1097+
{pr.ciStatus === "pending" && (
1098+
<>
1099+
<Clock className="w-3.5 h-3.5 text-yellow-500" />
1100+
<span>Checks in progress</span>
1101+
</>
1102+
)}
1103+
{pr.ciStatus === "action_required" && (
1104+
<>
1105+
<AlertCircle className="w-3.5 h-3.5 text-yellow-500" />
1106+
<span>Action required</span>
1107+
</>
1108+
)}
1109+
</div>
1110+
{checks.length > 0 ? (
1111+
<div className="space-y-2">
1112+
{/* Failed checks first */}
1113+
{failureChecks.length > 0 && (
1114+
<div className="space-y-1">
1115+
{failureChecks.map((c) => (
1116+
<div
1117+
key={c.name}
1118+
className="flex items-center gap-2 text-[11px]"
1119+
>
1120+
<XCircle className="w-3 h-3 text-red-500 shrink-0" />
1121+
<span className="truncate text-red-400">{c.name}</span>
1122+
</div>
1123+
))}
1124+
</div>
1125+
)}
1126+
{/* Pending checks */}
1127+
{pendingChecks.length > 0 && (
1128+
<div className="space-y-1">
1129+
{pendingChecks.map((c) => (
1130+
<div
1131+
key={c.name}
1132+
className="flex items-center gap-2 text-[11px]"
1133+
>
1134+
<Circle className="w-3 h-3 text-yellow-500 shrink-0" />
1135+
<span className="truncate text-muted-foreground">
1136+
{c.name}
1137+
</span>
1138+
</div>
1139+
))}
1140+
</div>
1141+
)}
1142+
{/* Successful checks (collapsed if many) */}
1143+
{successChecks.length > 0 && (
1144+
<div className="space-y-1">
1145+
{successChecks.length <= 5 ? (
1146+
successChecks.map((c) => (
1147+
<div
1148+
key={c.name}
1149+
className="flex items-center gap-2 text-[11px]"
1150+
>
1151+
<CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
1152+
<span className="truncate text-muted-foreground">
1153+
{c.name}
1154+
</span>
1155+
</div>
1156+
))
1157+
) : (
1158+
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
1159+
<CheckCircle2 className="w-3 h-3 text-green-500 shrink-0" />
1160+
<span>{successChecks.length} checks passed</span>
1161+
</div>
1162+
)}
1163+
</div>
1164+
)}
1165+
</div>
1166+
) : (
1167+
<div className="text-[11px] text-muted-foreground">
1168+
{pr.ciStatus === "action_required"
1169+
? "Workflow approval required from a maintainer"
1170+
: "No detailed check information available"}
1171+
</div>
1172+
)}
1173+
</div>
1174+
);
1175+
1176+
const badgeContent = (className: string, icon: React.ReactNode) => (
1177+
<span
1178+
className={cn(
1179+
"shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded border cursor-default",
1180+
className
1181+
)}
1182+
>
1183+
{icon}
1184+
<span className="hidden sm:inline max-w-[100px] truncate">
1185+
{summary}
1186+
</span>
1187+
</span>
1188+
);
1189+
10741190
switch (pr.ciStatus) {
10751191
case "success":
10761192
return (
1077-
<span
1078-
title={
1079-
pr.ciChecks
1080-
?.map(
1081-
(c) =>
1082-
`${c.state === "success" ? "✓" : c.state === "failure" ? "✗" : "○"} ${c.name}`
1083-
)
1084-
.join("\n") || "CI passed"
1085-
}
1086-
className="shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-green-500/15 text-green-500 border border-green-500/30"
1087-
>
1088-
<CheckCircle2 className="w-3 h-3" />
1089-
<span className="hidden sm:inline max-w-[100px] truncate">
1090-
{summary}
1091-
</span>
1092-
</span>
1193+
<Tooltip>
1194+
<TooltipTrigger asChild>
1195+
{badgeContent(
1196+
"bg-green-500/15 text-green-500 border-green-500/30",
1197+
<CheckCircle2 className="w-3 h-3" />
1198+
)}
1199+
</TooltipTrigger>
1200+
<TooltipContent side="bottom" align="start">
1201+
<TooltipChecks />
1202+
</TooltipContent>
1203+
</Tooltip>
10931204
);
10941205
case "failure":
10951206
return (
1096-
<span
1097-
title={
1098-
pr.ciChecks
1099-
?.map(
1100-
(c) =>
1101-
`${c.state === "success" ? "✓" : c.state === "failure" ? "✗" : "○"} ${c.name}`
1102-
)
1103-
.join("\n") || "CI failed"
1104-
}
1105-
className="shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-red-500/15 text-red-500 border border-red-500/30"
1106-
>
1107-
<XCircle className="w-3 h-3" />
1108-
<span className="hidden sm:inline max-w-[100px] truncate">
1109-
{summary}
1110-
</span>
1111-
</span>
1207+
<Tooltip>
1208+
<TooltipTrigger asChild>
1209+
{badgeContent(
1210+
"bg-red-500/15 text-red-500 border-red-500/30",
1211+
<XCircle className="w-3 h-3" />
1212+
)}
1213+
</TooltipTrigger>
1214+
<TooltipContent side="bottom" align="start">
1215+
<TooltipChecks />
1216+
</TooltipContent>
1217+
</Tooltip>
11121218
);
11131219
case "pending":
11141220
return (
1115-
<span
1116-
title={
1117-
pr.ciChecks
1118-
?.map(
1119-
(c) =>
1120-
`${c.state === "success" ? "✓" : c.state === "failure" ? "✗" : "○"} ${c.name}`
1121-
)
1122-
.join("\n") || "CI running"
1123-
}
1124-
className="shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-yellow-500/15 text-yellow-500 border border-yellow-500/30"
1125-
>
1126-
<Circle className="w-3 h-3 animate-pulse" />
1127-
<span className="hidden sm:inline max-w-[100px] truncate">
1128-
{summary}
1129-
</span>
1130-
</span>
1221+
<Tooltip>
1222+
<TooltipTrigger asChild>
1223+
{badgeContent(
1224+
"bg-yellow-500/15 text-yellow-500 border-yellow-500/30",
1225+
<Circle className="w-3 h-3 animate-pulse" />
1226+
)}
1227+
</TooltipTrigger>
1228+
<TooltipContent side="bottom" align="start">
1229+
<TooltipChecks />
1230+
</TooltipContent>
1231+
</Tooltip>
11311232
);
11321233
case "action_required":
11331234
return (
1134-
<span
1135-
title="Workflow approval required from a maintainer"
1136-
className="shrink-0 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-yellow-500/15 text-yellow-500 border border-yellow-500/30"
1137-
>
1138-
<AlertCircle className="w-3 h-3" />
1139-
<span className="hidden sm:inline max-w-[100px] truncate">
1140-
{summary}
1141-
</span>
1142-
</span>
1235+
<Tooltip>
1236+
<TooltipTrigger asChild>
1237+
{badgeContent(
1238+
"bg-yellow-500/15 text-yellow-500 border-yellow-500/30",
1239+
<AlertCircle className="w-3 h-3" />
1240+
)}
1241+
</TooltipTrigger>
1242+
<TooltipContent side="bottom" align="start">
1243+
<TooltipChecks />
1244+
</TooltipContent>
1245+
</Tooltip>
11431246
);
11441247
default:
11451248
return null;
@@ -1240,6 +1343,39 @@ function PRListItem({ pr, onSelect }: PRListItemProps) {
12401343
);
12411344
}
12421345

1346+
// ============================================================================
1347+
// Refresh Countdown
1348+
// ============================================================================
1349+
1350+
const REFRESH_INTERVAL_SECONDS = 60;
1351+
1352+
function RefreshCountdown({ lastFetchedAt }: { lastFetchedAt: number }) {
1353+
const [secondsRemaining, setSecondsRemaining] = useState(() => {
1354+
const elapsed = Math.floor((Date.now() - lastFetchedAt) / 1000);
1355+
return Math.max(0, REFRESH_INTERVAL_SECONDS - elapsed);
1356+
});
1357+
1358+
useEffect(() => {
1359+
// Recalculate on mount or when lastFetchedAt changes
1360+
const elapsed = Math.floor((Date.now() - lastFetchedAt) / 1000);
1361+
setSecondsRemaining(Math.max(0, REFRESH_INTERVAL_SECONDS - elapsed));
1362+
1363+
const interval = setInterval(() => {
1364+
const elapsed = Math.floor((Date.now() - lastFetchedAt) / 1000);
1365+
const remaining = Math.max(0, REFRESH_INTERVAL_SECONDS - elapsed);
1366+
setSecondsRemaining(remaining);
1367+
}, 1000);
1368+
1369+
return () => clearInterval(interval);
1370+
}, [lastFetchedAt]);
1371+
1372+
return (
1373+
<span className="text-[10px] text-muted-foreground tabular-nums">
1374+
Refreshing in {secondsRemaining}s
1375+
</span>
1376+
);
1377+
}
1378+
12431379
// ============================================================================
12441380
// Skeleton Components
12451381
// ============================================================================

0 commit comments

Comments
 (0)