+
+
+
diff --git a/app/registry/blocks/changelog-timeline/index.ts b/app/registry/blocks/changelog-timeline/index.ts
new file mode 100644
index 0000000..3b4f4e6
--- /dev/null
+++ b/app/registry/blocks/changelog-timeline/index.ts
@@ -0,0 +1 @@
+export { default as ChangelogTimeline, type ChangelogTimelineItem, type ChangelogTimelineProps } from "./ChangelogTimeline.vue";
diff --git a/content/docs/2.components/12.changelog-timeline.md b/content/docs/2.components/12.changelog-timeline.md
new file mode 100644
index 0000000..6a3450b
--- /dev/null
+++ b/content/docs/2.components/12.changelog-timeline.md
@@ -0,0 +1,155 @@
+---
+title: ChangelogTimeline
+description: Render a release timeline from app-owned changelog data with optional rich body slots.
+category: content
+---
+
+::component-preview
+---
+name: ChangelogTimelineDemo
+---
+::
+
+## Installation
+
+```bash
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/changelog-timeline.json"
+```
+
+## Usage
+
+```vue
+
+
+
+
+
+```
+
+## App-Owned Release Data
+
+`ChangelogTimeline` owns the timeline layout, latest marker, body slot boundary, and lightweight presentational states. Your app owns release fetching, repository configuration, normalization, caching, refresh lifecycle, date formatting, markdown rendering, and analytics.
+
+The component intentionally does not import Nuxt MDC, parse markdown, call `useFetch`, know about `ungh.cc`, or require a GitHub release response shape. Pass app-shaped items with an already formatted `dateLabel`. Use the optional raw `date` only when you want the rendered `time` element to include a machine-readable `datetime` value.
+
+## Rich Release Notes
+
+Use the `item` slot when your app wants rich release notes. The slot receives `item`, `index`, and `latest`, so the consuming app can render MDC, prose components, custom HTML, or plain Vue markup without turning that renderer into a registry dependency.
+
+```vue
+
+
+
+
{{ item.body }}
+
+ This is the latest release.
+
+
+
+
+```
+
+## GitHub Releases Adapter
+
+Keep GitHub release fetching in app code. Map your release source into `ChangelogTimelineItem` before passing it to the component.
+
+| Source field | Timeline item field |
+|--------------|---------------------|
+| `tag` | `tag` and fallback key |
+| `name` or `tag` | `title` |
+| `publishedAt` | raw `date` plus app-formatted `dateLabel` |
+| `markdown` | `body` or slot-rendered rich content |
+
+This keeps the generated registry item independent from network clients, API providers, and markdown modules.
+
+## Examples
+
+### Default
+
+::component-preview
+---
+name: ChangelogTimelineDemo
+---
+::
+
+### Loading
+
+```vue
+
+```
+
+### Empty
+
+```vue
+
+```
+
+### Error With Retry
+
+```vue
+
+```
+
+## API Reference
+
+### Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `items` | `ChangelogTimelineItem[]` | `[]` | Release items supplied by your app. |
+| `loading` | `boolean` | `false` | Shows loading skeletons when no items are present. |
+| `error` | `boolean \| string` | `false` | Shows an error state. A string is rendered as supporting error copy. |
+| `latestLabel` | `string` | `"Latest release"` | Label shown for the latest item. |
+| `emptyTitle` | `string` | `"No releases yet"` | Empty state title. |
+| `emptyDescription` | `string` | `"Published releases will appear here as changelog entries."` | Empty state description. |
+| `errorTitle` | `string` | `"Could not load releases"` | Error state title. |
+| `errorDescription` | `string` | `"Check the release source or try loading the changelog again."` | Error state description used when `error` is `true`. |
+| `retryLabel` | `string` | `"Try again"` | Retry button label. |
+| `showRetry` | `boolean` | `false` | Shows a native retry button in the error state. |
+| `ariaLabel` | `string` | `"Changelog timeline"` | Accessible label for the timeline list. |
+| `class` | `HTMLAttributes["class"]` | — | Additional CSS classes for the root element. |
+
+### Item Type
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | `string` | Stable item id. Falls back to `tag` or title-derived key when omitted. |
+| `tag` | `string` | Optional release tag supplied by your app. |
+| `title` | `string` | Release title. |
+| `dateLabel` | `string` | App-formatted display date. |
+| `date` | `string` | Optional machine-readable date for the `datetime` attribute. |
+| `body` | `string` | Optional plain text fallback body. |
+| `latest` | `boolean` | Marks this item as latest. Defaults to the first item when no item sets `latest`. |
+
+### Slots
+
+| Slot | Props | Description |
+|------|-------|-------------|
+| `item` | `{ item, index, latest }` | Rich body renderer for each release item. Falls back to `item.body`. |
+
+### Emits
+
+| Event | Description |
+|-------|-------------|
+| `retry` | Emitted when the optional retry button is clicked. The app owns refresh behavior. |
diff --git a/scripts/registry-verify.ts b/scripts/registry-verify.ts
index 29340ea..5e5119f 100644
--- a/scripts/registry-verify.ts
+++ b/scripts/registry-verify.ts
@@ -123,6 +123,10 @@ const expectedItems: Record = {
dependencies: [],
registryDependencies: [],
},
+ "changelog-timeline": {
+ dependencies: [],
+ registryDependencies: [],
+ },
};
if (registryIndex) {