From 9ecbc9ab8a69203149701532603a083103eafe31 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 21 Aug 2026 08:14:48 +0200 Subject: [PATCH] fix(settings-ui): prevent update checks from resetting modules (#50018) ## Summary of the Pull Request Prevents update checks from resetting enabled module states when Settings UI holds stale or default general settings. The update check now sends the existing action-only IPC shape from `src/settings-ui/Settings.UI/ViewModels/UpdateViewModel.cs`. Runner handles that command without applying its payload as general settings in `src/runner/settings_window.cpp`. ## PR Checklist - [x] Closes: #48907 - [x] **Communication:** Root cause analysis was posted on the linked issue - [x] **Tests:** Added/updated and all pass ## Detailed Description of the Pull Request / Additional comments Previously, `CheckForUpdates()` embedded the complete mutable `GeneralSettings` object in a custom-action message. Runner then called `apply_general_settings` on that action payload. If Settings UI had loaded fallback defaults, merely checking for an update could persist those defaults and overwrite the user's enabled module choices. This change: - sends only `{ "action": { "general": { "action_name": "check_for_updates" } } }`; - prevents Runner from treating an update action as a general-settings update; - preserves prerelease behavior because changing that setting already sends normal general-settings IPC before starting the update check; and - updates `src/settings-ui/Settings.UI.UnitTests/ViewModelTests/Update.cs` to verify non-default module states are neither transmitted nor mutated. No persisted settings schema, localization, documentation, or binary changes are required. ## Validation Steps Performed - Built `src/settings-ui/Settings.UI.UnitTests/Settings.UI.UnitTests.csproj` for x64 Debug. - Ran `ViewModelTests.Update` with `vstest.console.exe`: 26 passed. - Built `src/runner/runner.vcxproj` for x64 Debug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b3fb20d-6e9d-4fef-a5cd-f8921d28c220 --- src/runner/settings_window.cpp | 1 - .../ViewModelTests/Update.cs | 15 +++++++++++---- .../Settings.UI/ViewModels/UpdateViewModel.cs | 10 ++++++---- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index f6316a7aff..8c95b0f99f 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -111,7 +111,6 @@ std::optional dispatch_json_action_to_module(const json::JsonObjec } else if (action == L"check_for_updates") { - apply_general_settings(value); bool expected_isUpdateCheckThreadRunning = false; if (isUpdateCheckThreadRunning.compare_exchange_strong(expected_isUpdateCheckThreadRunning, true)) { diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/Update.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/Update.cs index 1f34dce8f3..822c2a68c6 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/Update.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/Update.cs @@ -58,13 +58,17 @@ namespace ViewModelTests } [TestMethod] - public void CheckForUpdatesShouldShowProgressAndSendCurrentSettings() + public void CheckForUpdatesShouldShowProgressAndSendActionOnly() { string sentMessage = null; var generalSettings = new GeneralSettings { IncludePrereleaseUpdates = true, }; + generalSettings.Enabled.AlwaysOnTop = false; + generalSettings.Enabled.FancyZones = false; + generalSettings.Enabled.PowerLauncher = true; + var settingsBeforeCheck = generalSettings.ToJsonString(); var viewModel = CreateViewModel( new TestSettingsRepository(generalSettings), new UpdatingSettings(), @@ -80,9 +84,12 @@ namespace ViewModelTests Assert.IsTrue(viewModel.IsActivityVisible); Assert.IsFalse(viewModel.CanStartAction); - var action = JsonSerializer.Deserialize(sentMessage); - Assert.IsTrue(action.GeneralSettingsAction.GeneralSettings.IncludePrereleaseUpdates); - Assert.AreEqual("check_for_updates", action.GeneralSettingsAction.GeneralSettings.CustomActionName); + using var message = JsonDocument.Parse(sentMessage); + var generalAction = message.RootElement.GetProperty("action").GetProperty("general"); + Assert.AreEqual("check_for_updates", generalAction.GetProperty("action_name").GetString()); + Assert.IsFalse(generalAction.TryGetProperty("enabled", out _)); + Assert.IsFalse(generalAction.TryGetProperty("include_prerelease_updates", out _)); + Assert.AreEqual(settingsBeforeCheck, generalSettings.ToJsonString()); } [TestMethod] diff --git a/src/settings-ui/Settings.UI/ViewModels/UpdateViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/UpdateViewModel.cs index 622b5b9ad9..730188df98 100644 --- a/src/settings-ui/Settings.UI/ViewModels/UpdateViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/UpdateViewModel.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics; using System.IO; using System.IO.Abstractions; +using System.Text.Json; using System.Threading; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; @@ -13,6 +14,7 @@ using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Interfaces; using Microsoft.PowerToys.Settings.UI.Library.Utilities; +using Microsoft.PowerToys.Settings.UI.SerializationContext; using Microsoft.UI.Dispatching; namespace Microsoft.PowerToys.Settings.UI.ViewModels @@ -314,15 +316,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels return; } - var generalSettings = _settingsRepository.SettingsConfig; - generalSettings.CustomActionName = "check_for_updates"; - var customAction = new GeneralSettingsCustomAction(new OutGoingGeneralSettings(generalSettings)); + var checkForUpdatesAction = JsonSerializer.Serialize( + ActionMessage.Create("check_for_updates"), + SourceGenerationContextContext.Default.ActionMessage); RequestActivity(); StartTransientOperation(TransientUpdateOperation.Checking); try { - if (_sendCheckForUpdatesConfigMessage(customAction.ToString()) != 0) + if (_sendCheckForUpdatesConfigMessage(checkForUpdatesAction) != 0) { FailTransientOperation(UpdateUIState.NetworkError, "Failed to send the update check request."); }