What's New feed
The Console landing page (#/console, the "Welcome to Atlas" view) shows a compact,
collapsible What's New section: the newest user-facing features, in plain
language, newest-first, each with a link to its PR/ADR and — where the UI is involved
— a small step-by-step tutorial and a "Try it" deep link. It is bilingual (DE/EN)
with a per-visitor toggle.
How it works
CHANGELOG.md ──┐
├─► go run ./scripts/whats-new ──► api/web/whats-new.json ──► app.js renderWhatsNew()
overrides/*.json ┘
CHANGELOG.md is the source of truth. The generator reads its version sections and
Added / Changed / Fixed bullets and derives each entry's structure: a stable
id (the title slug), the headline, the version/date, and a link to the ADR or PR the
bullet names. New CHANGELOG entries appear automatically.
overrides/ adds the human polish. One file per entry, named <id>.json, supplying the
layman-friendly, bilingual summary, tags, an optional tutorial, and an optional
"Try it" route — none of which belong in the developer-facing CHANGELOG. It can also
hidden: true a purely internal/API bullet so it stays out of the user list.
api/web/whats-new.json is generated and committed. It is served straight off
the embedded FS (//go:embed web in api/server.go), so the web UI stays buildless
(ADR-0012): the generator runs only at authoring time, never at runtime.
- The generator is Go (
scripts/whats-new, logic in feed/), and that is the
whole of why (ADR-0375): the feed was the only reason go build,
go vet, the race job and the docs job needed Node. Node stays where it is the
technology — the browser end-to-end suite and the screenshot capture — and left the
four places where it was incidental. The output is byte-for-byte what the script
produced, which is not a nicety: CI regenerates the feed to check the commit is
current, so anything else would fail that check on every run.
Adding or polishing an entry
-
Write the change in CHANGELOG.md as usual.
-
Run the generator once to see the derived id:
go run ./scripts/whats-new # or: make whats-new
Look up the entry's id in api/web/whats-new.json.
-
(Optional but recommended for anything a user should notice) add an override in
overrides/<id>.json — copy the id, don't retype it. The id is the
headline slugified and cut to 60 characters, so a hand-typed key is easy to get one
character wrong, and a key that matches nothing is not an error the entry shows:
it renders from the CHANGELOG's own wording, in English, in both languages. The
generator refuses an unmatched key for exactly that reason (below). Every field is
optional:
The file is the entry — its name carries the id, so the object holds only fields:
{
"tags": ["Organization", "Secrets"],
"summary": { "en": "…one or two plain sentences…", "de": "…dito auf Deutsch…" },
"tutorial": { "en": ["Step 1", "Step 2"], "de": ["Schritt 1", "Schritt 2"] },
"try": { "label": { "en": "Open Organization", "de": "Organisation öffnen" },
"route": "#/console/org" },
"hidden": false
}
summary / title / try.label: either a { en, de } object or a bare string
(used for both languages). de falls back to en.
try.route must start with a known hash route: #/console, #/modeler,
#/operations, or #/tasks (the UI ignores anything else).
link: only needed to override the auto-derived PR/ADR link.
-
Re-run make whats-new and commit the regenerated api/web/whats-new.json.
The only entries shown are the newest MaxEntries (see feed/feed.go) after hidden ones
are dropped.
What keeps it honest
Nothing regenerates the feed at build or run time, so two checks stand in for that:
-
CI fails on a stale feed. The check job re-runs the generator and diffs
api/web/whats-new.json; a CHANGELOG entry committed without re-running
make whats-new turns the build red with the missing diff printed. This is why
generatedAt is derived from the newest dated CHANGELOG section rather than the
wall clock — a today-stamp would make every CI run differ from the commit.
-
The generator refuses an override key that matches no bullet. Such a key does
nothing — the entry still renders, from the CHANGELOG headline, with German falling
back to English — and that is indistinguishable from having written no override at
all. Keys are curated by hand, so an unmatched one is always a typo or a headline
that was reworded out from under it; the generator exits non-zero and names it.
-
api/whatsnew_test.go guards the committed JSON: valid, non-empty, required
fields present, newest-first, and no summary that starts with punctuation (the
signature of a generator parse artifact rather than real prose).
-
Git will not merge the feed for you (.gitattributes: -merge). Two branches
that each add a changelog entry each regenerate the feed, and a textual merge of the
two results is not a function of the merged CHANGELOG — it lands clean, silently
wrong (an entry duplicated, or one past MAX_ENTRIES left in), and main goes red on
its own push rather than on either branch. So the merge conflicts instead.
The overrides used to have the same edge for a different reason: one JSON object,
every branch adding its key at the top, and git interleaving two insertions into a
single wrong entry. They are one file per entry now, so two branches adding two
entries touch two files and merge cleanly. Two branches editing the same entry still
conflict — in a small file, where that is the honest answer.
If you hit that conflict, do not pick a side. Take the merged CHANGELOG.md, then:
git checkout --ours api/web/whats-new.json # any side; it is about to be overwritten
make whats-new
git add api/web/whats-new.json
This covers merges git performs. GitHub's merge button ignores the attribute — the
same merge that conflicts locally lands clean there — so it is not prevention.
-
.github/workflows/whats-new-sync.yml repairs main. On every push to main it
regenerates the feed and pushes the correction if the committed one differs. That is
the backstop for the merge-button path; it is not a licence to skip make whats-new on
a branch, which still fails CI. Preventing the bad merge outright takes branch
protection's "require branches to be up to date before merging" — a repository
setting, not something a file in the tree can do.
Neither check can tell that an entry reads well, or that a user-facing change has
an override at all. That part stays a human step — step 3 above.
The other thing a changelog bullet feeds
The Console landing page carries a second bilingual section below this one: the
key features tile (api/web/key-features.json),
which says what Atlas is rather than what changed. It is hand-written, not generated
— but it reads the same source indirectly, through a reviewedThrough marker naming the
newest ### Added bullet somebody has held against it. go test ./api fails while
bullets sit above that marker, so a feature cannot land without the question being asked.
Most bullets change nothing in the tile; moving the marker records that they were
considered.