# Tauri → Electron auto-update cutover > Self-contained playbook. The branch and conversation where this plan was > designed will not be around when the cutover happens — read this file top to > bottom and execute; do not assume prior context. ## What this is OpenChamber historically shipped as a Tauri app. A parallel Electron shell was added on branch `electron-app` (merged to `main` as part of a larger migration). Since then, both desktop shells have been released in the same GitHub release and each has its own auto-update channel: | Shell | Manifest | Update format | Secret used to sign | |----------|-------------------|---------------------|---------------------| | Tauri | `latest.json` | `.tar.gz` + `.sig` | `TAURI_SIGNING_PRIVATE_KEY` (minisign) | | Electron | `latest-mac.yml` | `.zip` + `blockmap` | Developer ID codesign (APPLE_* secrets) | Existing Tauri installs keep their own auto-update path (`latest.json`). Electron installs auto-update through `latest-mac.yml`. They coexist without conflict because filenames and manifests differ. At some point the user wants to **stop maintaining the Tauri build** and make the Tauri installs migrate themselves into Electron via auto-update. This document describes how to do that in a single "transition release". ## The core trick Tauri's updater downloads whatever `.tar.gz` the `latest.json` points at, verifies the minisign signature, unpacks the contents **over** the existing `.app` directory, and restarts. It does **not** introspect the payload — it just replaces files. So: produce a `.tar.gz` of the Electron `.app`, sign it with the existing Tauri minisign key, point `latest.json` at it. Tauri users receive the update, their `OpenChamber.app` becomes the Electron bundle in-place, and next launch starts Electron. Subsequent updates go through `latest-mac.yml` (electron-updater). One-way migration, one-shot workflow change. ## Prerequisites before running the cutover Check all of these before making any release: 1. **Electron has shipped stable through its own `latest-mac.yml` path for at least 2 releases.** Verify: ``` gh release list --repo btriapitsyn/openchamber gh release view vX.Y.Z --repo btriapitsyn/openchamber \ | grep -E 'OpenChamber-.*\.zip|latest-mac\.yml' ``` A user on Electron should have successfully auto-updated at least once. If not, pause and stabilise that path first — don't stack risk. 2. **`~/.config/openchamber/settings.json` is still the shared state path.** Tauri `src-tauri/src/main.rs:settings_file_path` and Electron `packages/electron/main.mjs:settingsFilePath` must both resolve to `$HOME/.config/openchamber/settings.json`. If either has moved, data parity breaks and this migration loses user data. Audit both paths, update the non-migrated shell to match before proceeding. 3. **Electron `appId` is `dev.openchamber.desktop`** (check `packages/electron/package.json` `build.appId`). Tauri identifier is `ai.opencode.openchamber`. These differ intentionally — it means macOS LaunchServices will re-register after the in-place replace. That's fine but see "Risks" below. 4. **All GitHub secrets still valid:** `APPLE_CERTIFICATE`, `APPLE_CERTIFICATE_PASSWORD`, `APPLE_ID`, `APPLE_PASSWORD`, `APPLE_TEAM_ID`, `TAURI_SIGNING_PRIVATE_KEY`, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. A workflow_dispatch dry-run should succeed before the real tag. 5. **`minisign` CLI is available on the macOS runner** (or installable via brew). Used to sign the Electron tarball with the Tauri key. ## Release workflow changes The file to edit: `.github/workflows/release.yml`. Today it has these jobs (simplified): ``` create-release ├── build-desktop-macos (Tauri .dmg/.tar.gz/.tar.gz.sig) ├── build-desktop-electron-macos (Electron .dmg/.zip/blockmap/latest-mac.yml) ├── publish-npm ├── combine-manifests (merges Tauri per-arch JSONs → latest.json) ├── combine-electron-manifests (merges Electron per-arch YMLs → latest-mac.yml) └── finalize-release ``` ### Step 1 — Remove the Tauri build Delete these jobs entirely: - `build-desktop-macos` - `combine-manifests` They are replaced by the repackage job (below). `finalize-release` `needs:` list must be updated to drop both. ### Step 2 — Add a repackage job Insert after `build-desktop-electron-macos`: ```yaml repackage-electron-as-tauri-update: needs: [create-release, build-desktop-electron-macos] runs-on: macos-26 strategy: fail-fast: false matrix: include: - arch: arm64 platform: darwin-aarch64 - arch: x64 platform: darwin-x86_64 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - uses: actions/setup-node@v4 with: node-version: '20' # Pull the signed+notarized Electron .app that build-desktop-electron-macos # already produced. Either re-download the dmg and mount+copy the .app, or # (cleaner) modify build-desktop-electron-macos to upload the .app itself # as an artifact so this job can download it. Prefer the latter — adds one # `actions/upload-artifact@v4` step uploading `packages/electron/dist/mac-/OpenChamber.app`. - name: Download signed Electron .app uses: actions/download-artifact@v4 with: name: electron-app-${{ matrix.arch }} path: staged - name: Install minisign run: brew install minisign - name: Tar and sign Electron .app as Tauri update payload env: TAURI_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} VERSION: ${{ needs.create-release.outputs.version }} run: | set -euo pipefail cd staged # The tarball name convention Tauri's updater expects. Must end in # `.app.tar.gz`. Name stays stable — Tauri updater does not care about # the inner .app name. TARBALL="OpenChamber.app.tar.gz" tar -czf "$TARBALL" OpenChamber.app # minisign needs the private key written to a file and a non-interactive # password via -W (or env). The key in the secret is a minisign secret # key block (base64-ish multi-line blob). Write to a file verbatim. echo "$TAURI_KEY" > ../tauri-signing.key echo "$TAURI_KEY_PASSWORD" | minisign -S -s ../tauri-signing.key \ -m "$TARBALL" -W # Rename per platform so the release has distinct names for arm64/x64. mv "$TARBALL" "OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz" mv "${TARBALL}.minisig" "OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz.sig" - name: Generate Tauri latest-.json env: VERSION: ${{ needs.create-release.outputs.version }} REPO: ${{ github.repository }} run: | SIG=$(cat staged/OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz.sig) TAR=OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz cat > staged/latest-${{ matrix.platform }}.json <