How it is developed
Two computers, each with its own coding session and its own private memory, work on one repository. Everything both need to know lives in the repository itself - and the one rule that holds it together, a single branch, was learnt from a bug that shipped.
Two machines, one main
| The Windows PC | The Mac | |
|---|---|---|
| Works on | The server, the web app (internal/webui/assets), the Android app (android/), Android TV | The iPhone app and the Apple TV app (ios/) - Xcode runs nowhere else |
| Runs | The live server, in Docker | Simulators, against the live server at home or by its away-from-home name |
| Deploys | Yes - it is the only machine that does | No |
The PC deploys with:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build soundstorm
curl localhost:8099/healthz
git push
The dev override adds build: ., which the published compose file deliberately
lacks so that it works in an empty folder for somebody who never cloned anything.
Each session has its own memory, which the other never sees. So anything both must know - a
decision, a reversal, a trap, a task handed over - goes into CLAUDE.md, the working
notes at the root of the repository. Hand-offs are written there explicitly: a "For the PC" list
from the Mac's security review, "For the Mac" notes when the Apple TV's song analysis had to
follow a server change, each marked done when the other machine finished it.
Why main is the only branch
The apps used to live on branches of their own, android-app and
ios-app. That is how a bug shipped.
--safe-top and its siblings) and a Change server button. They were
made on the android-app branch. But the server serves the web app from
main, so the installed app's title sat under the phone's camera until the changes
were brought across.Both branches were merged into main and the rule written down: the apps are folders on main; do not start per-app branches again. If a short branch is needed for a risky change, it is merged the same day. Web changes for the phone apps always go to main, never into an app's folder.
The shared web files - app.js, style.css, index.html - are
where the two machines can collide. Web and server changes are made on the PC by default. When the
iPhone app needs one, the Mac may make it: pull first, keep it small, push at once, and tell the
owner it reaches phones only once the PC deploys.
The start and end of every session
- Start with
git pull. Before touching anything. - Work, with one real check per change, and commit.
- End with a commit and a push. Never leave work uncommitted on one machine.
The owner switches machines by telling the session they are leaving; that session commits and pushes everything, and the next one pulls before starting. Commits end with a Co-Authored-By line for the coding assistant, and never name a model.
Releases
The server image
.github/workflows/publish.yml runs on every push to main, on tags, and on pull
requests. A test job checks formatting, vet and tests, rehearses certificate issuance
against Pebble, validates compose with and without the Tailscale profile, decodes every committed
image, and checks the Windows setup file still has CRLF line endings. Only then does
publish build linux/amd64 and linux/arm64 and push to
ghcr.io/<owner>/soundstorm.
- arm64 matters: many home media servers are a Raspberry Pi, a Synology or an Apple silicon Mac. The Dockerfile cross-compiles on the builder's platform, so Go never runs under QEMU.
- The GitHub owner name has capitals, and a registry path may not, so the workflow folds it to lowercase.
- Every action is pinned to a commit, not a tag - a moved tag in the publish job would push to the image every install pulls - and jobs get only the permissions they need.
- A
v*tag also creates the release and attachesSoundStorm-Setup.cmd. The README links to/releases/latest/download/SoundStorm-Setup.cmd, because a raw link opens a.cmdas text in the browser instead of downloading it.
Users update by running the installer again, which pulls the newer image. SoundStorm never updates itself.
The Android app
Built on the PC from android/ on main with gradlew assembleDebug,
with versionCode raised every time so a phone installs over the last build. Published
as a GitHub pre-release named android-<version>, never marked
Latest, because Latest is where the README's installer link points.
The Apple apps
Archived on the Mac and sent to TestFlight. The Apple TV's release build is signed by hand with a distribution profile, because automatic signing first wants a development profile, which needs a registered Apple TV the team does not have.
How the owner works
These are working agreements, written down so both machines follow them:
- They dictate. A strange word may be a mis-transcription, so an ambiguous request is restated before anything is built.
- One real check per change, then commit. Not long comparison or mutation runs; say what was not verified instead. For phone fixes they would rather deploy and test on their own phone than wait on a long simulation.
- Plain words, about what they will see, rather than what the code does.
- Ask first before experiments on the live server, before writing to their real backends for a test, and before deleting media. Their media may be read for analysis, never changed or copied.
- Never print credentials.
That style shows throughout the notes: decisions are often recorded as "at the owner's asking", and a reversal records what was reported ("glitching when lyrics load"), what was measured, and what changed - so the reasoning survives even when the behaviour is changed again later.
Writing things down
The notes follow a few habits worth copying:
- Record reversals as reversals. "This used to say X, and that was wrong, because Y" stops the next person from reasoning their way back to X.
- Say how something was verified - against a live server, a real file, a throwaway stack - and what was not.
- Prefer measured numbers: 34 seconds to 2.5, 1,760 repeats to 0, 57 heap drops to 6.
- Note what to re-check if a version moves (Jellyfin's auth, foliate's touch handling, Immich's major version).