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
This commit is contained in:
Niels Laute
2026-08-21 08:14:48 +02:00
committed by Boliang Zhang (from Dev Box)
parent 0f94136c95
commit 9ecbc9ab8a
3 changed files with 17 additions and 9 deletions

View File

@@ -111,7 +111,6 @@ std::optional<std::wstring> 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))
{

View File

@@ -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<GeneralSettingsCustomAction>(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]

View File

@@ -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.");
}