The `changelog` type writes ordinary Markdown pages whose bodies are two MDC
components, rendered inside a layout of its own. All three are documented because
they are part of the public surface — a consumer can override any of them — not
because a page has to be written by hand.

## The `changelog` layout

Bound by the [`split` granularity](/reference/changelog/granularity) only; a flat
changelog is an ordinary docs page and renders in the docs shell.

It differs from `docs` in two ways, and both follow from what the pages are. The
**sidebar is the release list** rather than a prose tree — same navigation, same
collection, because a generated section is an ordinary collection and its pages
are the releases. And the **reading column is capped at a measure**: a release
note is prose, and prose set across the full window is unreadable. The contents
column stays, listing the groups.

Replace it with a file of your own at the same path:

```
app/layouts/changelog.vue
```

## ChangelogReleases

The overview's timeline: every release, newest first, filterable by what changed.
Written onto the section's index page.

| Prop       | Type  | Notes                                                              |
| :--------- | :---- | :----------------------------------------------------------------- |
| `releases` | array | Each with `version`, `date`, `to`, its `groups` and `contributors` |

The history opens on **four figures** — releases, changes, contributors, and the
day the last one shipped — each a number with a small labelled line under it, as
on the landing page — but laid out as a **four-column grid across the full
width** rather than a wrapping row, so they read as the header of the list under
them instead of as a caption with a hole beside it. The date is the only one that
is not a count, and it is dropped rather than dashed where the file dates
nothing. It answers what a list of rows cannot, which is how much there is. The
figures count **what is shown**, so a filtered page's numbers are about the
filtered page, with the total kept beside the first one — filter the history to
breaking changes and the contributor figure answers who has ever shipped one.

The **label carries the accent**, not the figure: `--primary` is the one colour a
consuming site sets to make the theme its own, and two short labelled lines are
where it costs nothing.

### Where the contributors come from

Not from the changelog. A release is a **range of commits** — everything from the
release before it up to itself — and who wrote them is in the repository's git
history, keyed by the tag the release was cut as. The authors of the changelog
_file_ are whoever ran the release tool, which is a different set of people and
the wrong answer given confidently, so it is never used.

Both spellings of a version are tried, so release-please's `include-v-in-tag`
needs no configuration: a changelog heading of `1.2.0` finds the tag `v1.2.0`. A
**merge commit** sets a release's boundary — release-please tags the merge its
release pull request produced — but credits nobody, because it introduces no
change of its own. Accounts GitHub itself marks as apps are left out, so the bot
that cuts the releases is not listed as having written them.

Three things leave a release with no contributors, and each is shown as an
**absent figure** rather than a nought, because a nought is a claim about the
project where an absent figure is a claim about this build:

- the artefact came from a **downloaded source** of another repository, which
  Content clones at `--depth 1` — a history in which every commit looks as though
  one person wrote it is wrong data rather than missing data. A downloaded source
  of the **repository being built** is the exception: a site that publishes its
  own tagged documentation reads the history from its checkout, which carries the
  same tags. Any remote of the checkout counts, and the build needs the full
  history and the tags — `fetch-depth: 0` in `actions/checkout`;
- the version was **never tagged** in this checkout, which is the ordinary state
  of a release between the moment its changelog entry is written and the moment
  its tag exists;
- there is **no git** to ask.

What travels onto the page is a **name and, where git carries one, a GitHub
handle** — never an email address. The address is what identifies a committer and
is what the build dedupes by, and it stops there: an address inside a commit
object is between a repository and whoever clones it, where the same address on a
prerendered, crawled and indexed page is something the person never agreed to.
The handle is what [`contributors.avatarUrl`](/reference/configuration) turns
into a picture; a contributor without one is drawn as an initial.

Below them every release is **one line in columns** — version, badge, what it
carried, who carried it, date — so the versions sit line under line and the
summaries start at the same
x. Under `sm` the summary drops to a second line; nothing scrolls sideways. The
current release says it is current with a badge beside the version — a badge is
about the version, so it sits with it — in a **fixed track that exists whether or
not a row fills it**, so the releases without one start their summary exactly where
the one with it does. The date sits at the far end beside the arrow: it is the
field a reader scans down rather than reads across.

