diff --git a/src/components/feed/feed-view.tsx b/src/components/feed/feed-view.tsx
index b923d6e..8420f3c 100644
--- a/src/components/feed/feed-view.tsx
+++ b/src/components/feed/feed-view.tsx
@@ -11,6 +11,7 @@ import { getLatestNodes } from "@/lib/graph-api"
import { schemaTypeComparator } from "@/lib/node-schema-utils"
import { FeedCard } from "./feed-card"
import { HotTakes } from "./hot-takes"
+import { GraphStats } from "./graph-stats"
import { cn } from "@/lib/utils"
export function FeedView() {
@@ -91,6 +92,7 @@ export function FeedView() {
return (
+ {showHotTakes &&
}
{showHotTakes &&
}
{(showHotTakes || typeCounts.length > 1) && (
diff --git a/src/components/feed/graph-stats.tsx b/src/components/feed/graph-stats.tsx
new file mode 100644
index 0000000..ee1be68
--- /dev/null
+++ b/src/components/feed/graph-stats.tsx
@@ -0,0 +1,58 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { getStats } from "@/lib/graph-api"
+import { isMocksEnabled } from "@/lib/mock-data"
+import { formatStatNumber, getDisplayStats, type GraphStat } from "@/lib/graph-stats"
+
+const MOCK_STATS: GraphStat[] = [{ label: "Nodes", value: 1234 }]
+
+export function GraphStats() {
+ const mocks = isMocksEnabled()
+ const [stats, setStats] = useState
(mocks ? MOCK_STATS : null)
+ const [error, setError] = useState(false)
+
+ useEffect(() => {
+ if (mocks) return
+ const controller = new AbortController()
+ getStats(controller.signal)
+ .then((res) => setStats(getDisplayStats(res)))
+ .catch((err) => {
+ if (controller.signal.aborted) return
+ console.error("[graph-stats] getStats failed:", err)
+ setError(true)
+ })
+ return () => controller.abort()
+ }, [mocks])
+
+ if (error) {
+ return (
+
+ Stats unavailable
+
+ )
+ }
+
+ if (stats === null) {
+ return (
+
+ )
+ }
+
+ if (stats.length === 0) return null
+
+ return (
+
+ {stats.map((s) => (
+
+
+ {s.label}
+
+
{formatStatNumber(s.value)}
+
+ ))}
+
+ )
+}
diff --git a/src/lib/__tests__/graph-stats.test.ts b/src/lib/__tests__/graph-stats.test.ts
new file mode 100644
index 0000000..7ee925d
--- /dev/null
+++ b/src/lib/__tests__/graph-stats.test.ts
@@ -0,0 +1,25 @@
+import { describe, it, expect } from "vitest"
+import { getDisplayStats, formatStatNumber } from "../graph-stats"
+
+describe("getDisplayStats", () => {
+ it("returns nodes and CVEs when cve_count present", () => {
+ expect(getDisplayStats({ node_count: 10, cve_count: 3 })).toEqual([
+ { label: "Nodes", value: 10 },
+ { label: "CVEs", value: 3 },
+ ])
+ })
+ it("returns only nodes when cve_count absent or 0", () => {
+ expect(getDisplayStats({ node_count: 10 })).toEqual([{ label: "Nodes", value: 10 }])
+ expect(getDisplayStats({ node_count: 10, cve_count: 0 })).toEqual([{ label: "Nodes", value: 10 }])
+ })
+ it("uses node_count, not num_nodes", () => {
+ expect(getDisplayStats({ node_count: 5, num_nodes: 0 })[0].value).toBe(5)
+ })
+ it("returns nothing without node_count", () => {
+ expect(getDisplayStats({ num_nodes: 7 })).toEqual([])
+ expect(getDisplayStats(null)).toEqual([])
+ })
+ it("formats with grouping", () => {
+ expect(formatStatNumber(1776798)).toBe("1,776,798")
+ })
+})
diff --git a/src/lib/graph-api.ts b/src/lib/graph-api.ts
index 88abd8c..f434a69 100644
--- a/src/lib/graph-api.ts
+++ b/src/lib/graph-api.ts
@@ -33,7 +33,8 @@ interface NodesListResponse {
totalCount?: number
}
-interface StatsResponse {
+export interface StatsResponse {
+ node_count: number
num_nodes: number
num_episodes: number
num_people: number
diff --git a/src/lib/graph-stats.ts b/src/lib/graph-stats.ts
new file mode 100644
index 0000000..b76e166
--- /dev/null
+++ b/src/lib/graph-stats.ts
@@ -0,0 +1,26 @@
+import type { StatsResponse } from "@/lib/graph-api"
+
+export interface GraphStat {
+ label: string
+ value: number
+}
+
+// Per-type counts shown only when the graph reports a positive count.
+// Add entries here to surface more node types.
+export const TYPE_STATS: readonly { key: string; label: string }[] = [
+ { key: "cve_count", label: "CVEs" },
+]
+
+export function getDisplayStats(stats: Partial | null | undefined): GraphStat[] {
+ if (!stats || typeof stats.node_count !== "number") return []
+ const result: GraphStat[] = [{ label: "Nodes", value: stats.node_count }]
+ for (const { key, label } of TYPE_STATS) {
+ const value = (stats as Record)[key]
+ if (typeof value === "number" && value > 0) result.push({ label, value })
+ }
+ return result
+}
+
+export function formatStatNumber(n: number): string {
+ return n.toLocaleString("en-US")
+}