main moved from8983ec04to35ba64cawhile this branch sat open, taking the release from 98 commits to 135. Merged in and folded the new work into the notes. The largest addition is a new `## Before you update` section, because this batch carries changes that need the reader to DO something and they were not going to survive being buried in a Fixed bullet: * Linux users of the virtual Steam Deck pad must `usermod -aG punktfunk` and log back in, or it stops attaching — the capability moved off the `input` group (which every gamepad guide tells you to join) onto its own, because it can emulate arbitrary USB hardware. * Plugin UIs moved to their own origin on PORT+1, so a self-signed console needs the new port trusted once, and custom firewalls/proxies need it opened. * Saving a custom launch command re-confirms the console password, and add-ons may no longer set launch/pre-launch commands at all — a real break for any third-party add-on that populated them. * A fresh install now runs the plugin runner by default (upgrades untouched). * The Deck setup script used to leave the generated console password world-readable, so rotating it is worth a sentence. The library-sources work is written as GROUNDWORK, deliberately. All six built-in scanners still ship, still on by default, and nothing is removed — and none of the replacement add-ons are published yet, so the migration banner only appears as they arrive. Promising a user they can move Steam to an add-on today would be the v0.22.3 mistake again: notes describing a build nobody is getting. Two other honesty items. The Android HUD entry says outright that the stream did not get faster and the headline number only got smaller because it stopped counting the compositor's wait — otherwise every reader takes it for a speed-up. The Windows non-C: settings entry says plainly that nothing is recoverable, because the writes never reached disk, so there is no orphaned copy to restore and the reader has to re-enter their preferences once. `56adb470` (pad-audio WASAPI module path) is deliberately NOT a user-facing Fixed entry: verified it is not an ancestor of v0.24.0, so it repairs a Windows build break in code that has never shipped. It folds into the pad-audio feature. Same for `19f637ea`, which is CI-only. Under the hood gained the origin-isolation mechanism, the allowlist authorization gate that fails the build on an unclassified route, store claims and the v2 library.json shape, the registry auth work, the config-writer fallback, send pacing, and the vendored Deck WSI layer. The unverified list grew too: the origin split has not been in a real browser, the packaging default-on changes have had no installer run, and no launcher tile has ever been clicked. Re-verified after the merge, all green: lock diff versions-only 32/32 against origin/main, `cargo metadata --locked` resolves (35 members), `cargo fmt --all --check` clean in both workspaces, doc lazy-continuation scanner 0 hits over 521 files, notes body 0 internal-vocabulary hits above `## Under the hood`, Play notes still 494/500 by android.yml's own gate logic. Wire 2, C ABI 16, and the capability bytes are all unchanged from the bump commit — host_caps still has exactly one free bit (0x80). Play's "What's new" is left as it stands: at 494/500 there is no room, and the only Android-facing additions here (the stats-overlay measurement change and a certificate-strictness fix) are both worth less to a phone user than any line already in it.
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
- Write the notes. Add
docs/releases/vX.Y.Z.mdin the same commit (or PR) as the version bump. CopyTEMPLATE.mdand fill it in. This file is the single source of truth for the body. - Tag & push.
git tag -a vX.Y.Z … && git push origin vX.Y.Zfans 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. - Wait for green. Let every platform's CI finish and go green.
- Announce. Dispatch the
announceworkflow (.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-rctag is refused unlessallow_prerelease=true.
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, and Play reusing text for internal testers costs nothing.
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.)
- 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.
- 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.
- 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. - Be specific and honest — no vague "various improvements"; a reader should know exactly what changed.
- 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.
- All protocol / ABI / driver / embedder detail goes in ONE
## Under the hood (for developers)section at the very bottom — the only place internal names and version numbers belong, clearly optional. The old dense engineering style survives only there.
The short annotated-tag message stays separate and short (a headline + a paragraph); it is the tag object's message, not this file.