cmux NEXT updates: download quietly, then one Restart to Update card; progress in Settings > Updates
Leo, 2026-11-03, on the sidebar's Downloading Update card: "will this be themeed properly? also idk chatgpt's seems to be just like a one step "ok it's already downloaded you click this button to accept the button and restart" idk if we wanna follow that too"
The theme half landed in #33103. This PR is the flow half: copy ChatGPT's one step.
What changes
-
No sidebar card while downloading.
UpdateFlow.card(downloadsAutomatically:)returns nil for a download, whether the person asked or not. Before, a download the person had asked for (Check for Updates, then Update) showed the Downloading Update card with its bar and Cancel. The check and the install still show on the notice card as before. -
One step when it is ready. The staged update is the existing Update Ready card, with one Restart to Update button that installs and relaunches (
installClicked). The card itself is unchanged: its design, its Hide, its popover and #33103's theme. -
Progress moves to Settings > Updates. A new App Update card leads the Updates category (
SettingsPageLayout.Card.appUpdate). It shows what the updater does now, whether or not the person asked:- a download: the bytes line, a bar and Cancel;
- staged: Restart to Update;
- a failed check or download, background ones included: the cause and Try Again;
- idle: the running version and Check Now.
The state is
UpdaterService.settingsStatus/settingsPageJSON(CmuxNextUpdater), read through the host lists asapp_updateand live throughcmux.settings.host.changed. Progress is sent in whole percent, so a download re-sends at most about 100 updates. The buttons go toAppUpdatePageProvider(cmux.settings.app_update.run). Restart to Update there needs the person's own click (userGesture), because it relaunches the app. -
No failure goes silent. A failed background download used to show nowhere (the notice card shows only what the person asked for). It now shows in Settings with Try Again.
The logic is in CmuxNextUpdater. The App target gets three wiring lines (an import, the host-list entry and the page route). Those are paid for by folding two single-expression closures in UpdaterService+App.swift, so App lines go down by 1.
Variant
The brief asked for 2-3 gallery variants of the ready state (a compact pill, a full-width card, a footer button). I kept the existing full-width Update Ready card instead of building alternatives. It already is the one-step card ChatGPT has: one Restart to Update. It is native, so the web gallery cannot hold it. The rulebook also says not to replace an existing component Leo didn't name. Leo can still ask for a pill. The new Settings card has its own gallery entry, pages.settings-app-update, with idle, downloading, ready and failed variants.
References (cmux-design-references): cmux/live/status-update-ready-panel and cmux/live/update-ready-card-unstyled-no-changelog-preview, our own one-step ready card. The index has no capture of ChatGPT's update prompt.
Design spec: leo-workspace design/sidebar.md, where the rule goes in a follow-up notes PR.
Risk
risk:high by path: this changes updater state. The Sparkle phases, installs and the relaunch gate are unchanged. Only what is shown for a download changed, plus a new read of the same flow and buttons that call the existing downloadClicked, cancelDownloadClicked, checkForUpdates and installClicked. The progress bar uses block-size, not height: it is a 4 px bar, not a control on the control-height scale.
Tests
UpdateOneStepTests(new):- a download the person asked for shows no sidebar card, notice card or ready card;
- the staged update is one Restart to Update card, and its button installs and relaunches;
- Settings shows the download's progress, bytes and Cancel, then Restart to Update once staged;
- a background failure shows in Settings with Try Again and not in the sidebar;
- the provider's Cancel stops the download, and its Restart refuses a call with no click.
- Updated to the new flow:
UpdateLoadingIndicatorTests(the stage order still covers the download, through Settings),UpdateAsksBeforeInstallTests,UpdateDownloadCancelTests(Cancel now through Settings) andUpdatesCategoryLayoutTests(lead == [.appUpdate]). - Web:
webviews/src/pages/settings/appUpdateCard.test.tsx:- no updater means no card;
- a download shows its bytes, bar and Cancel;
- ready shows one Restart to Update and sends
install.
Observed / Not run
- Ran:
prepush-gate.py(no new violations; app_lines -1),scripts/web-lint.sh(passes; the type-aware lint and type check are left to CI on this host), andbun teston the settings card tests. The new web test's ready and download cases fail locally until the PR autofix regeneratesschemas/settings/settings-schema.json, a generated file that carries the newappUpdatelead. Its other cases pass, andwebUpdatesCard.test.tsxpasses. - Not run: the Swift compile and the Swift suites (this host has no fleet client; they run in this PR's CI), and a DEV build with a real download.
Changelog
- Changed: Updates download quietly with no sidebar card, then one Restart to Update card installs them; download progress, Cancel and a failed update's Try Again are in Settings > Updates.
Agent session: agent session
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Updates download quietly with no sidebar card, then a single Restart to Update card installs and relaunches the app. Download progress, Cancel, and Try Again for failed checks or downloads, background ones included, moved to a new App Update card in Settings > Updates.
- The sidebar shows no Downloading Update card, whether or not the person asked; the check, the install, and the staged update's one-step Restart to Update card are unchanged.
- A new App Update card leads Settings > Updates: the running version and Check Now when idle, the download's bytes and progress bar with Cancel, Restart to Update once staged, or the failure cause and Try Again.
- The buttons call the existing download, cancel, check, and install actions; Restart to Update requires the person's own click since it relaunches the app.
- A download's progress is rounded to whole percent so the host lists re-send at most about a hundred times.
- Added
UpdateOneStepTestsand web tests for the App Update card; existing update flow tests updated to the new flow.