Posting an announcement¶
Announcements are stored as a JSON file in the repo and served via
GET /api/v1/announcements (+ /{slug} for detail). There is no admin UI —
posting is a code change committed by a developer.
Steps¶
- Open
app/data/announcements.json. - Prepend a new object to the
announcementsarray (newest first):
{
"id": "YYYY-MM-DD-short-slug",
"slug": "short-slug",
"title": "Headline shown on the card and detail page",
"summary": "1–2 sentence preview shown on the card and in OG/Twitter meta.",
"body_md": "## Markdown body\n\nGFM is supported (tables, lists, code).",
"category": "model",
"published_at": "YYYY-MM-DDT00:00:00Z",
"tags": ["anthropic", "claude"],
"is_published": true
}
- Commit + deploy the backend (
docker/<env>compose up). - Wait up to 5 minutes for the in-memory cache to expire, or restart the backend process to pick the file up immediately.
Field rules¶
category— one ofmodel,feature,announcement. Drives the colored badge on the card.slug— URL-safe, kebab-case. Becomes/announcements/{slug}. Must be unique across all entries.published_at— ISO 8601 (UTC). Drives ordering (newest first) and the 30-day "What's new" landing strip auto-hide.is_published: false— keeps the entry in the file but hides it from the list, detail, RSS, and landing strip. Use for drafts.
Where it shows up¶
| Surface | Source |
|---|---|
/[locale]/announcements (list) |
GET /api/v1/announcements |
/[locale]/announcements/{slug} (detail) |
GET /api/v1/announcements/{slug} |
/[locale]/announcements/feed.xml (Atom) |
reuses the list endpoint |
| Landing-page "What's new" strip | latest entry, hidden if older than 30 days |
| Footer link | static — always points to the list |
When this workflow is no longer enough¶
Reach for an admin UI + Mongo only if any of:
- Posting frequency exceeds ~5 entries/month and devs are the bottleneck.
- Non-developers need to post or schedule entries.
- You need per-post analytics (views, click-throughs).
Until then: a PR per post is faster than building an admin panel.