Updates and downloads
Background auto-updates via electron-updater, the per-OS update feed, and the /download page plus the always-latest installer links behind it.
Packaged builds update themselves through electron-updater, and your users get the installers from the public /download page. Both read the feed published by the release workflow.
Auto-updates
On launch the app checks the feed in the background, downloads a newer version, and installs it on the next quit. Integrity comes from the installer's code signature and per-file .blockmap, so there is no separate signing key to manage.
- Feed:
<config.desktop.autoUpdate.url>/latest-mac.ymlon macOS (latest.ymlon Windows,latest-linux.ymlon Linux), baked into the packaged app by electron-builder. - Check: Settings has "Check for updates" and an install action. A finished background download also raises an in-app "Restart to update" toast; installing quits and relaunches into the new version, and dismissing it leaves the update to install on the next quit.
- Dev builds report updates as unavailable;
apps/desktop/dev-app-update.ymlpoints electron-updater at a feed underelectron-vite devto test the flow.
| Platform | Updater artifact | Coverage |
|---|---|---|
| macOS | .zip | full auto-update (signed builds only) |
| Windows | nsis setup | full auto-update |
| Linux | AppImage | AppImage only - .deb users update through their package manager |
Unsigned macOS builds cannot auto-update. Ship a signed macOS build (see Releasing) before relying on updates there. With AI on, the runtime needs no separate update: the app forks it from its own binary, so replacing the bundle replaces it too.
The download page
Your users get the app at /download, a public page your site already ships and links from the footer. It reads the update feed and lists one card per platform with its published version and installer link.
- Before your first release the feed does not exist, so the page says "No release yet" rather than offering a link that 404s - what a freshly generated project shows.
- After a release, each platform whose feed resolves gets a card. A platform you did not build is not offered.
- With the app off (
config.desktop.enabled: false) the route 404s and the footer link is hidden.
Where the links point
Every release publishes a stable, always-latest installer per platform under config.desktop.autoUpdate.url, named from config.desktop.protocol:
| Platform | URL |
|---|---|
| Windows | <autoUpdate.url>/<protocol>-Setup.exe |
| macOS | <autoUpdate.url>/<protocol>.dmg |
| Linux | <autoUpdate.url>/<protocol>.AppImage |
To put a download button elsewhere (a navbar, a landing hero), @repo/utils/desktop-download builds those URLs:
| Function | Returns |
|---|---|
fetchDesktopReleases() | the published releases ({ platform, version, url }), empty when nothing is released - what the download page renders |
desktopDownloadUrls() | the per-OS map ({ mac, windows, linux }) of installer URLs, without checking the feed |
desktopDownloadUrlFor(userAgent) | the single installer URL for the visitor's OS (null when unknown) |
desktopDownloadUrls() and desktopDownloadUrlFor() skip the feed, so their URLs 404 until the first release - use fetchDesktopReleases() where a dead link would be visible. Call it from the server: the feed is a public bucket that sends no CORS headers. OS detection needs navigator.userAgent, so read it client-side with a fallback that lists every platform.
The app moved to apps/desktop
Since 3.4.3 the desktop app lives at apps/desktop (it was apps/electron). generatesaas update never deletes your files, so a project generated earlier keeps the old directory beside the freshly staged one, and update says so once.
apps/electron into apps/desktop.apps/electron, then run pnpm install so the lockfile drops the old workspace path.Migrating from the Tauri shell
The desktop app moved from Tauri to Electron. generatesaas update never deletes your files, so a project scaffolded before the move keeps apps/tauri beside the updated apps/desktop, and the update prints a notice when it sees both.
pnpm --filter desktop dev, then a packaged build) and confirm sign-in, updates, and any AI features work.apps/tauri - the frozen shell is no longer built, released, or referenced.Your config.desktop identity carries over unchanged, so installers and the update feed keep their names.
Releasing
Cut a desktop release from GitHub Actions - the secrets it needs, what it signs and publishes, and where the installers and update feed land.
AI agents
The desktop app's AI surface - Chat, Automations, and the AI settings screens - running on your hosted models or on the user's own coding CLIs.