Filtering **animates on both sides** — the chips that appear above the list and the
rows that move below it, at the same timings, so the two read as one gesture. Rows
fade and settle over 200 ms, a leaving row is taken
out of the flow so the ones under it close the gap while it goes, and the
browser's own FLIP moves the survivors. It is a `TransitionGroup`, not a motion
library — a fade and a nudge are not worth a runtime dependency in every site that
extends the layer — and every part of it is off under `prefers-reduced-motion`. A row names the **three largest
groups and counts the rest**: "Features 2 · Bug Fixes 2 · Documentation 1 · and 9
more". Three, **by count** rather than by the order the file wrote them, because
release-please's section order is fixed and means nothing. Nothing is dropped
silently, and a filtered row names what it was filtered by first.

Beside that summary the row shows **up to four faces and counts the rest**, in
the order the build put them — most of the release first. Faces rather than
names, because four names are a second line of prose competing with the summary
where four overlapping circles are read as "a few people" without being read at
all; the name is still there as each picture's alternative text, so a screen
reader hears the people and the row's link says who wrote the release it leads
to. They share the summary's column rather than taking a track of their own: a
fifth column would hold an empty gutter on every release whose contributors
could not be read.

The tone is **not** in the history. It stays where it distinguishes rather than
decorates: the filter menu, and the label a release page heads each group with.

A **mark in front of a name is drawn away** wherever a name is shown — the `⚠`
release-please prefixes its breaking block with, an emoji a hand-kept changelog
opens a section with. Leading only, so `C++ Support` survives. The stored name
stays whole: the filter matches on it, the anchor is slugified from it, and the
props carry it, which is why the mark is dropped in the drawing rather than in the
parser. See `changelogLabel`.

A release's `groups` carry a `name` and a `count` only — the entries are not
here, because each release has a page of its own and repeating its bullets would
put the whole history twice into the search index, `llms-full.txt` and the feed.

The filter chips are built from the names the file itself used. With nothing
selected the page shows **everything**, which is also what the server renders: a
filter is a reader's choice, so the page has to be complete before anyone makes
one — otherwise the crawler and the reader whose hydration has not landed are
each shown a filtered changelog nobody asked for.

## ChangelogGroup

One group of one release — "Features", "Bug Fixes", whatever the file said.

| Prop    | Type     | Notes                                        |
| :------ | :------- | :-------------------------------------------- |
| `name`  | `string` | The file's own heading, verbatim              |
| `count` | `number` | How many entries the group lists              |

The entries arrive in the **slot, as the Markdown they were written as** — so the
search indexes them, `llms-full.txt` carries them and the copy control hands a
model prose rather than a component call. Only the name and the count, which the
badge and the filters need as data, travel as props.

The name is a **label, not a headline**: set small, upper-case and in the group's
own tone, above a list that closes up under it. As a 20-pixel heading it claimed
the weight of a chapter for a word naming four bullet points, and six of them read
as six sections rather than as one release. It is still an `<h2>` — the outline,
the contents column and the anchor are unchanged, and only its setting says what
it is worth.

The colour is a **hint and never a meaning**: a name the hint list does not know
still gets a stable colour of its own, which is what makes the same kind of change
scannable down a page of forty releases in any language. The label takes the
tone's `text` pair rather than the `dot` fill, because a run of words and a
six-pixel dot do not clear contrast at the same lightness.

The **entries' own markers stay grey.** The colour is already stated in the label
directly above them; repeated on every line it stops being a signal. They are set
from `duxt.css`, not from this component, because they arrive as Markdown through
the slot.

## Overriding one

Same as any other component the layer ships: a file at the same path in the
consuming site wins.

```
app/components/content/ChangelogGroup.vue
```

The props above are the contract, and so is the layout's name. A rename here is a
breaking change of the layer.

## What a release page shows besides its groups

Nothing in the body. The day a release was cut and the **diff it was cut from**
travel as `date` and `compare` in the page's frontmatter, and are drawn where the
site already answers where a page came from: the provenance block under the
contents column, above "Edit this page".

The release date **replaces** the last-commit line rather than joining it — every
release of a changelog lives in one file, so its last commit dates all of them the
same day, which is at best the newest one's.

A release page therefore reads like every other Markdown page on the site, which
is the point: a release note is prose, not a dashboard.

## No heading of its own

Neither component opens on an `<h1>`. The docs shell draws the header —
breadcrumb, title, the copy control beside it — for every generated page whose
body opens on no heading, and a release page wants exactly that: a title and a
trail like any written page.