Skip to content

Commit 8888542

Browse files
committed
Add PR status overlay to bottom-right of sidebar task icon
1 parent 0a3e638 commit 8888542

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

components/pr-check-status.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
import { Check, Loader2, X } from 'lucide-react'
5+
6+
interface CheckRun {
7+
id: number
8+
name: string
9+
status: string
10+
conclusion: string | null
11+
html_url: string
12+
started_at: string | null
13+
completed_at: string | null
14+
}
15+
16+
interface PRCheckStatusProps {
17+
taskId: string
18+
className?: string
19+
}
20+
21+
export function PRCheckStatus({ taskId, className = '' }: PRCheckStatusProps) {
22+
const [checkRuns, setCheckRuns] = useState<CheckRun[]>([])
23+
const [isLoading, setIsLoading] = useState(true)
24+
25+
useEffect(() => {
26+
const fetchCheckRuns = async () => {
27+
try {
28+
const response = await fetch(`/api/tasks/${taskId}/check-runs`)
29+
if (response.ok) {
30+
const data = await response.json()
31+
if (data.success && data.checkRuns) {
32+
setCheckRuns(data.checkRuns)
33+
}
34+
}
35+
} catch (error) {
36+
console.error('Error fetching check runs:', error)
37+
} finally {
38+
setIsLoading(false)
39+
}
40+
}
41+
42+
fetchCheckRuns()
43+
// Refresh every 30 seconds for in-progress checks
44+
const interval = setInterval(fetchCheckRuns, 30000)
45+
return () => clearInterval(interval)
46+
}, [taskId])
47+
48+
// Don't render anything if loading or no check runs
49+
if (isLoading || checkRuns.length === 0) {
50+
return null
51+
}
52+
53+
// Determine overall status
54+
const hasInProgress = checkRuns.some((run) => run.status === 'in_progress' || run.status === 'queued')
55+
const hasFailed = checkRuns.some((run) => run.conclusion === 'failure' || run.conclusion === 'cancelled')
56+
const allPassed = checkRuns.every((run) => run.status === 'completed' && run.conclusion === 'success')
57+
58+
// Render the appropriate indicator
59+
if (hasInProgress) {
60+
return (
61+
<div className={`absolute -bottom-0.5 -right-0.5 bg-background rounded-full p-0.5 ${className}`}>
62+
<div className="w-2 h-2 rounded-full bg-yellow-500 animate-pulse" />
63+
</div>
64+
)
65+
}
66+
67+
if (hasFailed) {
68+
return (
69+
<div className={`absolute -bottom-0.5 -right-0.5 bg-background rounded-full p-0.5 ${className}`}>
70+
<div className="w-2 h-2 rounded-full bg-red-500" />
71+
</div>
72+
)
73+
}
74+
75+
if (allPassed) {
76+
return (
77+
<div className={`absolute -bottom-0.5 -right-0.5 bg-background rounded-full p-0.5 ${className}`}>
78+
<Check className="w-2.5 h-2.5 text-green-500" strokeWidth={3} />
79+
</div>
80+
)
81+
}
82+
83+
return null
84+
}

components/task-sidebar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { useTasks } from '@/components/app-layout'
2525
import { useAtomValue } from 'jotai'
2626
import { sessionAtom } from '@/lib/atoms/session'
2727
import { PRStatusIcon } from '@/components/pr-status-icon'
28+
import { PRCheckStatus } from '@/components/pr-check-status'
2829

2930
// Model mappings for human-friendly names
3031
const AGENT_MODELS = {
@@ -397,7 +398,12 @@ export function TaskSidebar({ tasks, onTaskSelect, width = 288 }: TaskSidebarPro
397398
</div>
398399
{task.repoUrl && (
399400
<div className="flex items-center gap-1 text-xs text-muted-foreground mb-0.5">
400-
{task.prStatus && <PRStatusIcon status={task.prStatus} />}
401+
{task.prStatus && (
402+
<div className="relative">
403+
<PRStatusIcon status={task.prStatus} />
404+
<PRCheckStatus taskId={task.id} />
405+
</div>
406+
)}
401407
<span className="truncate">
402408
{(() => {
403409
try {

components/tasks-list-client.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type { Session } from '@/lib/session/types'
2828
import { VERCEL_DEPLOY_URL } from '@/lib/constants'
2929
import { Claude, Codex, Copilot, Cursor, Gemini, OpenCode } from '@/components/logos'
3030
import { PRStatusIcon } from '@/components/pr-status-icon'
31+
import { PRCheckStatus } from '@/components/pr-check-status'
3132

3233
interface TasksListClientProps {
3334
user: Session['user'] | null
@@ -420,7 +421,12 @@ export function TasksListClient({ user, authProvider, initialStars = 1056 }: Tas
420421
</div>
421422
{task.repoUrl && (
422423
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-1">
423-
{task.prStatus && <PRStatusIcon status={task.prStatus} />}
424+
{task.prStatus && (
425+
<div className="relative">
426+
<PRStatusIcon status={task.prStatus} />
427+
<PRCheckStatus taskId={task.id} />
428+
</div>
429+
)}
424430
<span className="truncate">
425431
{(() => {
426432
try {

0 commit comments

Comments
 (0)