Use this page as a working list. Each section links to the page that explains the mechanism.

Order of a cross-repository release

When one feature touches several surfaces, release in this order:
  1. Migration first. Additive only. The API version that is still running must work against the new schema.
  2. Backend second. New endpoints and fields are added. Old ones stay.
  3. Web app third. It deploys in minutes and every coach gets the new version on the next page load.
  4. Mobile last. An OTA update reaches a device after two launches. A store build reaches it when the trainee updates, which can take weeks.
  5. Clean up later. Remove an old endpoint or field only when no supported app version calls it.
The API must stay backward compatible with every mobile runtime that still receives updates. See the runtime list in mobile/scripts/ota-all-runtimes.sh.

Before any release

  • The working tree of every repository you touched is clean and on the branch you expect.
  • You pulled the latest dev.
  • No secret, token or real customer data is in the diff.
  • Translations were added for every locale the surface supports.
  • The change was tested in Hebrew (RTL) and in one LTR language.
  • Any rule that exists in more than one repository was changed in all of them. See Repositories and workspace.

Database

See Database migrations.
  • The model change is in both packages/db/prisma/schema.prisma and packages/database/prisma/schema.prisma.
  • A hand-written migration folder exists, and its timestamp sorts after the newest existing one.
  • The SQL is safe to run twice (IF NOT EXISTS) because the deploy workflow may push the column before migrate deploy runs.
  • New columns are nullable or have defaults.
  • A data-moving migration was replayed on a scratch database.
  • A backup exists and the undo SQL is written down, for anything that rewrites or deletes data.
  • pnpm db:migrate:status shows what you expect on the target database.
  • pnpm db:migrate was run against the target database, and you checked the host in DATABASE_URL first.
  • pnpm --filter @repo/database build was run and the result type-checks.

Backend

See Deploying the backend.
  • pnpm ci passes locally: format check, lint, em-dash sweep, type-check, tests, build.
  • Every new environment variable is in the schema in apps/core-api/src/config.ts with a safe default, and is set on the host before the deploy.
  • A new module is registered in apps/core-api/src/modules/index.ts under the correct lane.
  • A new route under /v1/web has a role guard where it needs one, and filters by studioId.
  • A new worker is started in server.ts and closed in both shutdown paths (shutdown and the SIGUSR2 handler).
  • A new queue name is added to PerformQueue in packages/queue/src/index.ts.
  • Nothing in the change removes or renames a field or endpoint the mobile app uses.
  • TRAINEE_OTP_DEV_MODE is off on the host.
After the deploy:
  • /healthz returns 200.
  • /readyz returns 200 with no degraded entry.
  • The log shows core-api listening and no config validation failed output.
  • No burst of level 50 lines in the first minutes.
  • A signed-in coach can load the trainee list (web lane).
  • A trainee can open the app’s home screen (trainee lane).
  • If the schema changed, a request that reads the new column succeeds.

Web app

See Deploying the web app.
  • pnpm type-check, pnpm lint and pnpm test pass.
  • pnpm check:theme passes. Check the screen in dark mode.
  • pnpm check:core-api was run and you understand its output.
  • pnpm build succeeds locally for any change to next.config.ts, proxy.ts, the Dockerfile, or a server component’s imports.
  • The backend change it depends on is already deployed.
  • If the trainee preview should change, the bundle was re-exported from the mobile repository and is part of the commit.
  • If a NEXT_PUBLIC_* value changed, the build arguments or build env were updated. A runtime env change is not enough.
After the deploy:
  • The landing page loads at / when signed out.
  • /api/health on the web origin returns OK.
  • Login works with a phone code or an email code.
  • The trainee list, one trainee card, and one builder load.
  • A browser tab that was open before the deploy still works after a refresh.

Mobile: OTA update

See OTA updates.
  • The diff contains no native change: package.json dependencies with native code, app.json, plugins/, modules/, patches/.
  • Every import of a native module added since the oldest runtime in the list is guarded with requireOptionalNativeModule.
  • pnpm typecheck and pnpm lint pass.
  • The relevant node --test scripts/test-*.cjs files pass.
  • The production API already serves everything the bundle calls.
  • app.json is committed and shows the current version.
  • Risky change: published to preview first and tested on a preview build, opened twice.
  • Published to the current version with pnpm ota:production.
  • Published to older runtimes with scripts/ota-all-runtimes.sh.
  • eas update:list --branch production shows each group under the right runtime.
  • app.json is unchanged after the script finished.
  • Verified on an iOS device and an Android device, each relaunched twice.

Mobile: store build

See Mobile builds.
  • version is the same in app.json and package.json.
  • eas.json and .env.production hold the same public values.
  • A development or preview build with the new native code was tested on a real device on both platforms.
  • Push notifications, sign-in, a workout, a meal log and a form submission were tested on the build.
  • HealthKit on iOS and Health Connect on Android were tested if anything near them changed.
  • RTL and LTR were both checked on a real build. Expo Go does not apply native RTL.
  • The build was submitted, and the store review notes mention the review login if the reviewer needs it.
After the build is live in the stores:
  • TRAINEE_APP_IOS_VERSION and TRAINEE_APP_ANDROID_VERSION on the backend were raised to the new version, each only after its own store serves the build.
  • The previous version was added to the runtime list in scripts/ota-all-runtimes.sh.
  • Runtimes with no remaining installs were removed from that list.
  • The first OTA update for the new version was published and verified, so the update path is known to work before it is needed.

If something goes wrong

Rolling back code never rolls back the database. That is why step one of every release is an additive migration.