zudo-doc includes a changelog section for tracking release notes and version history. The changelog uses descending sidebar sort so the newest entries always appear at the top.

## Directory Structure

Changelog entries live in a dedicated content directory:

```
src/content/docs/
└── changelog/
    ├── index.mdx          # Category index page (sets desc sort order)
    ├── 0.2.0.mdx          # Newer entry (sidebar_position: 2)
    └── 0.1.0.mdx          # Older entry (sidebar_position: 1)
```

## Category Configuration

The `index.mdx` file configures descending sort order so newer entries appear first in the sidebar:

```mdx title="changelog/index.mdx"
---
title: Changelog
sidebar_position: 10
category_sort_order: "desc"
---
```

With `category_sort_order: "desc"`, entries with higher `sidebar_position` values appear first. This means newer versions naturally sort to the top.

## Adding a New Entry

To add a new changelog entry:

1. Create a new MDX file in `src/content/docs/changelog/` named after the version (e.g., `0.2.0.mdx`)
2. Set the `sidebar_position` to a value higher than the previous entry
3. Mirror the file in every configured additional-locale directory (for example, `src/content/docs-ja/changelog/` and `src/content/docs-de/changelog/`)

```mdx title="src/content/docs/changelog/0.2.0.mdx"
---
title: "0.2.0"
description: Short summary of this release.
sidebar_position: 2
---

Summary of changes in this release.

### Features

- Feature A
- Feature B

### Bug Fixes

- Fix for issue X
```

[Dynamic content unavailable in text export]

Use incrementing `sidebar_position` values for each new version. Combined with `category_sort_order: "desc"` in the category's `index.mdx`, this ensures the newest entry always appears at the top of the sidebar.



## Entry Format

Each changelog entry is a standard MDX file. zudo-doc recommends a
[Keep a Changelog](https://keepachangelog.com/)-style structure because those
entries can be converted into package-friendly `CHANGELOG.md` files:

- **Title**: The version number (e.g., `"0.2.0"`)
- **Description**: A brief summary of the release
- **Release date**: A `Released: YYYY-MM-DD` line near the top when the release date is known
- **Content sections**: grouped human-written notes such as Added, Changed, Deprecated, Removed, Fixed, Security, Breaking Changes, Features, and Bug Fixes

Write entries for humans. Do not dump raw git logs into changelog pages; group
notable user-facing changes into short bullets.

## Generated Package Changelogs

Projects can configure generated changelog outputs with the `changelogs` field.
Each item points at a source directory and an output file:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    changelogs: [
      {
        sourceDir: "src/content/docs/changelog",
        outputFile: "packages/zudo-doc/CHANGELOG.md",
        packageName: "@takazudo/zudo-doc",
      },
    ],
  }),
);
```

The MDX files remain the source of truth. The generated `CHANGELOG.md` is
overwritten from those pages and should not be edited by hand. zudo-doc strips
frontmatter, imports, exports, JSX tags/components, and MDX comments so the
output stays readable as CommonMark in `node_modules`.

Relative links inside entry bodies are also rewritten at emit time, so the
published artifact never carries a dead href. A same-directory link to
another entry's source file (for example, `[5.15.0](./5.15.0.mdx)`) becomes
an in-file anchor in the generated `CHANGELOG.md`, and any `#fragment` on
that link is dropped, since sub-heading text repeats across versions. Any
other relative `.md`/`.mdx` link — one that doesn't resolve to an emitted
entry — is unlinked instead: the label text stays, the href is removed.
Authors keep writing doc-site-correct relative links in MDX; nothing changes
on the docs site itself.

Multiple outputs can be configured by adding more items to the array. This is
useful when a monorepo wants separate package changelogs, each generated from
its own changelog source directory.

## Multiple Changelogs (Multi-Package Projects)

Use a nested changelog when a monorepo publishes several packages independently. The top-level page becomes a landing page, and each package gets its own nested category:

```
src/content/docs/changelog/
├── index.mdx
├── core/
│   └── index.mdx
└── cli/
    └── index.mdx
```

This produces `/docs/changelog`, `/docs/changelog/core`, and `/docs/changelog/cli`. The landing page lists the package categories and does **not** use `category_sort_order: "desc"`:

```mdx title="changelog/index.mdx"
---
title: Changelog
sidebar_position: 10
---

Release notes for every package in this repository.

<CategoryNav category="changelog" />
```

### One Page per Package (Default)

By default, the generator creates one `index.mdx` per package. Keep the complete package history on that page, with `## Unreleased` followed by version sections:

```mdx title="changelog/core/index.mdx"
---
title: Core
sidebar_position: 1
---

## Unreleased

### Added

- Work in progress.

## 1.0.0

Released: 2026-08-22

### Added

- Initial release.
```

### One File per Version

Use separate version files when you also emit a package `CHANGELOG.md`. In this variant, the package index sets descending order and renders its release files:

```
src/content/docs/changelog/core/
├── index.mdx
├── 1.1.0.mdx
└── 1.0.0.mdx
```

```mdx title="changelog/core/index.mdx"
---
title: Core
sidebar_position: 1
category_sort_order: "desc"
---

Release notes for the core package.

<CategoryNav category="changelog/core" />
```

Each package directory configured in `changelogs` must contain non-index, per-release MDX files. Set `sourceDir` to that package directory, never to the top-level `changelog/` landing directory:

```ts title="zfb.config.ts"
changelogs: [
  {
    sourceDir: "src/content/docs/changelog/core",
    outputFile: "packages/core/CHANGELOG.md",
    packageName: "@acme/core",
  },
  {
    sourceDir: "src/content/docs/changelog/cli",
    outputFile: "packages/cli/CHANGELOG.md",
    packageName: "@acme/cli",
  },
],
```

The build warns about the landing-directory pitfall when a configured `sourceDir` yields no release entries but contains subdirectories with MDX files.

[Dynamic content unavailable in text export]

Previous/next pagination runs across the entire `changelog` section, so it can cross from one package into another. On package boundary pages, set `pagination_prev: null` or `pagination_next: null` in frontmatter when that transition would be confusing.



### Generate the Layout

Pass a comma-separated package list when scaffolding a project:

```bash
pnpm create zudo-doc my-docs --changelog-packages core,cli
```

`--changelog-packages` implies `--changelog`. The generator creates the landing page and one package index for each slug, but it does not add a `changelogs` output configuration. To emit package `CHANGELOG.md` files, switch those packages to the per-version-file layout and configure one `changelogs` entry per package.

The version-bump skill keeps one lockstep root version and updates all packages by default, or only the packages you select. It adds a new version section to each selected single-page changelog, or a new per-version file to each selected package using the per-version-file layout. Independent per-package versioning is not supported.

## Version Bump Script

zudo-doc includes a `scripts/version-bump.sh` script that automates version management:

```bash
# Bump version and create changelog entry
./scripts/version-bump.sh 0.2.0

# Bump version, create changelog entry, and snapshot current docs
./scripts/version-bump.sh 1.0.0 --snapshot
```

The script performs the following steps:

1. Updates the `version` field in `package.json`
2. Creates a changelog entry MDX file in the primary and each configured additional-locale directory
3. Sets the correct `sidebar_position` automatically (incremented from existing entries)

### Doc Snapshots

When called with `--snapshot`, the script also archives the current documentation as a versioned snapshot before bumping. This integrates with zudo-doc's [versioning system](https://zudo-doc.takazudomodular.com/docs/guides/versioning):

1. Copies `src/content/docs/` to `src/content/docs-v{old}/`
2. Copies each configured additional-locale directory (for example, `src/content/docs-ja/` to `src/content/docs-v{old}-ja/` and `src/content/docs-de/` to `src/content/docs-v{old}-de/`)
3. Prints the version config entry to add to `src/config/settings.ts`

[Dynamic content unavailable in text export]

The `--snapshot` flag archives the **old** version's docs, not the new version. After the script runs, `src/content/docs/` represents the new version and the snapshot preserves the previous state.



## Version Bump Skill

For [Claude Code](https://claude.com/claude-code) users, zudo-doc includes a `/zudo-doc-version-bump` skill that orchestrates the entire release workflow. Instead of running the script manually, the skill handles everything end-to-end:

1. Analyzes commits since the last git tag and categorizes them (breaking, features, fixes, other)
2. Proposes a version bump type (major/minor/patch) based on the changes
3. Runs `version-bump.sh` to update `package.json` and create changelog entries
4. Fills in the changelog templates with actual commit details for the primary and each configured locale
5. Runs `pnpm b4push` to validate the build
6. Commits, pushes, and waits for CI
7. Creates a git tag and GitHub release
8. Guides you through npm publishing (or skips it for private packages)

```bash
# Run the skill in Claude Code
/zudo-doc-version-bump

# Or skip the proposal step by specifying the bump type
/zudo-doc-version-bump patch
```

[Dynamic content unavailable in text export]

The skill requires at least one `v*` tag to exist. If this is the first release, create the initial tag manually: `git tag v0.1.0 && git push --tags`.



## Header Navigation

For one changelog, use a flat `headerNav` item:

```ts
headerNav: [
  // ...other items
  { label: "Changelog", labelKey: "nav.changelog", path: "/docs/changelog", categoryMatch: "changelog" },
],
```

For multiple package changelogs, make the parent a dropdown. Keep `categoryMatch: "changelog"` on the parent and omit `categoryMatch` from its children:

```ts
headerNav: [
  // ...other items
  {
    label: "Changelog",
    path: "/docs/changelog",
    categoryMatch: "changelog",
    children: [
      { label: "Core", path: "/docs/changelog/core" },
      { label: "CLI", path: "/docs/changelog/cli" },
    ],
  },
],
```

Nested child active state is path-based, while `categoryMatch` scopes the sidebar by a single top-level directory. Two or more children that share a `categoryMatch` can all appear active in server-rendered HTML. A multi-segment value such as `"changelog/core"` never matches the top-level category and can leave the sidebar empty. The build reports a warning for either mistake. See [Header Navigation](https://zudo-doc.takazudomodular.com/docs/guides/header-navigation#properties-categorymatch-nested-children-under-one-category) for the general rule.

## i18n

Mirror changelog entries in every configured additional-locale directory (such as `src/content/docs-ja/changelog/` and `src/content/docs-de/changelog/`) to provide translated release notes. The `index.mdx` and directory structure should match the primary version.