# Changelog and releases

> CHANGELOG.md is what an app's users read; one line per visible change, golo release cuts a version, and the app shows what is new after sign in.

Every app repo has a `CHANGELOG.md`. It is written for the app's users, not for developers. A release turns its `## Unreleased` section into a numbered version, and the app shows the new lines to each user once, after sign in. The client's side: [Releases and deploys](https://golocomotive.fr/docs/using/releases.md).

## The file

```md
# Changelog

Free text above the first section.

## Unreleased
- new: Export the orders as CSV.

## v2 2026-10-20
- fix: The total no longer counts cancelled orders.
- Search is faster.
```

- `## Unreleased` first, then `## v<n> YYYY-MM-DD`, newest first.
- One `- ` line per change, with an optional lowercase tag: `new:`, `fix:`, `change:`.
- The parser is strict. Any other line, an empty release, versions out of order, or a bad date fail with the line number. The format in full: [Changelog format](https://golocomotive.fr/docs/reference/changelog-format.md).

## The rule

- Every change a user can see gets one line under `## Unreleased`, in the same commit. Write it for the user, in the app's language. Never a commit message, never a file name.
- `golo gate` fails when `src/` (tests aside) or `schema.ts` changed since the last tag while `## Unreleased` is empty.
- A release is the client's decision. An agent never runs `golo release` unless its task asks for it.

## Commands

```sh
bunx golo release                    # Unreleased becomes v<n> <today>, commit "release v<n>", tag v<n>
bunx golo changelog --since v2 --json
```

`golo release` refuses an empty Unreleased, a dirty working tree, and an app that is never deployed (below). The version is a plain integer: easy to read, easy to compare.

## In the app

- At boot the server reads `CHANGELOG.md` into a system table, `_changelog`. Unreleased lines never ship. A second system table, `_seen`, keeps the last version each user saw. The records API never reaches either table.
- `GET /api/_/changelog[?since=<v>]` answers `{ version, seen, releases }`: the releases above `since`, else above the user's seen version. `POST /api/_/seen { version? }` stores it. Both need a signed in user.
- A user with no seen version gets the current one on the first call: a first visit sees nothing.
- `<AppRoot client={client}>` shows the what's new modal after sign in when releases wait: grouped by version, one "Got it" button that stores the version. English and French.

## Deploy and revert

A deploy ships a tag to production. A revert deploys the tag below the live one. An undeploy stops production and takes its address down; the data stays. The client does all three from the portal.

An app that must never ship (a demo on mock data) says so in its config:

```ts
export default {
    app: { name: 'Demo', color: '#0b7285', logo: null, lang: 'en' },
    deploy: false,
} satisfies LocoConfig
```

Then deploy, revert, release and `golo release` all refuse it, before any build, with `<app> is never deployed: its loco.config.ts says deploy: false`. Undeploy stays open.
