Skip to content

feat(updates): download updates in the background, install on one click - #152

Merged
juacker merged 3 commits into
mainfrom
feat/mandatory-update-download
Aug 5, 2026
Merged

feat(updates): download updates in the background, install on one click#152
juacker merged 3 commits into
mainfrom
feat/mandatory-update-download

Conversation

@juacker

@juacker juacker commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Updates stop asking. On builds that can self-install, the package is fetched in the
background as soon as a release is detected, and the only thing left for the user is
one click that restarts into it.

What changes

  • Background download is mandatory where the app can install itself (macOS, Windows,
    and the Linux bundles with a working installer). Every setting that governed it is gone,
    including the autoUpdate config key.
  • The toast is gone. Detection now surfaces as a persistent pill in the top bar —
    same detection logic, on every platform, including the ones that cannot self-install.
  • Once the package is downloaded, a "Restart to install" button appears next to the
    pill. That click is the only thing that ever installs. Nothing installs on its own.
  • Installing from the cached package takes no network round-trip. The bytes are already
    verified: Update::download() checks the signature before returning them, and the
    install applies bytes that never left process memory.
  • A cached package is never installed once the UI stops offering it.
    take_downloaded_if_current() compares the cached version against the version the last
    check recorded, and drops the package on a mismatch rather than installing something
    older than what the user was shown.

Config compatibility

Configs written before this change still load; the removed autoUpdate key is ignored
rather than rejected. Covered by
config_written_before_updates_became_mandatory_keeps_its_settings.

Review status

Three review rounds. Round 1 found the mandatory network round-trip before installing a
cached package; round 2 found the stale-package install. Both are fixed here, and round 3
verified both fixes and returned production_quality on the simplicity, test-quality, and
UX lane.

The round-3 correctness/concurrency lane did not run — both reviewers assigned to it
were out of provider quota. The round-2 correctness review passed on everything except the
stale-package finding, and that fix is covered by three unit tests
(take_downloaded_if_current_*), but the restructured install path has not had a fresh
adversarial correctness pass since. Draft until it does.

juacker added 3 commits August 5, 2026 17:33
…oast

Updating was three surfaces deep: a settings toggle, a dismissible toast,
and the About panel. The toggle asked a question nobody wants asked (a
downloaded package costs nothing and is discarded if never used), and the
toast interrupted work to say something the top-bar pill was already
saying.

Now: detection stays as-is on every platform, the download is
unconditional wherever the build can install updates itself, and the
persistent pill grows a single "Restart to install" button once the
package is on disk. Clicking it is still the only thing that installs an
update — nothing restarts the app on its own.

- Remove `AutoUpdateConfig` and the `get/set_auto_update_settings`
  commands; `AppUpdateStatus`/`AppUpdateCheckResult` no longer carry
  settings, and `get_app_update_status` is now infallible.
- Configs on disk still carry `autoUpdate.autoDownload`; the field is
  ignored on load rather than rejected (regression test added).
- Delete `AppUpdateNotifications` (toast) and its MainLayout wiring.
- `AppUpdateBadge` keeps a stable "Update available · vX" label and adds
  the restart action when `downloaded`, with a visible, retryable error
  path so a failed install can never look like a dead click.
Review of the first commit found that "Restart to install" did not actually
restart: `install_app_update` fetched the manifest again before looking at
the cache, so the click failed outright when offline, and if a newer release
had appeared meanwhile the matching-version cache was dropped and the click
silently became a multi-minute download behind a disabled "Restarting..."
with no progress. That was survivable while the toast offered progress; it
is not survivable now that the button is the only install surface.

The updater's `Update` handle is `Clone` and carries everything `install`
needs, so the package cache now keeps the handle with the bytes it produced.
Applying it touches no network at all, and installs exactly the version the
badge advertised. The manifest check moves into the no-cache branch, where
Settings > About still downloads with progress.

Also from review:
- A failed install now always keeps its verified package (a fresh download
  used to be discarded), via one `cache_downloaded_package` helper shared
  with the background download so both leave the same state behind.
- The mandatory-download rule is now a pure `should_download_in_background`
  predicate with tests that fail if a setting is ever wired back in — the
  property had no Rust coverage at all.
- The config back-compat test asserted values identical to the defaults, so
  it could not fail. It now carries non-default settings and a negative
  control was run (adding `deny_unknown_fields` makes it fail).
- The install error left the top bar: it is the button's own label plus a
  tooltip and an `sr-only` live region, so an arbitrarily long message can
  no longer stretch the bar. `.counters` shrinks and clips instead of
  pushing the update action and settings button off-screen.
Round-2 review found that dropping the version match from `take_downloaded`
traded one silent failure for another. Reachable trace: 26.8.1 downloads and
caches, a later check finds 26.8.2, its download fails, and Settings > About
(whose install button does not wait for `downloaded`) then installs 26.8.1
while every surface says 26.8.2 — no error, no log line.

`take_downloaded_if_current` restores the guard without giving up the offline
fast path: the expected version comes from the last recorded check, which is
already in memory, so the common case still touches no network. A mismatch
drops the stale package and lets the install path fetch what is being offered.

Also from review:
- The pill's `text-overflow: ellipsis` was inert — it sat on a flex
  container, so a narrow window hard-clipped "v26.8.12" into the
  plausible-but-wrong "v26.8.". The label is now its own span that really
  ellipsizes, the dot no longer shrinks, and the pill (never the action
  button) is what gives way.
- Corrected two comments that overstated the fast path: a click can still
  wait on `install_lock` behind an in-flight background download, which is
  reachable from About but not from the badge.
@juacker
juacker marked this pull request as ready for review August 5, 2026 15:55
@juacker
juacker merged commit 163896a into main Aug 5, 2026
2 checks passed
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