Repository navigation
feat: in-app auto-update via electron-updater #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
70cda16
docs: auto-update design spec (electron-updater, manual download/inst…
AhmedFr 25530ec
docs: auto-update implementation plan (11 tasks, TDD)
AhmedFr c815f4e
feat(updater): shared updater types, IPC channels, electron-updater dep
AhmedFr 60ae81a
feat(updater): dismissedUpdateVersion setting with IPC validation
AhmedFr 5f5e77c
feat(updater): pure logic for update summaries, error kinds, transloc…
AhmedFr 8f52167
feat(updater): UpdaterService state machine over injectable autoUpdater
AhmedFr f366a93
style(updater): biome format + import order in updater-logic
AhmedFr c3b80f9
feat(updater): update analytics events and post-install version detec…
AhmedFr 318f4f5
feat(updater): wire UpdaterService through IPC, preload and app startup
AhmedFr 4d07d3a
feat(updater): useUpdater hook syncing updater state to renderers
AhmedFr 4c1a750
feat(updater): Updates tab in Settings with manual download/install flow
AhmedFr f5c1daa
feat(updater): dismissible update banner in the menu bar panel
AhmedFr 404b6c8
feat(updater): settings-updates deep link from panel banner to Update…
AhmedFr 3166f18
fix(updater): reset settings landing tab on cmd-comma shortcut too
AhmedFr 858da18
fix(updater): dedupe update_available, keep banner through background…
AhmedFr 5433d47
docs: STATUS — auto-update feature branch ready for review
AhmedFr 671ad80
fix(updater): review fixes — instant downloading state, dedupe & polish
AhmedFr ac0e495
docs: STATUS — PR #46 review fixes landed (671ad80)
AhmedFr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # Auto-update via electron-updater + GitHub Releases — Design | ||
|
|
||
| **Date:** 2026-07-22 | ||
| **Status:** Approved | ||
|
|
||
| ## Goal | ||
|
|
||
| TidyDisk users update in place instead of downloading a DMG and reinstalling. The flow | ||
| is deliberately user-driven: the app checks silently, the user clicks to download and | ||
| clicks again to restart and install. | ||
|
|
||
| ## Context (verified) | ||
|
|
||
| - Builds are signed + notarized (`hardenedRuntime`, `notarize: true`) — a hard | ||
| requirement for macOS auto-update. | ||
| - `package.json` already has `publish: { provider: "github", releaseType: "draft" }`, | ||
| and published releases (e.g. v1.1.0) already contain `latest-mac.yml`, the ZIP, and | ||
| blockmaps — everything electron-updater consumes. Repo is public. | ||
| - The release process does not change: `pnpm release` + upload + publish the draft. | ||
| Publishing the GitHub release remains the "go live" switch. | ||
| - Users on versions before this feature ships still need one manual reinstall. | ||
|
|
||
| ## Approach | ||
|
|
||
| `electron-updater` with `autoDownload = false`, wrapped in a single main-process | ||
| service exposing a state machine over IPC. Rejected alternatives: built-in | ||
| `autoUpdater` + update.electronjs.org (no release notes/size before download, poor | ||
| manual-flow support); hand-rolled GitHub API + DMG download (reinvents checksum and | ||
| atomic install; recreates the reinstall UX). | ||
|
|
||
| ## 1. Main process: `UpdaterService` | ||
|
|
||
| New folder `src/main/updater/`: | ||
|
|
||
| - `updater-service.ts` wraps electron-updater with `autoDownload = false`, | ||
| `autoInstallOnAppQuit = true` (a download click expresses intent; quitting after | ||
| download applies the update even without the install click). | ||
| - State machine, every transition broadcast over IPC: | ||
|
|
||
| ```ts | ||
| type UpdaterState = | ||
| | { phase: 'idle'; currentVersion: string } | ||
| | { phase: 'checking' } | ||
| | { phase: 'available'; info: UpdateSummary } // version, date, sizeBytes, notes | ||
| | { phase: 'downloading'; info: UpdateSummary; percent: number } | ||
| | { phase: 'downloaded'; info: UpdateSummary } | ||
| | { phase: 'error'; message: string; kind: 'network' | 'translocation' | 'unknown' } | ||
| ``` | ||
|
|
||
| - Public methods: `check()`, `download()`, `quitAndInstall()`, `getState()`. | ||
| - Auto-check: a few seconds after app ready (never delays startup), then every | ||
| 6 hours (same pattern as license revalidation). Disabled in dev / unpackaged builds. | ||
| - `UpdateSummary.notes` = GitHub release body; empty body → no changelog shown. | ||
|
|
||
| ## 2. IPC + shared types + settings | ||
|
|
||
| - Invokable channels: `updaterCheck`, `updaterDownload`, `updaterInstall`. | ||
| - Broadcast: `onUpdaterState`. | ||
| - `UpdaterState`/`UpdateSummary` types in `src/shared/`. | ||
| - New persisted setting `dismissedUpdateVersion: string | null` in `SettingsStore`. | ||
| Banner shows iff `available.version !== dismissedUpdateVersion`; a newer version | ||
| naturally re-shows the banner. | ||
|
|
||
| ## 3. Panel banner (tray dropdown only) | ||
|
|
||
| New component folder `src/renderer/src/components/UpdateBanner/`: | ||
|
|
||
| - Renders when phase is `available` / `downloading` / `downloaded` and not dismissed. | ||
| - Compact single row: "TidyDisk v1.2.0 available · 4.2 MB · Jul 20" + ✕. | ||
| - ✕ only in the `available` phase; `downloading` shows percent, `downloaded` shows | ||
| "Ready to install". | ||
| - Clicking the body opens the Launcher on the Settings view, deep-linked/scrolled to | ||
| the Updates section. | ||
| - No banner in the Launcher window. | ||
|
|
||
| ## 4. Settings "Updates" section | ||
|
|
||
| New component folder `UpdateSettings/` (pattern: `ScanLocationsSettings`), new section | ||
| in `SettingsView`: | ||
|
|
||
| - Always: current version, "Check for updates" button, last-checked time. | ||
| - When update available: version, release date, download size, rendered release notes | ||
| (markdown, restrained styling), and one action button that morphs: | ||
| **Download update → progress → Restart and install**. | ||
| - Errors inline: network → "Try again"; translocation → "Move TidyDisk to | ||
| Applications to enable updates". | ||
|
|
||
| ## 5. Analytics, tests, release | ||
|
|
||
| - PostHog: `update_available`, `update_download_clicked`, `update_installed` (fired on | ||
| next launch via persisted last-seen-version comparison), all with version props. | ||
| - Tests: vitest unit tests for state-machine transitions and banner-dismissal logic. | ||
| Real download/install can't run in CI (needs signed app) → manual test checklist in | ||
| the PR. | ||
| - Branch `feat/auto-update`, PR into `main`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use “requires a signed app.”
Line 93’s “needs signed app” wording is ungrammatical.
🧰 Tools
🪛 LanguageTool
[style] ~93-~93: The double modal “needs signed” is nonstandard (only accepted in certain dialects). Consider “to be signed”.
Context: ...download/install can't run in CI (needs signed app) → manual test checklist in the P...
(NEEDS_FIXED)
🤖 Prompt for AI Agents
Source: Linters/SAST tools