| name | subwave-desktop-release |
| description | Cut and publish a release of the SUB/WAVE desktop player — version bump, then macOS DMGs (Apple Silicon + Intel), a Windows zip and a Linux tarball built by CI via scripts/make-release.sh, GitHub release, and announcement drafts. Use this whenever the user asks to release, ship, publish, or tag a new version of the desktop app, build a dmg/installer/windows/linux build, upload release artifacts, or draft a release announcement — even if they only mention one piece (e.g. "make a new dmg" or "bump the version and ship it"). |
SUB/WAVE Desktop release
This repo already carries the release machinery; this skill is the judgment
around it — preflight, versioning, publishing, verification, and the
announcement. The heavy lifting is one script (run from the repo root):
./scripts/make-release.sh
./scripts/make-release.sh --publish
The script builds nothing. All four artifacts come from CI, triggered by
release: published. The script reads the version from app.zon, refuses to
run unless main is clean and in sync, refuses to reuse an existing tag, and
refuses to publish unless ci.yml is already green on this exact commit
across every leg (Linux, macOS on both CPU flavors, Windows) — v0.2.0
shipped half-populated because nothing checked that. Then it creates the
release and follows the three runs (the macOS one is a two-leg matrix) until
every asset is attached, failing if fewer than four arrive.
Useful flags: --notes-file <path> (instead of --generate-notes) and
--skip-ci-check for emergencies.
Because nothing is built locally, a release can be cut from any machine — it
no longer needs a Mac.
Workflow
1. Preflight
git status — the tag must point at what ships. Commit (or get the user to
decide about) anything pending in src/, app.zon, or scripts/ before
publishing. Leave .claude/, design-reference/, and dist/ alone; they're
local by design.
./scripts/apply-sdk-patches.sh — the installed @native-sdk/cli loses the
local patch on EVERY npm upgrade, silently. The script is idempotent and
the release script also runs it, but checking first gives a clearer error.
As of SDK 0.6.0 there is exactly ONE patch left, the Linux fractional-HiDPI
fix (vercel-labs/native#156); its symptom is pixelated text on a
fractional-scale Linux display, and integer scales look fine, so it hides on
a second machine. Details: docs/sdk-notes.md.
- Two SDK version pins must agree, and a mismatch is a release-stopper:
native-sdk-version in .github/actions/setup-native (what CI installs)
and patch_sdk_version in scripts/apply-sdk-patches.sh (what the patch
was generated against). The patch script reads the installed package's
version and refuses to run on anything else. On the 0.6.0 upgrade the CI pin
was left at 0.5.3 and every leg died several steps later on markup the older
SDK could not parse — that gate exists so the next one fails at setup with
both versions named.
- If the SDK version changed since the last release (
native --version vs
docs/sdk-notes.md), run native test early and check the upstream issues —
a fix may have shipped that lets a patch retire. This is not theoretical:
0.6.0 retired two of the three patches at once.
2. Version
.version in app.zon is the single source of truth — the script names the
artifacts and the tag from it. Bump it (patch for fixes, minor for features)
and commit the bump; a release from an unbumped version will try to clobber
the previous tag's assets, which is only right for re-cutting a botched build.
3. Build and publish
./scripts/make-release.sh --publish
- Takes ~10-15 minutes, nearly all of it waiting on the runners
(Windows is by far the slowest). The script prints each leg's status as it
goes and exits non-zero if fewer than four assets land.
- CI signs macOS ad-hoc. There is no
SIGN_IDENTITY path any more,
because signing happens on a runner that holds no certificate. Giving CI a
real Developer ID means putting a .p12 plus an App Store Connect key into
repository secrets and adding keychain setup to release-macos.yml first.
Until that exists, never describe a DMG as notarized.
--publish uses --generate-notes. For a release users will actually
read, replace them: gh release edit vX.Y.Z --notes-file … with the shape
used by v0.1.0 — what's new (concrete, feature-level), a download table
with per-platform caveats, and build-from-source including the SDK patch
step. Keep the honest flags: macOS ad-hoc = "right-click → Open on first
launch"; Linux = needs glibc 2.39+ and GTK4, older distros build from
source. The old Windows caveat ("cross-compiled, not yet tested on real
hardware") is retired — release-windows.yml builds AND launches the app on
a real Windows runner. Don't reinstate it, but don't overclaim either: CI
launching the binary is not the same as a person listening on it.
4. Verify before telling the user it's done
gh release view vX.Y.Z --json assets --jq '.assets[].name'
gh run list --workflow release-linux.yml --limit 1
All four assets must be listed — both DMGs (Apple Silicon and -intel),
the Windows zip, and the Linux tarball that CI attaches a few minutes later. For more than a smoke: mount the DMG
(hdiutil attach … -nobrowse), launch the app binary inside, detach. The
running dev copy of the app is unaffected — but if one is running from
zig-out/, rebuilding replaced its binary on disk; relaunch it after.
5. Announce (when asked)
Download URLs follow this pattern:
https://github.com/getsubwave/subwave-desktop/releases/download/vX.Y.Z/SUBWAVE-Player-X.Y.Z.dmg
https://github.com/getsubwave/subwave-desktop/releases/download/vX.Y.Z/SUBWAVE-Player-X.Y.Z-intel.dmg
https://github.com/getsubwave/subwave-desktop/releases/download/vX.Y.Z/SUBWAVE-Player-X.Y.Z-windows-x64.zip
https://github.com/getsubwave/subwave-desktop/releases/download/vX.Y.Z/SUBWAVE-Player-X.Y.Z-linux-x64.tar.gz
(The plain .dmg is Apple Silicon; -intel.dmg is for Intel Macs — say which
is which when announcing, most people don't know their CPU by name.)
(The repo moved to the getsubwave org; perminder-klair/subwave-desktop
URLs still redirect but don't hand those out.)
For Discord/social drafts, read references/announcement-voice.md — the user
has a specific humanized voice for these (first person, concrete numbers, no
marketing gloss) and approved examples live there.
Facts that keep biting
-Dcpu=baseline on every release build is load bearing. A plain
native build bakes the CI runner's CPU features into the binary; v0.2.1's
Windows exe carried the runner's AVX-512 and died on an illegal instruction
at startup on most listeners' machines ("app does not load", no dialog).
The smoke test cannot catch it — it runs on the CPU that built the binary.
scripts/audit-cpu-baseline.sh disassembles each shipped x86-64 artifact
in CI and fails the leg if any AVX register reference appears.
- The SDK patch vanishes on upgrade — see preflight. This has already
happened (0.5.2 → 0.5.3 wiped the patches mid-day, back when there were
two).
- Close-to-hide is
app.zon, not a patch, and it is per-target. SDK 0.6.0
made it a manifest field, and it is validated against the BUILD TARGET at
comptime: macOS allows "hide", Windows allows it with the "tray"
capability, Linux refuses it outright (the GTK host has no tray, so a hidden
window would be stranded). app.zon has no per-platform scoping, so it ships
the Linux-safe "quit" and scripts/set-close-policy.sh hide flips it on
the macOS and Windows legs of ci.yml and both release workflows. Keep the
.close_policy = "…" shape in app.zon intact — the script asserts on it
and fails rather than silently shipping the wrong close behavior. Consequence
for announcements: the window keeps playing when closed on macOS and Windows,
and QUITS on Linux. Say so; it reads as a bug otherwise.
- A stale instance can hold the binary before a build. On macOS/Windows the
app survives its window closing, so it may be running invisibly. Kill it by
exact name —
pgrep -x subwave-desktop then kill those pids. Avoid
pkill -f zig-out/bin/subwave-desktop: the pattern matches the killing
shell's own command line and takes the tool session down with it.
- All four artifacts build NATIVELY on their own runner, Windows included
(
windows-latest). There is no cross-compile step any more — an older note
here described building the Windows exe from a Mac with
-Dtarget=x86_64-windows-gnu, which is no longer how any shipped binary is
produced.
- All four artifacts are built by CI, one workflow per platform
(
release-macos.yml — a two-leg matrix producing both DMGs,
release-windows.yml, release-linux.yml), triggered by
release: published. So starts them automatically. Don't
announce until all four are listed; re-run a failed leg with
.