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.
The file#
# 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.
## Unreleasedfirst, 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.
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 gatefails whensrc/(tests aside) orschema.tschanged since the last tag while## Unreleasedis empty.- A release is the client's decision. An agent never runs
golo releaseunless its task asks for it.
Commands#
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.mdinto 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 abovesince, 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:
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.