What's wrong
ARC-Client (currently 0.93.4) has no auto-update system. Every release requires a manual download and install. Users stay on old versions and miss bug fixes or features.
What users see
- App never prompts for an update.
- Bug fixes require manual download from GitHub Releases.
- No way to receive beta builds automatically.
Impact
Users stuck on old versions. Bug reports pile up against issues that are already fixed in unreleased code.
Expected behavior
- App automatically checks for updates on launch.
- Two channels: Stable (default, from
master) and Beta (from Dev-chloe).
- Updates are downloaded silently and prompt the user to restart.
- SmartScreen warnings accepted as a known limitation for v1 (deferred to Phase C).
Acceptance criteria (this issue = Phase A only)
Technical details
Architecture decisions
| Element |
Choice |
| Library |
electron-updater@~8.0.0 (peer of electron-builder 25.1.0) |
| Channels |
Stable ← master merge + version tag; Beta ← annotated tag vX.Y.Z-beta.N |
| Artifact |
NSIS per-user installer only (cuts portable complexity) |
| Hosting |
GitHub Releases on this repository |
| Code signing |
Deferred to Phase C |
| User channel pick |
Default Stable; Settings → Updates → switch with confirmation dialog |
| Force-update |
Hardcoded MIN_REQUIRED_VERSION in main/updater.ts |
Branch → channel mapping
master ──approved merge + version tag──> Stable release (tag vX.Y.Z)
Dev-chloe ──tag vX.Y.Z-beta.N──> Beta release
Dev-chloe is re-cast as a release-candidate branch: feature work happens on topic branches, lands on Dev-chloe via PR, and only an explicit annotated tag publishes to Beta.
- Branch protection on
master: PR + 1 approval + green CI.
Dev-chloe is not force-pushed (ruleset enforced).
Versioning
package.json version is the canonical Stable version.
- Stable: bump
package.json version in the PR that lands on master. Workflow tags v<version> from the merge commit.
- Beta: workflow computes
versionSuffix=-beta.${GITHUB_RUN_NUMBER} at build time. app.getVersion() returns 0.94.0-beta.12. Tag is v<semver>-beta.N.
- No
package.json mutation in CI.
- All version comparisons in client code use
semver.coerce().
- Commit-message escape hatches:
[skip ci] (don't run workflow) and [skip-release] (run workflow, fail it if a beta tag would publish without a CHANGELOG entry).
Build artifacts
| Build target |
Purpose |
Manifest name |
Released? |
electron-builder --win nsis |
Per-user NSIS installer |
latest.yml |
Yes |
electron-builder --win dir (portable) |
Local dev / smoke tests |
portable-latest.yml |
No |
NSIS config (per-user default):
win:
target: nsis
nsis:
oneClick: false
perMachine: false
allowToChangeInstallationDirectory: true
installLocationBase: localAppData
Blockmap auto-generated by electron-builder. Differential download is automatic when both client and server have blockmaps.
GitHub Actions workflow
Single workflow file: .github/workflows/release.yml.
Triggers:
on:
push:
branches: [master]
tags: ['v*.*.*-beta.*']
Steps:
checkout (fetch-depth: 0).
setup-node 20, cache npm.
npm ci.
npm run typecheck (gate).
- Beta-only: tag-rate-limit check (fail if previous beta tag < 30 min old; configurable via
MIN_BETA_INTERVAL_MIN).
- Beta-only: changelog gate (fail if
CHANGELOG.md or beta-notes/ not in diff vs previous beta tag and commit message does not contain [skip-release]).
npm run build → electron-builder --win nsis (Beta: --config.win.versionSuffix=-beta.${GITHUB_RUN_NUMBER}).
- Smoke test:
scripts/verify-build-output.js (manifest shape, asset hashes, version match).
- Rename manifest:
latest.yml (Stable) / latest-beta.yml (Beta).
- Create GitHub Release:
release_type=release (Stable) / prerelease (Beta).
- Upload
dist/*.exe, dist/*.blockmap, manifest.
Permissions: contents: write (required to create Releases).
CI smoke test — scripts/verify-build-output.js
Verifies before manifest upload:
dist/latest.yml (or dist/latest-beta.yml) is well-formed.
- Every
path exists in dist/.
- Every
sha512 matches the actual file hash.
blockmap files exist for every executable.
version in the manifest matches the expected v<version> (Stable) or v<version>-beta.<run_number> (Beta).
If any check fails, the workflow fails before the GitHub Release is created.
Phase A verification gates (must pass before Phase B)
| # |
Gate |
Method |
Pass criterion |
| G1 |
bundledLibs path resolution under NSIS |
Build NSIS, install to temp dir, exec .exe with --smoke |
process.resourcesPath + '/bundledLibs/' exists and whisper-node-addon loads its DLL from it |
| G2 |
userdata preserved across update |
Install v0.94 NSIS, write user data, install v0.95 over it |
userdata/config.json content unchanged |
| G3 |
Blockmap differential works |
Build v0.94 + v0.95, install v0.94, trigger update to v0.95 with verbose logging |
Downloaded bytes < 50% of full installer size |
| G5 |
Beta manifest resolves |
Fetch the actual latest-beta.yml asset URL from the first beta release |
HTTP 200, valid YAML, correct version, path/sha512 for the beta release |
Rollback playbook (documented in docs/release-process.md)
For a bad Stable release:
- Stop further promotion and mark the affected GitHub release as bad; do not delete or force-move an existing tag.
- Identify the last-known-good commit; create a short-lived recovery branch.
- Bump
package.json to a version higher than the bad release (usually next patch).
- Run the normal build and updater gates; publish as a new Stable release.
- Test an installed bad build against the new manifest.
- Keep the previous installer available for manual rollback.
Pre-flight before implementation
- Verify GitHub repo visibility. If private, design token delivery without embedding a long-lived token in the installer or logs.
- Coordinate
Dev-chloe branch re-purposing.
- Enable branch protection on
master in GitHub UI.
- Build v0.94 (current) and v0.95 (test) NSIS installers for gate verification.
Documentation deliverables
| File |
Content |
docs/release-process.md |
How to tag a beta; how to bump Stable; rollback playbook; emergency hotfix procedure |
Initial CHANGELOG.md |
Created as part of Phase A so the changelog gate has something to check |
Out of scope
- Client runtime + UI + force-update gate — see Phase B issue.
- Code signing & notarization (Phase C).
- macOS / Linux support.
- Auto-update of bundled whisper models / native addons.
- Delta/differential updates beyond what blockmap provides.
- Telemetry / opt-in crash reporting.
- Multi-architecture (ARM64 Windows).
What's wrong
ARC-Client (currently
0.93.4) has no auto-update system. Every release requires a manual download and install. Users stay on old versions and miss bug fixes or features.What users see
Impact
Users stuck on old versions. Bug reports pile up against issues that are already fixed in unreleased code.
Expected behavior
master) and Beta (fromDev-chloe).Acceptance criteria (this issue = Phase A only)
.github/workflows/release.ymltriggers on push tomasterand onv*.*.*-beta.*tags.latest.yml; Beta channel buildslatest-beta.yml.prerelease; Stable asrelease.master(PR + 1 approval + green CI).Technical details
Architecture decisions
electron-updater@~8.0.0(peer of electron-builder 25.1.0)mastermerge + version tag; Beta ← annotated tagvX.Y.Z-beta.NMIN_REQUIRED_VERSIONinmain/updater.tsBranch → channel mapping
Dev-chloeis re-cast as a release-candidate branch: feature work happens on topic branches, lands onDev-chloevia PR, and only an explicit annotated tag publishes to Beta.master: PR + 1 approval + green CI.Dev-chloeis not force-pushed (ruleset enforced).Versioning
package.jsonversionis the canonical Stable version.package.jsonversion in the PR that lands onmaster. Workflow tagsv<version>from the merge commit.versionSuffix=-beta.${GITHUB_RUN_NUMBER}at build time.app.getVersion()returns0.94.0-beta.12. Tag isv<semver>-beta.N.package.jsonmutation in CI.semver.coerce().[skip ci](don't run workflow) and[skip-release](run workflow, fail it if a beta tag would publish without a CHANGELOG entry).Build artifacts
electron-builder --win nsislatest.ymlelectron-builder --win dir(portable)portable-latest.ymlNSIS config (per-user default):
Blockmap auto-generated by electron-builder. Differential download is automatic when both client and server have blockmaps.
GitHub Actions workflow
Single workflow file:
.github/workflows/release.yml.Triggers:
Steps:
checkout(fetch-depth: 0).setup-node 20, cache npm.npm ci.npm run typecheck(gate).MIN_BETA_INTERVAL_MIN).CHANGELOG.mdorbeta-notes/not in diff vs previous beta tag and commit message does not contain[skip-release]).npm run build→electron-builder --win nsis(Beta:--config.win.versionSuffix=-beta.${GITHUB_RUN_NUMBER}).scripts/verify-build-output.js(manifest shape, asset hashes, version match).latest.yml(Stable) /latest-beta.yml(Beta).release_type=release(Stable) /prerelease(Beta).dist/*.exe,dist/*.blockmap, manifest.Permissions:
contents: write(required to create Releases).CI smoke test —
scripts/verify-build-output.jsVerifies before manifest upload:
dist/latest.yml(ordist/latest-beta.yml) is well-formed.pathexists indist/.sha512matches the actual file hash.blockmapfiles exist for every executable.versionin the manifest matches the expectedv<version>(Stable) orv<version>-beta.<run_number>(Beta).If any check fails, the workflow fails before the GitHub Release is created.
Phase A verification gates (must pass before Phase B)
bundledLibspath resolution under NSIS--smokeprocess.resourcesPath + '/bundledLibs/'exists andwhisper-node-addonloads its DLL from ituserdatapreserved across updateuserdata/config.jsoncontent unchangedlatest-beta.ymlasset URL from the first beta releaseversion,path/sha512for the beta releaseRollback playbook (documented in
docs/release-process.md)For a bad Stable release:
package.jsonto a version higher than the bad release (usually next patch).Pre-flight before implementation
Dev-chloebranch re-purposing.masterin GitHub UI.Documentation deliverables
docs/release-process.mdCHANGELOG.mdOut of scope