From 9fb4e0fe77573daef0edca5d45651f1f7c12ecc4 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Mon, 24 Aug 2026 00:06:13 +0800 Subject: [PATCH] fix(update): read the update prompt's answer by the layout it actually showed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The elevation rule was spelled out twice — once to decide whether to offer a "next launch" button, once twenty lines later to decide whether answer index 1 meant that button. With elevation in play there is no such button and index 1 is "Later", so the two must agree exactly. Nothing held the second copy. Dropping `!needs_elevation()` from it turned every "Later" on an elevation-needing update into a staged install: the user declines, and the app schedules the thing they declined for the next launch — the one install it already knows it cannot carry out unattended, which is why the button was withheld in the first place (#504). Derived once now and read in both places, so the reader cannot believe in a button the layout did not draw. --- src/core/update.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/core/update.rs b/src/core/update.rs index eab669b7..c86d5a21 100644 --- a/src/core/update.rs +++ b/src/core/update.rs @@ -498,6 +498,16 @@ fn prompt_update(update: &AvailableUpdate, window: &mut Window, cx: &mut App) { ], ) }; + // An unattended next-launch install is a promise an elevation-needing + // package cannot keep: nobody is there to answer UAC before the first + // window (#504). The only honest offer is "now". + // + // Decided once and read twice — here to lay the buttons out, and below to + // read the answer back. Two spellings would be two rules, and the one that + // drifts is the reader: with elevation in play button 1 *is* "Later", so a + // reader that still believed it was "next launch" would take a decline as + // consent and stage an install nobody agreed to. + let offered_next_launch = update.installable && !update.needs_elevation(); // "Later" is the cancel answer: it takes Escape, and a closed window or a // dropped channel falls through to it too, so the outcome nobody chose is // always the one that changes nothing. Skipping a version is a decision @@ -507,10 +517,7 @@ fn prompt_update(update: &AvailableUpdate, window: &mut Window, cx: &mut App) { let mut buttons = vec![gpui::PromptButton::ok(t( L10nKey::SettingsUpdateAndRelaunch, ))]; - // An unattended next-launch install is a promise an elevation-needing - // package cannot keep: nobody is there to answer UAC before the first - // window (#504). The only honest offer is "now". - if !update.needs_elevation() { + if offered_next_launch { buttons.push(gpui::PromptButton::ok(t(L10nKey::UpdateDialogNextLaunch))); } buttons.push(gpui::PromptButton::cancel(t(L10nKey::UpdateDialogLater))); @@ -531,9 +538,6 @@ fn prompt_update(update: &AvailableUpdate, window: &mut Window, cx: &mut App) { let update = update.clone(); cx.spawn(async move |cx| { let installable = update.installable; - // With elevation in play, button 1 *is* "Later" — only a layout that - // offered "next launch" may treat index 1 as that answer. - let offered_next_launch = installable && !update.needs_elevation(); match answer.await { Ok(0) if installable => { cx.update(install_available);