SupportRAG / frontend /components /agent-interface.tsx
Sakshamyadav15's picture
Revision
e7dfc31
"use client"
import { useEffect, useRef, useState, useCallback } from "react"
import { GitPullRequest, GitMerge, MessageSquare, CheckCircle2, Clock, AlertCircle, Zap, GitCommit, Eye, Terminal } from "lucide-react"
// ── Data ─────────────────────────────────────────────────────────────────────
const ALL_PRS = [
{ id: 145, title: "feat: multi-agent orchestration v2", agent: "orchestrator", status: "review", comments: 2, additions: 57, deletions: 4, branch: "feat/orchestration-v2", time: "Just now" },
{ id: 144, title: "fix: memory context window overflow", agent: "analyst-agent", status: "review", comments: 1, additions: 18, deletions: 3, branch: "fix/ctx-overflow", time: "1m ago" },
{ id: 143, title: "feat: streaming tool response", agent: "monitor-agent", status: "merged", comments: 4, additions: 93, deletions: 11, branch: "feat/stream-tools", time: "1m ago" },
{ id: 142, title: "feat: add memory context to executor", agent: "executor-agent", status: "merged", comments: 3, additions: 84, deletions: 12, branch: "feat/memory-ctx", time: "2m ago" },
{ id: 141, title: "fix: rate limit backoff strategy", agent: "monitor-agent", status: "approved",comments: 1, additions: 31, deletions: 8, branch: "fix/rate-backoff", time: "8m ago" },
{ id: 140, title: "feat: parallel tool execution", agent: "researcher-agent",status: "review", comments: 5, additions: 142, deletions: 27, branch: "feat/parallel-tools", time: "22m ago" },
{ id: 139, title: "refactor: orchestrator pipeline", agent: "analyst-agent", status: "merged", comments: 7, additions: 209, deletions: 88, branch: "refactor/pipeline", time: "1h ago" },
]
const ALL_REVIEW_FILES = [
{ file: "agent/executor.ts", pct: 72 },
{ file: "lib/tools/index.ts", pct: 45 },
{ file: "core/planner.ts", pct: 88 },
{ file: "utils/retry.ts", pct: 31 },
{ file: "agent/memory.ts", pct: 60 },
]
const ALL_REVIEW_LINES: { type: "comment"|"approve"|"change"|"code"; text: string; author?: string }[] = [
{ type: "code", text: 'const ctx = await memory.load(task.id)' },
{ type: "comment", text: "Should we cache this per agent run?", author: "analyst-agent" },
{ type: "code", text: 'return researcher.execute(task, ctx)' },
{ type: "approve", text: "LGTM — memory scope looks correct", author: "monitor-agent" },
{ type: "code", text: 'export const run = async (task) => {' },
{ type: "change", text: "Consider adding retry logic here", author: "executor-agent" },
{ type: "code", text: ' const plan = await planner.run(goal)' },
{ type: "approve", text: "Approved — ship it", author: "orchestrator" },
{ type: "code", text: ' await ctx.memory.save(result)' },
{ type: "comment", text: "Add error boundary here", author: "monitor-agent" },
{ type: "code", text: 'return { status: "done", result }' },
{ type: "approve", text: "All checks pass", author: "analyst-agent" },
]
const COMMITS = [
{ hash: "a3f8c21", msg: "fix: memory leak in long-running agents", time: "Just now" },
{ hash: "b7d2e09", msg: "feat: streaming response for analyst", time: "4m ago" },
{ hash: "c9a1f34", msg: "chore: bump @agentic/sdk to 2.4.1", time: "12m ago" },
{ hash: "d4e6b78", msg: "perf: reduce token overhead by 18%", time: "31m ago" },
{ hash: "e2c9d56", msg: "feat: add guardrails to executor-agent", time: "1h ago" },
]
// Activity graph data — 7 cols x 5 rows like GitHub contributions
const ACTIVITY_SEED = Array.from({ length: 35 }, () => ({
level: Math.random() > 0.4 ? Math.floor(Math.random() * 4) + 1 : 0,
}))
// ── Sub-components ────────────────────────────────────────────────────────────
// Smooth 60fps bar chart — canvas fills full container width
function MiniBarGraph({ seed }: { seed: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const rafRef = useRef<number>(0)
const barsRef = useRef<number[]>([])
useEffect(() => {
const N = 20
// Initialise bars with a seeded pattern
barsRef.current = Array.from({ length: N }, (_, i) =>
0.2 + 0.8 * Math.abs(Math.sin((i + seed) * 1.3))
)
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")!
const draw = () => {
const W = canvas.offsetWidth
const H = canvas.offsetHeight
canvas.width = W * devicePixelRatio
canvas.height = H * devicePixelRatio
ctx.scale(devicePixelRatio, devicePixelRatio)
ctx.clearRect(0, 0, W, H)
// Slowly drift each bar toward a new random target
barsRef.current = barsRef.current.map((v, i) => {
const target = 0.15 + 0.85 * Math.abs(Math.sin(Date.now() / 3000 + i * 0.8 + seed))
return v + (target - v) * 0.012 // lerp — very smooth
})
const bars = barsRef.current
const gap = 2
const bw = (W - gap * (N - 1)) / N
bars.forEach((v, i) => {
const bh = v * H
const x = i * (bw + gap)
const y = H - bh
ctx.beginPath()
ctx.roundRect(x, y, bw, bh, 2)
ctx.fillStyle = `rgba(17,17,17,${0.12 + v * 0.65})`
ctx.fill()
})
rafRef.current = requestAnimationFrame(draw)
}
rafRef.current = requestAnimationFrame(draw)
return () => cancelAnimationFrame(rafRef.current)
}, [seed])
return <canvas ref={canvasRef} style={{ width: "100%", height: 28, display: "block" }} />
}
// Smooth 60fps area sparkline — fills full width
function LiveSparkline({ seed }: { seed?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const rafRef = useRef<number>(0)
const ptsRef = useRef<number[]>([])
useEffect(() => {
const N = 24
ptsRef.current = Array.from({ length: N }, (_, i) =>
0.1 + 0.7 * Math.abs(Math.sin(i * 0.6 + (seed ?? 0)))
)
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")!
const draw = () => {
const W = canvas.offsetWidth
const H = canvas.offsetHeight
canvas.width = W * devicePixelRatio
canvas.height = H * devicePixelRatio
ctx.scale(devicePixelRatio, devicePixelRatio)
ctx.clearRect(0, 0, W, H)
// Scroll left: drop first point, lerp last toward new target
const last = ptsRef.current[ptsRef.current.length - 1]
const target = 0.1 + 0.85 * (0.5 + 0.5 * Math.sin(Date.now() / 2200 + (seed ?? 0)))
ptsRef.current = [...ptsRef.current.slice(1), last + (target - last) * 0.04]
const pts = ptsRef.current
const step = W / (N - 1)
// Area fill
const grad = ctx.createLinearGradient(0, 0, 0, H)
grad.addColorStop(0, "rgba(17,17,17,0.10)")
grad.addColorStop(1, "rgba(17,17,17,0)")
ctx.beginPath()
ctx.moveTo(0, H)
pts.forEach((v, i) => ctx.lineTo(i * step, H - v * H * 0.9))
ctx.lineTo(W, H)
ctx.closePath()
ctx.fillStyle = grad
ctx.fill()
// Line
ctx.beginPath()
pts.forEach((v, i) => {
const x = i * step, y = H - v * H * 0.9
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)
})
ctx.strokeStyle = "rgba(17,17,17,0.75)"
ctx.lineWidth = 1.5
ctx.lineJoin = "round"
ctx.lineCap = "round"
ctx.stroke()
// Dot at end
const ex = W
const ey = H - pts[pts.length - 1] * H * 0.9
ctx.beginPath()
ctx.arc(ex, ey, 2.5, 0, Math.PI * 2)
ctx.fillStyle = "rgba(17,17,17,0.85)"
ctx.fill()
rafRef.current = requestAnimationFrame(draw)
}
rafRef.current = requestAnimationFrame(draw)
return () => cancelAnimationFrame(rafRef.current)
}, [seed])
return <canvas ref={canvasRef} style={{ width: "100%", height: 28, display: "block" }} />
}
// Smooth 60fps dot/line graph — fills full width
function MiniDotGraph({ seed }: { seed?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const rafRef = useRef<number>(0)
const ptsRef = useRef<number[]>([])
useEffect(() => {
const N = 18
ptsRef.current = Array.from({ length: N }, (_, i) =>
0.1 + 0.8 * Math.abs(Math.sin(i * 0.9 + (seed ?? 2)))
)
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")!
const draw = () => {
const W = canvas.offsetWidth
const H = canvas.offsetHeight
canvas.width = W * devicePixelRatio
canvas.height = H * devicePixelRatio
ctx.scale(devicePixelRatio, devicePixelRatio)
ctx.clearRect(0, 0, W, H)
const last = ptsRef.current[ptsRef.current.length - 1]
const target = 0.1 + 0.85 * (0.5 + 0.5 * Math.sin(Date.now() / 2800 + (seed ?? 2) * 1.5))
ptsRef.current = [...ptsRef.current.slice(1), last + (target - last) * 0.03]
const pts = ptsRef.current
const step = W / (N - 1)
// Dashed connector line
ctx.beginPath()
pts.forEach((v, i) => {
const x = i * step, y = H - v * H * 0.88
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)
})
ctx.strokeStyle = "rgba(17,17,17,0.15)"
ctx.lineWidth = 1
ctx.setLineDash([3, 3])
ctx.stroke()
ctx.setLineDash([])
// Dots
pts.forEach((v, i) => {
const x = i * step, y = H - v * H * 0.88
ctx.beginPath()
ctx.arc(x, y, 2.2, 0, Math.PI * 2)
ctx.fillStyle = `rgba(17,17,17,${0.2 + v * 0.65})`
ctx.fill()
})
rafRef.current = requestAnimationFrame(draw)
}
rafRef.current = requestAnimationFrame(draw)
return () => cancelAnimationFrame(rafRef.current)
}, [seed])
return <canvas ref={canvasRef} style={{ width: "100%", height: 28, display: "block" }} />
}
function StatusBadge({ status }: { status: string }) {
const cfg = {
merged: { bg: "rgba(130,80,255,0.1)", color: "#8250df", icon: <GitMerge style={{ width: 9, height: 9 }} />, label: "Merged" },
approved: { bg: "rgba(40,167,69,0.1)", color: "#28a745", icon: <CheckCircle2 style={{ width: 9, height: 9 }} />, label: "Approved" },
review: { bg: "rgba(201,169,110,0.12)",color: "#b07d30", icon: <Eye style={{ width: 9, height: 9 }} />, label: "In Review"},
}[status] ?? { bg: "#eee", color: "#666", icon: null, label: status }
return (
<span style={{
display: "inline-flex", alignItems: "center", gap: 4,
fontSize: 8, padding: "2px 7px", borderRadius: 99,
background: cfg.bg, color: cfg.color,
fontFamily: "monospace", letterSpacing: "0.08em", textTransform: "uppercase",
fontWeight: 600,
}}>
{cfg.icon}{cfg.label}
</span>
)
}
function Bar({ pct, color = "rgba(0,0,0,0.75)" }: { pct: number; color?: string }) {
const [w, setW] = useState(0)
useEffect(() => {
const t = setTimeout(() => setW(pct), 600)
return () => clearTimeout(t)
}, [pct])
return (
<div style={{ height: 2, background: "rgba(0,0,0,0.07)", borderRadius: 99, width: "100%", overflow: "hidden" }}>
<div style={{ height: "100%", width: `${w}%`, background: color, borderRadius: 99, transition: "width 1.4s cubic-bezier(0.16,1,0.3,1)" }} />
</div>
)
}
function Counter({ to, suffix = "" }: { to: number; suffix?: string }) {
const [val, setVal] = useState(0)
useEffect(() => {
let s: number | null = null
const f = (ts: number) => {
if (!s) s = ts
const p = Math.min((ts - s) / 1100, 1)
setVal(Math.round((1 - Math.pow(1 - p, 3)) * to))
if (p < 1) requestAnimationFrame(f)
}
requestAnimationFrame(f)
}, [to])
return <>{val}{suffix}</>
}
function LiveDot() {
return (
<span style={{ position: "relative", display: "inline-flex", width: 7, height: 7, flexShrink: 0 }}>
<span style={{ position: "absolute", inset: 0, borderRadius: "50%", background: "#28a745", opacity: 0.4, animation: "ping 1.8s cubic-bezier(0,0,0.2,1) infinite" }} />
<span style={{ borderRadius: "50%", width: "100%", height: "100%", background: "#28a745" }} />
</span>
)
}
// Activity heatmap cell
function HeatCell({ level, animDelay }: { level: number; animDelay: number }) {
const [visible, setVisible] = useState(false)
useEffect(() => { const t = setTimeout(() => setVisible(true), animDelay); return () => clearTimeout(t) }, [animDelay])
const colors = ["rgba(0,0,0,0.05)", "rgba(0,0,0,0.15)", "rgba(0,0,0,0.32)", "rgba(0,0,0,0.55)", "rgba(0,0,0,0.8)"]
return (
<div style={{
width: 9, height: 9, borderRadius: 2,
background: colors[level],
opacity: visible ? 1 : 0,
transition: `opacity 0.4s ease`,
}} />
)
}
// Animated typing cursor in review
function ReviewLine({ item, delay }: { item: typeof REVIEW_LINES[0]; delay: number }) {
const [visible, setVisible] = useState(false)
useEffect(() => { const t = setTimeout(() => setVisible(true), delay); return () => clearTimeout(t) }, [delay])
if (!visible) return null
if (item.type === "code") {
return (
<div style={{ padding: "3px 10px", background: "rgba(0,0,0,0.04)", borderLeft: "2px solid rgba(0,0,0,0.08)", margin: "2px 0", animation: "logIn 0.2s ease forwards", opacity: 0 }}>
<code style={{ fontSize: 9, fontFamily: "monospace", color: "rgba(0,0,0,0.55)" }}>{item.text}</code>
</div>
)
}
const iconCfg = {
approve: { icon: <CheckCircle2 style={{ width: 9, height: 9, color: "#28a745", flexShrink: 0 }} />, color: "#28a745" },
change: { icon: <AlertCircle style={{ width: 9, height: 9, color: "#b07d30", flexShrink: 0 }} />, color: "#b07d30" },
comment: { icon: <MessageSquare style={{ width: 9, height: 9, color: "rgba(0,0,0,0.35)", flexShrink: 0 }} />, color: "rgba(0,0,0,0.5)" },
}[item.type] ?? { icon: null, color: "rgba(0,0,0,0.5)" }
return (
<div style={{ display: "flex", gap: 6, alignItems: "flex-start", padding: "4px 0", animation: "logIn 0.2s ease forwards", opacity: 0 }}>
{iconCfg.icon}
<div>
<span style={{ fontSize: 9, color: iconCfg.color, fontFamily: "monospace" }}>{item.text}</span>
{item.author && <span style={{ fontSize: 8, color: "rgba(0,0,0,0.3)", marginLeft: 5, fontFamily: "monospace" }}>— {item.author}</span>}
</div>
</div>
)
}
// ── Main ─────────────────────────────────────────────────────────────────────
export function AgentInterface({ revealDelay = 0 }: { revealDelay?: number }) {
const [revealed, setRevealed] = useState(false)
const [mounted, setMounted] = useState(false)
const [reqCount, setReqCount] = useState(1847)
const [cursor, setCursor] = useState(true)
const [prOffset, setPrOffset] = useState(0)
const [reviewFileIdx, setReviewFileIdx] = useState(0)
const [reviewFilePcts, setReviewFilePcts] = useState([72, 45, 88, 31, 60])
const [reviewLineIdx, setReviewLineIdx] = useState(0)
const [activity, setActivity] = useState(ACTIVITY_SEED)
// Slide-up reveal synced to intro animation end
useEffect(() => {
const t = setTimeout(() => setRevealed(true), revealDelay)
return () => clearTimeout(t)
}, [revealDelay])
// Internal content mounts 300ms after reveal starts (lets slide settle first)
useEffect(() => {
const t = setTimeout(() => setMounted(true), revealDelay + 300)
return () => clearTimeout(t)
}, [revealDelay])
// Live tasks/min counter
useEffect(() => {
const t = setInterval(() => {
setReqCount(v => v + Math.floor(Math.random() * 8 + 2))
}, 1600)
return () => clearInterval(t)
}, [])
// PR list auto-scroll: new PR arrives every 5s, list shifts
useEffect(() => {
if (!mounted) return
const t = setInterval(() => setPrOffset(v => (v + 1) % (ALL_PRS.length - 3)), 4000)
return () => clearInterval(t)
}, [mounted])
// Code review: cycle through files and advance progress bars
useEffect(() => {
if (!mounted) return
const t = setInterval(() => {
setReviewFilePcts(p => p.map((v, i) => {
const delta = Math.random() * 4 - 1
return Math.max(10, Math.min(99, v + (i === reviewFileIdx ? Math.abs(delta) + 1 : delta * 0.3)))
}))
}, 800)
return () => clearInterval(t)
}, [mounted, reviewFileIdx])
// Cycle active file highlight every 3s
useEffect(() => {
if (!mounted) return
const t = setInterval(() => setReviewFileIdx(v => (v + 1) % ALL_REVIEW_FILES.length), 2800)
return () => clearInterval(t)
}, [mounted])
// Review lines appear one by one, then reset and loop
useEffect(() => {
if (!mounted) return
const t = setInterval(() => {
setReviewLineIdx(p => {
if (p >= ALL_REVIEW_LINES.length) return 0
return p + 1
})
}, 650)
return () => clearInterval(t)
}, [mounted])
// Heatmap: occasional cell lights up
useEffect(() => {
if (!mounted) return
const t = setInterval(() => {
setActivity(prev => {
const next = [...prev]
const idx = Math.floor(Math.random() * next.length)
next[idx] = { level: Math.min(4, next[idx].level + 1) }
return next
})
}, 700)
return () => clearInterval(t)
}, [mounted])
// Cursor blink
useEffect(() => { const t = setInterval(() => setCursor(c => !c), 530); return () => clearInterval(t) }, [])
const anim = (delay: number): React.CSSProperties => ({
opacity: mounted ? 1 : 0,
transform: mounted ? "translateY(0)" : "translateY(10px)",
transition: `opacity 0.7s cubic-bezier(0.16,1,0.3,1) ${delay}ms, transform 0.7s cubic-bezier(0.16,1,0.3,1) ${delay}ms`,
})
const panel: React.CSSProperties = { background: "#fff", border: "1px solid rgba(0,0,0,0.07)", borderRadius: 10, overflow: "hidden" }
const visiblePRs = ALL_PRS.slice(prOffset, prOffset + 4)
return (
<div
className="relative z-10 flex items-center justify-center pointer-events-none select-none px-3 md:px-8 w-full md:absolute md:inset-0 md:pt-[220px] md:pb-[8%]"
style={{ paddingTop: "16px", paddingBottom: "16px" }}
>
<div style={{
width: "100%", maxWidth: 900,
background: "rgba(246,245,242,0.96)",
border: "1px solid rgba(0,0,0,0.1)",
backdropFilter: "blur(32px)",
borderRadius: 18,
overflow: "hidden",
boxShadow: "0 28px 70px rgba(0,0,0,0.25), 0 1px 0 rgba(255,255,255,0.95) inset",
// Slide up from bottom when revealed
opacity: revealed ? 1 : 0,
transform: revealed ? "translateY(0)" : "translateY(72px)",
transition: "opacity 0.9s cubic-bezier(0.16,1,0.3,1), transform 0.9s cubic-bezier(0.16,1,0.3,1)",
}}>
{/* Titlebar */}
<div style={{
display: "flex", alignItems: "center",
padding: "9px 14px",
borderBottom: "1px solid rgba(0,0,0,0.07)",
background: "rgba(255,255,255,0.65)",
position: "relative",
}}>
<div style={{ display: "flex", gap: 5 }}>
{["#ff5f56","#ffbd2e","#27c93f"].map(c => (
<span key={c} style={{ width: 10, height: 10, borderRadius: "50%", background: c, display: "inline-block" }} />
))}
</div>
<span style={{
position: "absolute", left: "50%", transform: "translateX(-50%)",
fontSize: 10, letterSpacing: "0.18em", color: "rgba(0,0,0,0.28)", fontFamily: "monospace",
}}>agentic / platform — main</span>
<div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 8 }}>
<LiveDot />
<span style={{ fontSize: 8, color: "rgba(40,167,69,0.8)", letterSpacing: "0.16em", fontFamily: "monospace" }}>ALL SYSTEMS OPERATIONAL</span>
</div>
</div>
{/* Metrics strip — fixed height */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", borderBottom: "1px solid rgba(0,0,0,0.06)", background: "rgba(251,250,247,0.9)" }}>
{[
{ label: "PRs Merged today", val: 18, icon: <GitMerge style={{ width: 11, height: 11 }} />, graph: <MiniBarGraph seed={0} /> },
{ label: "Reviews completed", val: 34, icon: <Eye style={{ width: 11, height: 11 }} />, graph: <MiniBarGraph seed={5} /> },
{ label: "Agent commits", val: 127, icon: <GitCommit style={{ width: 11, height: 11 }} />, graph: <MiniDotGraph seed={2} /> },
{ label: "Tasks / min", val: reqCount, icon: <Zap style={{ width: 11, height: 11 }} />, graph: <LiveSparkline seed={7} /> },
].map((m, i) => (
<div key={i} style={{ padding: "9px 12px", height: 82, overflow: "hidden", borderRight: i < 3 ? "1px solid rgba(0,0,0,0.06)" : "none", ...anim(60 + i * 45) }}>
<div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 4 }}>
<span style={{ color: "rgba(0,0,0,0.32)" }}>{m.icon}</span>
<span style={{ fontSize: 7.5, letterSpacing: "0.14em", textTransform: "uppercase", color: "rgba(0,0,0,0.32)", fontFamily: "monospace" }}>{m.label}</span>
</div>
<div style={{ fontSize: 20, fontWeight: 700, color: "#111", lineHeight: 1, marginBottom: 5, fontFamily: "monospace" }}>
{mounted ? <Counter to={m.val} /> : "—"}
</div>
{mounted && m.graph}
</div>
))}
</div>
{/* Main 3-col body — fixed height container */}
<div style={{ display: "grid", gridTemplateColumns: "1.1fr 1fr 0.85fr", gap: 8, padding: 8, height: 340, overflow: "hidden" }}>
{/* Col 1 — PR list */}
<div style={{ display: "flex", flexDirection: "column", gap: 5, height: "100%", overflow: "hidden", ...anim(160) }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 2px", flexShrink: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 5 }}>
<GitPullRequest style={{ width: 10, height: 10, color: "rgba(0,0,0,0.38)" }} />
<span style={{ fontSize: 8.5, letterSpacing: "0.13em", textTransform: "uppercase", color: "rgba(0,0,0,0.38)", fontFamily: "monospace" }}>Pull Requests</span>
</div>
<span style={{ fontSize: 7.5, color: "rgba(0,0,0,0.25)", fontFamily: "monospace" }}>{ALL_PRS.filter(p => p.status === "review").length} OPEN</span>
</div>
{/* Fixed-height PR container — clips overflow, no layout shift */}
<div style={{ position: "relative", overflow: "hidden", flex: 1 }}>
{visiblePRs.map((pr, i) => (
<div key={`${pr.id}-${prOffset}`} style={{
...panel,
padding: "8px 10px",
marginBottom: 5,
animation: i === 0 ? "prSlideIn 0.4s cubic-bezier(0.16,1,0.3,1) both" : "none",
}}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 5, marginBottom: 5 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 9.5, fontWeight: 600, color: "#111", lineHeight: 1.3, marginBottom: 2 }}>{pr.title}</div>
<div style={{ fontSize: 7.5, fontFamily: "monospace", color: "rgba(0,0,0,0.32)" }}>{pr.branch} · {pr.agent}</div>
</div>
<StatusBadge status={pr.status} />
</div>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<div style={{ display: "flex", gap: 7 }}>
<span style={{ fontSize: 7.5, color: "#28a745", fontFamily: "monospace" }}>+{pr.additions}</span>
<span style={{ fontSize: 7.5, color: "#d73a49", fontFamily: "monospace" }}>-{pr.deletions}</span>
<div style={{ display: "flex", alignItems: "center", gap: 2 }}>
<MessageSquare style={{ width: 7, height: 7, color: "rgba(0,0,0,0.28)" }} />
<span style={{ fontSize: 7.5, color: "rgba(0,0,0,0.28)" }}>{pr.comments}</span>
</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 3 }}>
<Clock style={{ width: 7, height: 7, color: "rgba(0,0,0,0.22)" }} />
<span style={{ fontSize: 7.5, color: "rgba(0,0,0,0.28)", fontFamily: "monospace" }}>{pr.time}</span>
</div>
</div>
</div>
))}
</div>
{/* Heatmap — fixed height, no layout shift */}
<div style={{ ...panel, padding: "8px 10px", flexShrink: 0, height: 76, overflow: "hidden" }}>
<div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 6 }}>
<Terminal style={{ width: 9, height: 9, color: "rgba(0,0,0,0.33)" }} />
<span style={{ fontSize: 7.5, letterSpacing: "0.13em", textTransform: "uppercase", color: "rgba(0,0,0,0.33)", fontFamily: "monospace" }}>Commit Activity</span>
</div>
<div style={{ display: "flex", gap: 2, flexWrap: "wrap", maxWidth: 210, height: 30, overflow: "hidden" }}>
{activity.map((a, i) => <HeatCell key={i} level={a.level} animDelay={i * 18 + 300} />)}
</div>
<div style={{ display: "flex", alignItems: "center", gap: 3, marginTop: 4 }}>
<span style={{ fontSize: 7, color: "rgba(0,0,0,0.26)", fontFamily: "monospace" }}>Less</span>
{[0,1,2,3,4].map(l => (
<div key={l} style={{ width: 7, height: 7, borderRadius: 1.5, background: ["rgba(0,0,0,0.05)","rgba(0,0,0,0.15)","rgba(0,0,0,0.32)","rgba(0,0,0,0.55)","rgba(0,0,0,0.8)"][l] }} />
))}
<span style={{ fontSize: 7, color: "rgba(0,0,0,0.26)", fontFamily: "monospace" }}>More</span>
</div>
</div>
</div>
{/* Col 2 — Code Review — fixed height */}
<div style={{ display: "flex", flexDirection: "column", gap: 5, height: "100%", overflow: "hidden", ...anim(210) }}>
<div style={{ display: "flex", alignItems: "center", gap: 5, padding: "0 2px", flexShrink: 0 }}>
<Eye style={{ width: 10, height: 10, color: "rgba(0,0,0,0.38)" }} />
<span style={{ fontSize: 8.5, letterSpacing: "0.13em", textTransform: "uppercase", color: "rgba(0,0,0,0.38)", fontFamily: "monospace" }}>Code Review — #{ALL_PRS[reviewFileIdx % ALL_PRS.length].id}</span>
</div>
<div style={{ ...panel, flex: 1, padding: "9px 10px", overflow: "hidden", display: "flex", flexDirection: "column" }}>
{/* Header — fixed */}
<div style={{ marginBottom: 7, paddingBottom: 7, borderBottom: "1px solid rgba(0,0,0,0.05)", flexShrink: 0 }}>
<div style={{ fontSize: 9.5, fontWeight: 600, color: "#111", marginBottom: 2 }}>feat: parallel tool execution</div>
<div style={{ display: "flex", gap: 5 }}>
<span style={{ fontSize: 7.5, color: "#28a745", fontFamily: "monospace" }}>+142</span>
<span style={{ fontSize: 7.5, color: "#d73a49", fontFamily: "monospace" }}>-27</span>
<span style={{ fontSize: 7.5, color: "rgba(0,0,0,0.28)" }}>5 files</span>
</div>
</div>
{/* Progress bars — fixed height */}
<div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 9, flexShrink: 0 }}>
{ALL_REVIEW_FILES.map((f, i) => (
<div key={f.file} style={{ opacity: i === reviewFileIdx ? 1 : 0.55, transition: "opacity 0.4s ease" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
<span style={{ fontSize: 7.5, fontFamily: "monospace", color: i === reviewFileIdx ? "#111" : "rgba(0,0,0,0.42)", transition: "color 0.4s ease" }}>{f.file}</span>
<span style={{ fontSize: 7.5, fontFamily: "monospace", color: reviewFilePcts[i] > 70 ? "#28a745" : "#d73a49", transition: "color 0.4s ease", fontWeight: i === reviewFileIdx ? 700 : 400 }}>{Math.round(reviewFilePcts[i])}%</span>
</div>
<Bar pct={reviewFilePcts[i]} color={i === reviewFileIdx ? "#111" : "rgba(0,0,0,0.3)"} />
</div>
))}
</div>
{/* Review comments — fixed height, clips overflow, no scroll */}
<div style={{ borderTop: "1px solid rgba(0,0,0,0.05)", paddingTop: 7, flex: 1, overflow: "hidden" }}>
{ALL_REVIEW_LINES.slice(0, reviewLineIdx).slice(-5).map((item, i) => (
<ReviewLine key={`${reviewLineIdx}-${i}`} item={item} delay={0} />
))}
<div style={{ display: "flex", alignItems: "center", gap: 3, marginTop: 2 }}>
<Terminal style={{ width: 7, height: 7, color: "rgba(0,0,0,0.18)" }} />
<span style={{ display: "inline-block", width: 4, height: 9, background: cursor ? "rgba(0,0,0,0.38)" : "transparent", transition: "background 0.08s" }} />
</div>
</div>
</div>
</div>
{/* Col 3 — Commits + CI — fixed height */}
<div style={{ display: "flex", flexDirection: "column", gap: 5, height: "100%", overflow: "hidden", ...anim(260) }}>
<div style={{ display: "flex", alignItems: "center", gap: 5, padding: "0 2px", flexShrink: 0 }}>
<GitCommit style={{ width: 10, height: 10, color: "rgba(0,0,0,0.38)" }} />
<span style={{ fontSize: 8.5, letterSpacing: "0.13em", textTransform: "uppercase", color: "rgba(0,0,0,0.38)", fontFamily: "monospace" }}>Recent Commits</span>
</div>
<div style={{ ...panel, flexShrink: 0, overflow: "hidden" }}>
{COMMITS.slice(0, 4).map((c, i) => (
<div key={c.hash} style={{
padding: "7px 10px",
borderBottom: i < 3 ? "1px solid rgba(0,0,0,0.04)" : "none",
animation: mounted ? `fadeSlide 0.3s ease ${280 + i * 55}ms both` : "none",
}}>
<div style={{ fontSize: 8.5, fontWeight: 500, color: "#111", lineHeight: 1.35, marginBottom: 2 }}>{c.msg}</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<span style={{ fontSize: 7.5, fontFamily: "monospace", color: "#8250df" }}>{c.hash}</span>
<span style={{ fontSize: 7.5, color: "rgba(0,0,0,0.28)", fontFamily: "monospace" }}>{c.time}</span>
</div>
</div>
))}
</div>
<div style={{ display: "flex", alignItems: "center", gap: 5, padding: "2px 2px 0", flexShrink: 0 }}>
<Zap style={{ width: 10, height: 10, color: "rgba(0,0,0,0.38)" }} />
<span style={{ fontSize: 8.5, letterSpacing: "0.13em", textTransform: "uppercase", color: "rgba(0,0,0,0.38)", fontFamily: "monospace" }}>CI / Agents</span>
</div>
<div style={{ ...panel, flexShrink: 0, overflow: "hidden" }}>
{[
{ name: "researcher-agent", status: "passing", duration: "1m 32s" },
{ name: "analyst-agent", status: "running", duration: "0m 48s" },
{ name: "executor-agent", status: "passing", duration: "2m 11s" },
{ name: "monitor-agent", status: "running", duration: "0m 54s" },
].map((a, i) => (
<div key={a.name} style={{
display: "flex", alignItems: "center", justifyContent: "space-between",
padding: "6px 10px",
borderBottom: i < 3 ? "1px solid rgba(0,0,0,0.04)" : "none",
}}>
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
{a.status === "running"
? <div style={{ width: 8, height: 8, borderRadius: "50%", border: "1.5px solid rgba(0,0,0,0.5)", borderTopColor: "transparent", animation: "spin 0.9s linear infinite", flexShrink: 0 }} />
: <CheckCircle2 style={{ width: 8, height: 8, color: "#28a745", flexShrink: 0 }} />
}
<span style={{ fontSize: 8.5, fontFamily: "monospace", color: "#111" }}>{a.name}</span>
</div>
<span style={{ fontSize: 7.5, color: "rgba(0,0,0,0.28)", fontFamily: "monospace" }}>{a.duration}</span>
</div>
))}
</div>
</div>
</div>
</div>
<style>{`
@keyframes ping { 75%, 100% { transform: scale(2.2); opacity: 0; } }
@keyframes logIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeSlide { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: translateX(0); } }
@keyframes prSlideIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes spin { to { transform: rotate(360deg); } }
`}</style>
</div>
)
}