Patternflow uses one unified semantic version for the public project.
The project version covers firmware, PCB, case files, web, and docs together. Use tags like v2.0.0, not separate public fw-*, hw-*, or web-* release tags.
- PATCH (
v2.0.1) -- documentation fixes, small firmware/web bug fixes, no user-facing build change. - MINOR (
v2.1.0) -- new patterns, new web features, compatible case or PCB improvements. - MAJOR (
v3.0.0) -- hardware-incompatible changes, major interaction changes, or a new build path.
Since 3.9.3 the checklist below is a script. On dev, with a clean tree and
the [Unreleased] section of CHANGELOG.md written:
python firmware/toolchain/release.py cut v3.9.4 --audio v0.5.4 --performance v0.2.4bumps the versions, dates the changelog section, updates AGENTS.md, runs
shelf.sh for the core and each named edition, points the flasher manifest
and the /editions cards at the new images, runs the web checks, commits
release: v3.9.4 and tags it. Name only the editions that should be re-cut;
the others keep their image. Then write the release notes and
python firmware/toolchain/release.py publish v3.9.4 --notes notes.mdpushes, opens the dev → main pull request from the changelog section, waits
for its checks, merges, creates the GitHub release from the notes, attaches
the edition images under their release names, and waits for the workflow
that attaches the core images. README.md's Moving fast note is prose and
stays yours. cut --no-build --no-commit is the dry run.
An edition's maintainer ships between core releases, from a pull request, with one command on a clean tree:
python firmware/toolchain/release.py edition performance v0.2.8It bumps that edition's PF_VARIANT_VERSION, runs shelf.sh for it (a clean
build — the image is refused if it carries credentials or the wrong version),
points its /editions card at the new folder, updates its clause in
AGENTS.md and commits release: performance v0.2.8. No tag, no changelog
section, no manifest — those are the core's. Push and open the pull request;
CI checks that the six places agree and compiles every composition, and the
shelf serves the image once main deploys. The core's maintainer merges and
does nothing else. --no-build --no-commit is the dry run.
Only editions in release.py's EDITIONS tuple are on the shelf. A bundle in
the tree that is not there (clock, midi) is a composition CI keeps
compiling, and nothing more; there is no card to point and nothing to cut.
What the two commands do, step by step - and the way to do it by hand.
-
Make sure all intended changes are committed, and
./firmware/bundles/build.sh allis green if anything underfirmware/moved. -
Bump the version the firmware reports:
PF_IMPROV_FW_VERSIONinfirmware/patternflow/net_config.h(writtenX.Y.Z, nov).shelf.shrefuses a core image whose define disagrees with the version it is being shelved as. -
Turn
CHANGELOG.md's[Unreleased]into## [X.Y.Z] - YYYY-MM-DDand open a fresh[Unreleased]above it. -
Update the version the docs claim: the "current" line in
AGENTS.md, the Moving fast note inREADME.md, and any guide that names a release. -
If hardware changed: confirm
BUILD_GUIDE.md's parts table,hardware/bom/bom_v*.csvand the schematic agree; regenerate the Gerber zip, renders and schematic exports with the recipe inhardware/pcb/README.md; add a### Hardwareentry to the changelog section; update the board table inhardware/README.mdand the version line indocs/assembly/README.md; attach the Gerber zip, the BOM CSV and the STLs to the release. -
Stage the images the site serves:
./firmware/bundles/shelf.sh core vX.Y.Z
then point
web/public/flash/manifest.jsonat the new folder. An edition that also moved gets its ownshelf.sh <edition> vA.B.Cand a card update inweb/src/app/editions/editions-data.ts. The shelf retires the previous folder of the same name — older images stay on their tags. Try-out images (clock-v0.1.5,midi-v0.1.0, the ones the features page installs) are not part of a release and stay as they are; a fresh one is its ownshelf.sh <name> vA.B.Cand a row update inweb/src/app/features/features-data.ts, done when somebody wants a newer one, not because the core moved. -
Regenerate the guide's live console demo, which carries the version, the core image's build id and the manifest version:
python firmware/toolchain/console_demo.py(neitherrelease.pynor CI does this; without it the demo on/guide/playkeeps presenting the previous release), and confirmpython firmware/toolchain/editor_demo.py --check. Then run the web checks fromweb/:npm run lint && npm run typecheck && npm run check:ci && npm run build. -
Commit and tag:
git commit -m "release: vX.Y.Z" git tag -a vX.Y.Z -m "Release vX.Y.Z" git push origin dev && git push --tags
-
Create the GitHub Release from the tag. Publishing it triggers Firmware release assets, which attaches the four flash images from the tag plus a generated
FLASHING.mdwith offsets and hashes. -
Confirm it went green before announcing; re-run it with
workflow_dispatchif it did not fire. -
Redeploy the community host (the Pi: pull
main, build, restart the web and worker units) oncemainhas deployed. The Basics pack card and the pattern builds are served from there, so until it is redeployed it hands out the previous pack and the previous build settings. Do it before announcing anything that says "install the pack again".
Work happens on dev, then lands on main through a pull request. main is protected: nobody pushes to it directly, the maintainer included.
-
Commit freely on
dev. Commits are cheap save points; small and frequent is good. Throwawaywip:commits belong here rather than onmain. -
Bigger or riskier work gets its own branch (
feat/...,fix/...) offdev. -
Outside pull requests target
main. After one merges, pullmainback intodevso the branches don't drift:git checkout dev && git merge origin/main && git push origin dev -
A release is
dev→mainas one pull request, opened byrelease.py publishfrom the changelog section; merging it posts the dev-log to Discord.
CHANGELOG.md is the record — one section per release, newest first — and the releases page carries the notes and the flashable images. The shape of the line, for orientation:
v1.x-- first public buildable release, then the multi-pattern firmware and browser flasher.v2.x-- the v2.0 board (GPIO0 cold-boot fix, cleaned silkscreen), custom pattern workflow, the web platform.v2.1.0is the last release for v2.x hardware.v3.0.0-- the v3.0 board;v3.9(2026-09, unreleased as a tag) removed its USB-C footprint and changed nothing else. Every laterv3.xis firmware/web on that hardware:.pfmmodules over Wi-Fi (3.2), shows and the Director (3.6), the feature seam (3.7), editions (3.8).- Editions on the shelf (
audio,performance) carry their own version lines, independent of the project version — seedocs/EDITIONS.md.clockandmidiare bundles CI builds but the shelf does not carry.