Skip to content

Commit e262872

Browse files
committed
feat(impact): live PR status from GitHub API; mark #102 merged
Fetch each PR's real state (merged/open/closed) from the public GitHub API on load and override the badge + counts; the JSON status stays as a fallback when the API is unreachable or rate-limited. Also update #102 to merged and #101 to closed.
1 parent e84daf9 commit e262872

2 files changed

Lines changed: 35 additions & 7 deletions

File tree

app/components/StatsPage.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,44 @@ export function StatsPage({ onNavigate, from = "graph" }: { onNavigate: (v: "her
8989
const [filter, setFilter] = useState<Kind | "all">("all")
9090
const [openId, setOpenId] = useState<string | null>(null)
9191

92+
// Live PR state from GitHub's public API; the JSON status is the fallback.
93+
const [live, setLive] = useState<Record<string, Status>>({})
94+
useEffect(() => {
95+
let cancelled = false
96+
Promise.all(
97+
items.filter(i => i.kind === "pr").map(async (it) => {
98+
const m = it.url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/)
99+
if (!m) return null
100+
try {
101+
const r = await fetch(`https://api.github.com/repos/${m[1]}/${m[2]}/pulls/${m[3]}`)
102+
if (!r.ok) return null
103+
const d = await r.json()
104+
const s: Status = d.merged ? "merged" : d.state === "open" ? "open" : "closed"
105+
return [it.id, s] as const
106+
} catch { return null }
107+
})
108+
).then(rows => {
109+
if (cancelled) return
110+
const map: Record<string, Status> = {}
111+
for (const row of rows) if (row) map[row[0]] = row[1]
112+
setLive(map)
113+
})
114+
return () => { cancelled = true }
115+
}, [])
116+
117+
const eff = (i: Item): Status => live[i.id] ?? i.status
118+
92119
const stats = useMemo(() => {
93120
const by = (k: Kind) => items.filter(i => i.kind === k).length
94-
const prMerged = items.filter(i => i.kind === "pr" && i.status === "merged").length
95-
const prOpen = items.filter(i => i.kind === "pr" && i.status === "open").length
121+
const prMerged = items.filter(i => i.kind === "pr" && eff(i) === "merged").length
122+
const prOpen = items.filter(i => i.kind === "pr" && eff(i) === "open").length
96123
const repos = new Set(items.map(i => i.repo)).size
97124
return {
98125
total: items.length, pr: by("pr"), review: by("review"), issue: by("issue"),
99126
prMerged, prOpen, repos,
100127
}
101-
}, [])
128+
// eslint-disable-next-line react-hooks/exhaustive-deps
129+
}, [live])
102130

103131
const filtered = useMemo(() => {
104132
const list = filter === "all" ? items : items.filter(i => i.kind === filter)
@@ -117,7 +145,7 @@ export function StatsPage({ onNavigate, from = "graph" }: { onNavigate: (v: "her
117145
sub?: string; filter?: Kind | "all"
118146
}> = [
119147
{ label: "Contributions", value: stats.total, icon: LayoutGrid, color: CYAN, sub: `${stats.repos} repositories`, filter: "all" },
120-
{ label: "Pull Requests", value: stats.pr, icon: GitPullRequest, color: KIND.pr.color, sub: `${items.filter(i => i.kind === "pr" && i.status === "merged").length} merged · ${items.filter(i => i.kind === "pr" && i.status === "open").length} open`, filter: "pr" },
148+
{ label: "Pull Requests", value: stats.pr, icon: GitPullRequest, color: KIND.pr.color, sub: `${stats.prMerged} merged${stats.prOpen ? ` · ${stats.prOpen} open` : ""}`, filter: "pr" },
121149
{ label: "Reviews", value: stats.review, icon: MessageSquareText, color: KIND.review.color, sub: "on real OSS PRs", filter: "review" },
122150
{ label: "Issues & Triage", value: stats.issue, icon: CircleDot, color: KIND.issue.color, sub: "reported & reproduced", filter: "issue" },
123151
]
@@ -260,7 +288,7 @@ export function StatsPage({ onNavigate, from = "graph" }: { onNavigate: (v: "her
260288
{filtered.map((it, i) => {
261289
const k = KIND[it.kind]
262290
const Icon = k.icon
263-
const st = STATUS[it.status]
291+
const st = STATUS[eff(it)]
264292
const isOpen = openId === it.id
265293
return (
266294
<motion.div

data/contributions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"kind": "pr",
77
"title": "Persist editor panel sizes across launches",
88
"repo": "palmier-io/palmier-pro",
9-
"status": "open",
9+
"status": "merged",
1010
"date": "2026-06-22",
1111
"url": "https://github.com/palmier-io/palmier-pro/pull/102",
1212
"summary": "NSSplitView.autosaveName + a per-split guard so dragged panel sizes survive a quit and relaunch."
@@ -16,7 +16,7 @@
1616
"kind": "issue",
1717
"title": "Editor panel sizes reset to defaults on relaunch",
1818
"repo": "palmier-io/palmier-pro",
19-
"status": "open",
19+
"status": "closed",
2020
"date": "2026-06-22",
2121
"url": "https://github.com/palmier-io/palmier-pro/issues/101",
2222
"summary": "Filed the bug report that PR #102 fixes, issue-first per their CONTRIBUTING guide."

0 commit comments

Comments
 (0)