Skip to content

Add a built-in update check with installer cleanup - #96

Open
manovotny wants to merge 4 commits into
fuji-mak:mainfrom
manovotny:feature/update-check
Open

Add a built-in update check with installer cleanup#96
manovotny wants to merge 4 commits into
fuji-mak:mainfrom
manovotny:feature/update-check

Conversation

@manovotny

@manovotny manovotny commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

New releases currently reach users only if they revisit the GitHub releases page — the app has no way to tell them an update exists. This adds an update check with zero dependencies and zero infrastructure: it reads the latest version from the GitHub releases API and compares it to the running one. Sparkle was considered and skipped on purpose — since Capsomnia updates through a signed, notarized .pkg with a privileged helper, the installer prompt is unavoidable either way, and Sparkle would add an embedded framework, an EdDSA key to manage, and appcast generation to the release process for little gain here.

Changes

  • "Check for Updates…" in the menu bar menu. When a newer version is known, the item reads "Update available — X.Y.Z", and that state survives a relaunch.
  • Automatic daily check, on by default, with an opt-out toggle in Advanced Settings under System Behavior. An hourly re-arming timer keeps the daily cadence honest in long-running sessions, failed checks are floored to one attempt per hour, and manual checks work regardless of the toggle.
  • Updating downloads Capsomnia-<version>.pkg to Capsomnia's own caches folder (~/Library/Caches/Capsomnia/), applies the quarantine attribute so Installer runs it through Gatekeeper (failing closed if the attribute can't be set), and opens it. The app-owned location avoids the macOS Downloads-folder privacy prompt, and no new signing material is introduced.
  • The downloaded installer is removed automatically on the first launch after the update is installed; failed removals retry on the next launch, so nothing lingers on disk and the user is never asked about it.
  • Decision logic (version comparison, release parsing, check throttling, cleanup state) lives in UpdateCheck with unit tests; side effects live in UpdateController. 108 tests passing overall.
  • Localized for all four languages, and the "no network requests" statements in SECURITY.md and the four READMEs now describe the update check precisely: it reads GitHub's public release information, sends no telemetry or identifiers, and turns off in one toggle.

Notes for the maintainer

Nothing is required beyond review — no keys, no hosting, no release-process changes. The check does depend on two conventions RELEASING.md already documents: tags named v<version> and a Capsomnia-<version>.pkg asset on each release. I verified both against the live API for v3.4.0.

One behavior worth a conscious yes: the daily check is on by default (opt-out), which felt consistent with "Open at login" defaulting on. Happy to flip it to opt-in if you'd rather.

🤖 Generated with Claude Code

Check the GitHub releases API for a newer version from "Check for
Updates…" in the menu bar menu, and once a day automatically (opt-out
in Advanced Settings). Updating downloads the signed installer package
to the Downloads folder and opens it; on the first launch after the
update, Capsomnia offers once to move the downloaded installer to the
Trash. Only the latest version number is requested — no telemetry, no
dependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@manovotny
manovotny marked this pull request as draft August 24, 2026 16:08
manovotny and others added 3 commits August 24, 2026 11:17
Writing to ~/Downloads triggers the macOS Downloads-folder privacy
prompt on the first update. Downloading to Capsomnia's own caches
folder avoids the prompt entirely, and since the file never lives in a
user-facing location, the post-update Trash prompt becomes an automatic
silent removal on the first launch after the update.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Quarantine downloaded installer packages before opening them so
  Installer runs them through Gatekeeper, and fail closed when the
  attribute cannot be set.
- Keep the daily check honest: an hourly re-arming timer covers
  long-running sessions where the menu never opens, and an in-session
  attempt floor stops failed checks from retrying on every menu open.
- Promote a manual "Check for Updates…" during an in-flight automatic
  check to user-initiated so its result still alerts.
- Persist the last known release version so the "Update available"
  menu state survives a relaunch within the daily check window.
- Surface installer-open failures instead of ignoring the result, and
  retry failed cache cleanups on the next launch instead of dropping
  the record.
- Describe the network behavior precisely: the check reads GitHub's
  public release information and sends no telemetry or identifiers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Download, quarantine, and Installer-launch failures now show a
dedicated "Update failed" message instead of the check-failed one,
which wrongly claimed GitHub was unreachable after the check had
already succeeded. The quarantine DataURL property now passes a URL,
matching the documented CFURLRef type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@manovotny
manovotny marked this pull request as ready for review August 24, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant