
Release changelog on a timeline: each entry opens with a mono version and date, a Latest badge on the newest, a summary line, and New, Improved or Fixed badges against each change.
blocks/feed/changelog/index.tsx
import {
Timeline,
TimelineContent,
TimelineDescription,
TimelineIndicator,
TimelineItem,
TimelineLabel,
TimelineTitle,
} from "@/components/editorial/timeline"
import { Badge } from "@/components/ui/badge"
// The copy: change text here, not in the markup.
const COPY = {
title: "Changelog",
body: "Every release, newest first.",
latest: "Latest",
kinds: { new: "New", improved: "Improved", fixed: "Fixed" },
} as const
type Kind = keyof typeof COPY.kinds
const RELEASES: {
version: string
date: string
summary: string
changes: { kind: Kind; text: string }[]
}[] = [
{
version: "2.4.0",
date: "24 October 2026",
summary: "Charts in both modes and a comparison table for pricing.",
changes: [
{
kind: "new",
text: "pricing-table-01, the plan comparison table as its own block.",
},
{
kind: "improved",
text: "Every chart block sets axis ink, so labels show on recharts 3.8.",
},
{
kind: "fixed",
text: "docs-nav-01 seam back on the left at every width.",
},
],
},
{
version: "2.3.2",
date: "17 October 2026",
summary: "Demos at realistic sizes.",
changes: [
{
kind: "improved",
text: "Loaders at 48 with ground around them; nimbus at 80.",
},
{
kind: "fixed",
text: "Eclipse void covers the paper rim in dark mode.",
},
],
},
{
version: "2.3.1",
date: "10 October 2026",
summary: "Milestones ships.",
changes: [
{ kind: "new", text: "Four milestones blocks, two of them free." },
{ kind: "fixed", text: "cta-02 headline holds one line to 390." },
],
},
]
const KIND_VARIANT: Record<Kind, "default" | "secondary" | "outline"> = {
new: "default",
improved: "secondary",
fixed: "outline",
}
// A version heads each entry; the body line leads, so --timeline-lh is 1.5rem.
export default function Changelog() {
return (
<section className="mx-auto w-full max-w-2xl">
<header className="mb-8">
<h2 className="text-xl font-semibold tracking-tight">{COPY.title}</h2>
<p className="mt-1 text-sm text-muted-foreground">{COPY.body}</p>
</header>
<Timeline className="[--timeline-lh:1.5rem]">
{RELEASES.map((r, i) => (
<TimelineItem key={r.version}>
<TimelineIndicator />
<TimelineContent className="pb-10">
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<TimelineTitle className="font-mono text-base leading-6">
v{r.version}
</TimelineTitle>
<TimelineLabel className="text-sm/6">{r.date}</TimelineLabel>
{i === 0 && <Badge variant="secondary">{COPY.latest}</Badge>}
</div>
<TimelineDescription className="text-base text-foreground">
{r.summary}
</TimelineDescription>
<ul className="mt-4 space-y-2">
{r.changes.map((c) => (
<li key={c.text} className="flex items-start gap-3 text-sm">
<Badge
variant={KIND_VARIANT[c.kind]}
className="mt-px w-20 shrink-0 justify-center"
>
{COPY.kinds[c.kind]}
</Badge>
<span className="text-muted-foreground">{c.text}</span>
</li>
))}
</ul>
</TimelineContent>
</TimelineItem>
))}
</Timeline>
</section>
)
}