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 PCThe Mac
Works onThe server, the web app (internal/webui/assets), the Android app (android/), Android TVThe iPhone app and the Apple TV app (ios/) - Xcode runs nowhere else
RunsThe live server, in DockerSimulators, against the live server at home or by its away-from-home name
DeploysYes - it is the only machine that doesNo

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.

The Android app needed two changes to the web files - CSS variables for the camera cutout (--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

  1. Start with git pull. Before touching anything.
  2. Work, with one real check per change, and commit.
  3. 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.

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:

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: