ci / docs-drift (pull_request) Successful in 48s
ci / docs-site (pull_request) Successful in 1m30s
ci / web (pull_request) Successful in 2m8s
apple / swift (pull_request) Successful in 2m16s
apple / distribute (pull_request) Skipped
installer-smoke / smoke (debian-13) (pull_request) Successful in 58s
apple / screenshots (pull_request) Skipped
installer-smoke / smoke (fedora-44) (pull_request) Successful in 43s
ci / bun-nix (pull_request) Successful in 30s
ci / rust-arm64 (pull_request) Successful in 3m6s
installer-smoke / smoke (arch) (pull_request) Successful in 1m34s
ci / rust (pull_request) Successful in 7m54s
android / android (pull_request) Successful in 8m7s
Phase 2 of the docs-and-onboarding overhaul (items 1-partial, 2 and 4 of the handoff): install.sh: --uninstall reverses step 1 + step 6 per family (user units off first, only the punktfunk packages actually installed, then the repo; config/groups/firewall stay, as /docs/uninstall states) — smoke-tested as a new installer-smoke step on all three families. The end-of-run check now catches the two NVIDIA silent failures on every family: no driver at all, and a module the kernel refused to load (Secure Boot) via an nvidia-smi probe pointing at the troubleshooting anchor; the Fedora ffmpeg-libs/NVENC warning folds into the same block. check-docs-drift.sh gate 7: the manual 16-file os-release matrix PR #345 was verified with, committed — every family's detection, its install line, its removal line and the four unsupported pointers run through the real script under --dry-run on every push (docs-drift's container gains curl, the script's own prerequisite). Screenshots (RFC: "screenshots over prose"): four console shots captured from the same storybook-fixture pipeline web-screenshots.yml runs — login and the armed Pairing page into quickstart.md, the Approve dialog (access level + expiry + guest fast-path) into pairing.md replacing the prose that described it, live status into web-console.md. Files under docs-site/public/img/, dark-theme, bundled+preloaded by the docs build (verified served). Still missing: a client host-list shot — linux-client-screenshots run 19546 built it, but its artifact isn't API-downloadable; add it when a browser session can fetch the zip. WP5 rider: the release-flow docs-freshness step now includes the website content look-over. NOT flipped: installer stays preview — the handoff gates the default flip on real-box mileage (Bazzite above all), which a Mac can't provide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
133 lines
8.7 KiB
Markdown
133 lines
8.7 KiB
Markdown
# Release notes
|
|
|
|
One file per **stable** release: `docs/releases/vX.Y.Z.md`. Its contents become the Gitea release
|
|
body **verbatim** and are the source of the Discord `#releases` announcement.
|
|
|
|
## Why this exists
|
|
|
|
Releases used to be created by CI with an **empty body**; the notes were pasted in by hand
|
|
afterward. That left a window where the release — and anything announcing it — carried no notes.
|
|
Now the notes are authored **before the tag is pushed**, as part of the version bump, so the
|
|
release is born complete and the announcement always has something to say.
|
|
|
|
## The flow
|
|
|
|
1. **Write the notes.** Add `docs/releases/vX.Y.Z.md` in the same commit (or PR) as the version
|
|
bump. Copy `TEMPLATE.md` and fill it in. This file is the single source of truth for the body.
|
|
**Docs freshness, while you have the diff in front of you:** every user-facing fact the release
|
|
changes has its docs-site page updated (CONTRIBUTING.md "Where facts live" — `docs-drift` in CI
|
|
catches renamed knobs and dead links, not a stale sentence). If an install command, repo URL or
|
|
port changed, `data/platforms.json` changed with it — then run `bun run sync-platforms` in
|
|
punktfunk-website and commit, because its download page vendors that file and only refreshes
|
|
when someone does. Same pass for the website itself: does the landing page still describe what
|
|
this release ships (features, platforms, the blog post the CMS expects per release)?
|
|
2. **Tag & push.** `git tag -a vX.Y.Z … && git push origin vX.Y.Z` fans out to the build
|
|
workflows. Whichever one wins the create race seeds the release body from this file
|
|
(`scripts/ci/gitea-release.sh` → `ensure_release`, and its PowerShell twin). The release page
|
|
shows the notes immediately.
|
|
3. **Wait for green.** Let every platform's CI finish and go green.
|
|
4. **Announce.** Dispatch the `announce` workflow (`.gitea/workflows/announce.yml`) with the tag.
|
|
It re-asserts this file over the live release (so any late edit wins) and posts an embed to
|
|
Discord `#releases`. Pressing "go" is the quality gate — a half-built release is never
|
|
announced. Stable-only; a `-rc` tag is refused unless `allow_prerelease=true`.
|
|
|
|
**If a platform's run never appears, do not re-run the PR run — it cannot publish.** A re-run
|
|
replays the original event (`pull_request`), and android's publish steps are gated on a `push`, so
|
|
they stay skipped no matter how often you press it. Merging two PRs seconds apart can leave the
|
|
older merge sha with **no run at all** — Gitea attributes the window's runs to the newer head
|
|
(2026-08-14: `1e5dca4c` lost its run to `b5cace3a`, 12 s later), which is how an android change
|
|
reaches main having never been built. Recover it by dispatching `android.yml` on that ref with
|
|
**`publish=true`**; that is the only manual path reaching the registry and Play, and a plain
|
|
dispatch stays build-only so a stray click can't ship to testers. Check for the gap by matching
|
|
your own merge sha in the run list — "CI ran" is not the same as "your commit ran".
|
|
|
|
Editing the notes after the tag is fine: update this file, then re-run step 4 (or PATCH the body
|
|
via the API) — the announce step always re-syncs from the file, so the file stays authoritative
|
|
even across a tag re-point.
|
|
|
|
Canary / `-rc` builds have **no** file here on purpose: they get no curated body and are not
|
|
announced.
|
|
|
|
## Google Play "What's new": `whatsnew/vX.Y.Z.txt`
|
|
|
|
Play shows its own release notes on the Play Store listing and in the Play Store app, and caps
|
|
them at **500 characters per language** — the `vX.Y.Z.md` body is two orders of magnitude too
|
|
long, so it gets its own short file: `docs/releases/whatsnew/vX.Y.Z.txt`.
|
|
|
|
Write it for a **phone/TV user**, not a host operator: only what changed in the Android app is
|
|
worth their 500 characters. Plain text (Play renders no markdown), one `•` bullet per line, same
|
|
voice rules as below. Copy `whatsnew/TEMPLATE.txt`.
|
|
|
|
**A `vX.Y.Z` tag without this file fails the android job before it builds.** This is a hard gate,
|
|
not a warning, because the failure it prevents is silent: when the file is missing Play does not
|
|
show an empty "What's new" — it **carries the previous release's text onto the new version**, so
|
|
the store listing describes a build nobody is getting, and nothing surfaces that but reading the
|
|
listing. It is the same shape as the v0.22.3 notes announcing a feature that release never
|
|
contained. The gate also rejects a file byte-identical to another release's, which is that bug
|
|
reached by copy-paste instead of by omission.
|
|
|
|
The gate runs first in the job, so a miss costs a second and leaves nothing half-published —
|
|
no build, no assets on the Gitea release, nothing on Play. Two more checks sit downstream:
|
|
`play-upload.py` refuses text over the 500-char cap (printing the real count) before it uploads,
|
|
because the API only rejects oversized notes at commit, by which point the AAB is already on Play.
|
|
|
|
Canary is exempt: it has no curated notes; open-testing users see the previous release's text on
|
|
a canary, which is cosmetic and cheaper than gating every main push on a notes file.
|
|
|
|
Same freeze rule as the notes: once the tag exists, this file is the record of what that
|
|
versionCode shipped.
|
|
|
|
## Voice & format
|
|
|
|
**Write for the people who USE Punktfunk to stream their games and desktops — not for the people who
|
|
build it.** A non-engineer should finish knowing what's new and whether it affects them; an engineer
|
|
should never be confused or forced to decode internals. (See any recent `vX.Y.Z.md` for the target.)
|
|
|
|
1. **Lead with the benefit.** Each entry = what the user can now *do*, what now *works*, or what
|
|
stopped *going wrong* — in their words. Implementation is not the story.
|
|
2. **No internal vocabulary in the body.** No protocol/message names, code type names, hex codes or
|
|
hardware IDs, crate/component names, or API symbols. Translate any essential detail to plain
|
|
language. Name things users recognize (iPad, Apple Pencil, Steam Deck, Android TV, the Windows
|
|
sign-in screen) — not subsystems.
|
|
3. **Group as New / Improved / Fixed**, each a bold one-line lead-in + a tight plain explanation.
|
|
Skimmable. The lead-in text before the first `##` is what the Discord announcement shows, so make
|
|
it a real, plain-language summary.
|
|
4. **Be specific and honest** — no vague "various improvements"; a reader should know exactly what
|
|
changed.
|
|
5. **Compatibility line up top, in plain terms:** can they update one side at a time? does their
|
|
existing setup keep working? No version numbers in the lead.
|
|
6. **No protocol / ABI / driver / embedder detail in this file at all.** It goes in the root
|
|
`CHANGELOG.md` (see below), and the notes carry a single short `## For developers` section
|
|
linking there. Nothing else in `vX.Y.Z.md` may use an internal name.
|
|
7. **Open with a `## TL;DR`** — three to six bullets naming only what most readers would be sorry to
|
|
miss, each one line. A large release is exactly where a reader gives up, and the TL;DR is what
|
|
they read instead of giving up. If something needs the reader to *act*, it belongs here and in
|
|
`## Before you update`, not buried in `## Fixed`.
|
|
|
|
## The technical half: root `CHANGELOG.md`
|
|
|
|
**Why it is separate.** Through v0.24.0 the engineering detail lived in an `## Under the hood (for
|
|
developers)` section at the bottom of each release's notes. That worked while releases were small.
|
|
It stopped working: v0.25.0 is 300+ commits, and the section had grown long enough to bury the
|
|
user-facing half it was appended to — the exact failure the voice rules exist to prevent. The two
|
|
audiences also want different shapes. A user reads one release and wants prose; an embedder wants to
|
|
diff *across* releases and see when the ABI moved, which is a table, not a paragraph.
|
|
|
|
So: `vX.Y.Z.md` is for people who use Punktfunk, `CHANGELOG.md` is for people who build against it,
|
|
and neither has to compromise for the other.
|
|
|
|
**Format.** Newest release first, one `## vX.Y.Z` section each. Lead with a version table (wire
|
|
protocol, C ABI, driver protocol, gamepad channel — every row, marked *unchanged* where it did not
|
|
move, because "unchanged" is the answer an embedder most often needs). Then breaking changes, then
|
|
whatever else matters: capability bits, new environment variables, wire additions, workspace
|
|
members. Internal names are the point here — use them.
|
|
|
|
**Linking.** The notes link to the file **at the tag**, not at `main`:
|
|
`https://git.unom.io/unom/punktfunk/src/tag/vX.Y.Z/CHANGELOG.md`. A release's notes are frozen; a
|
|
link to `main` would silently start describing a later release.
|
|
|
|
**Same freeze rule.** Add the release's section in the version-bump commit, alongside the notes.
|
|
|
|
The short annotated-**tag** message stays separate and short (a headline + a paragraph); it is the
|
|
tag object's message, not this file.
|