JustAPI Bot fix: comprehensive UI, docs, security, and deployment improvements · 7/14/2026
381467d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
---
interface Props {
stack: {
id: string;
name: string;
repoName: string;
repoOwner: string;
status: string;
updatedAt: string;
prs: {
id: string;
number: number;
title: string;
status: string;
url: string;
position: number;
isMerged: boolean;
isClosed: boolean;
}[];
};
}
const { stack } = Astro.props;
// Determine stack status color
const isFullyMerged = stack.prs.every(pr => pr.isMerged);
const hasFailures = stack.prs.some(pr => pr.status === 'failure' || pr.status === 'error');
const isDraft = stack.prs.some(pr => pr.status === 'draft');
const statusColor = isFullyMerged ? "text-green-400" : hasFailures ? "text-red-400" : isDraft ? "text-muted-foreground" : "text-blue-400";
const statusBorder = isFullyMerged ? "border-green-500/30" : hasFailures ? "border-red-500/30" : isDraft ? "border-gray-500/30" : "border-blue-500/30";
const statusBg = isFullyMerged ? "bg-green-500/10" : hasFailures ? "bg-red-500/10" : isDraft ? "bg-gray-500/10" : "bg-blue-500/10";
---
<div class="relative group">
<div class="absolute -inset-[1px] bg-gradient-to-r from-blue-500/20 via-cyan-500/20 to-purple-500/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity" />
<div class="relative rounded-xl border border-border bg-card/80 backdrop-blur-sm p-5 hover:bg-accent/50 transition-colors">
<!-- Header -->
<div class="flex items-center justify-between mb-4">
<div>
<div class="flex items-center gap-2">
<span class={`text-sm font-semibold ${statusColor}`}>{stack.name}</span>
<span class="text-xs text-muted-foreground">in {stack.repoOwner}/{stack.repoName}</span>
</div>
<div class="text-xs text-muted-foreground mt-0.5">
Updated {new Date(stack.updatedAt).toLocaleDateString()}
</div>
</div>
<div class={`text-xs px-2 py-0.5 rounded-full border ${statusBorder} ${statusColor} ${statusBg}`}>
{isFullyMerged ? "Merged" : hasFailures ? "Failed" : isDraft ? "Draft" : "Active"}
</div>
</div>
<!-- Stack Visualization (Subway Map) -->
<div class="relative pl-2 space-y-0">
{stack.prs.map((pr, index) => (
<div class="relative flex items-start gap-3 pb-4 last:pb-0">
<!-- Connecting Line -->
{index !== stack.prs.length - 1 && (
<div class="absolute left-[5px] top-2.5 bottom-0 w-0.5 bg-gray-800" />
)}
<!-- Dot -->
<div class={`relative z-10 w-3 h-3 rounded-full mt-1.5 border-2 ${
pr.isMerged
? "bg-green-500 border-green-500"
: pr.isClosed
? "bg-red-500 border-red-500"
: "bg-card border-blue-500"
}`} />
<!-- Content -->
<div class="flex-1 min-w-0 -mt-0.5">
<a href={pr.url} class="block hover:underline truncate text-sm font-medium text-muted-foreground hover:text-blue-400 decoration-blue-400/50">
{pr.title}
</a>
<div class="flex items-center gap-2 text-xs text-muted-foreground">
<span class="font-mono">#{pr.number}</span>
<span>•</span>
<span class={
pr.isMerged ? "text-green-500" : pr.isClosed ? "text-red-500" : "text-blue-500"
}>
{pr.status.charAt(0).toUpperCase() + pr.status.slice(1)}
</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>