DocsDevelopers

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.
  • ## 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.

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#

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:

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.

This page as markdown