From 6951f00135aa6832eb6cc11e2fdbed8fa79620df Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 9 Aug 2026 20:36:11 +0200 Subject: [PATCH] Fixed managed settings keeping their plugin value after the configuration plugin was removed --- .../Settings/ConfigMetaBase.cs | 23 +++++++++++++ .../Settings/ManagedConfiguration.Parsing.cs | 2 +- .../Settings/ManagedConfiguration.cs | 34 +++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Settings/ConfigMetaBase.cs b/app/MindWork AI Studio/Settings/ConfigMetaBase.cs index 43fb5c29..75a7ad6a 100644 --- a/app/MindWork AI Studio/Settings/ConfigMetaBase.cs +++ b/app/MindWork AI Studio/Settings/ConfigMetaBase.cs @@ -139,6 +139,29 @@ public abstract record ConfigMetaBase(string SettingName) : IConfig this.EditableDefaultByConfigPluginId = Guid.Empty; } + /// + /// Clears the editable-default state and hands the setting back to the user. + /// + /// + /// Without a snapshot of the user's value, the current value stays as it is. That is the + /// difference to a locked setting: the user was allowed to change an editable default all + /// along, so its value is a plausible choice of theirs. Resetting it to the app's default would + /// take away something nobody asked us to remove. + /// + /// + /// True when the user has changed the value in the meantime. Their decision outlives the + /// configuration plugin, so the snapshot is dropped instead of applied. + /// + public void ResetEditableDefaultConfiguration(bool keepCurrentValue) + { + this.ClearEditableDefaultConfiguration(); + + if (keepCurrentValue) + this.ClearUserValueSnapshot(); + else + this.TryRestoreUserValueSnapshot(); + } + /// /// Removes the contribution of one configuration plugin without changing the current value. /// diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs index 41ac1d54..ebd3f284 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs @@ -1024,7 +1024,7 @@ public static partial class ManagedConfiguration case false when configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT && TryGetEditableDefaultState(settingName, out var editableDefaultStateToRemove) && editableDefaultStateToRemove.ConfigPluginId == configPluginId: - configMeta.ClearEditableDefaultConfiguration(); + configMeta.ResetEditableDefaultConfiguration(HasUserChangedEditableDefault(configMeta, editableDefaultStateToRemove)); ClearEditableDefaultState(settingName); break; } diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs index 64a33bed..417d48be 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs @@ -361,6 +361,19 @@ public static partial class ManagedConfiguration configMeta.RemovePluginContribution(contributingConfigPluginId); wasChanged = true; } + + // + // Finally, drop any snapshot of the user's value which nobody claims anymore. Without + // this, a setting which stopped being managed outside of the paths above would keep its + // snapshot in the settings file forever. The persisted editable default counts as a + // claim as well: it survives a configuration plugin which is deployed but could not be + // loaded, and that plugin is still in charge: + // + if (configMeta.ManagedMode is null && !TryGetEditableDefaultState(configMeta.SettingName, out _) && configMeta.ClearUserValueSnapshot()) + { + Log.LogInformation($"Dropping the snapshot of the user's value for the setting '{configMeta.SettingName}': no configuration plugin manages it anymore."); + wasChanged = true; + } } // Remove persisted states which belong to settings that do not exist anymore: @@ -405,6 +418,13 @@ public static partial class ManagedConfiguration wasChanged = true; } + foreach (var settingName in configurationData.ManagedUserValueSnapshots.Keys.Where(x => !registeredSettingNames.Contains(x)).ToList()) + { + Log.LogInformation($"Removing the snapshot of the user's value for the setting '{settingName}': this setting does not exist anymore."); + configurationData.ManagedUserValueSnapshots.Remove(settingName); + wasChanged = true; + } + return wasChanged; } @@ -447,7 +467,7 @@ public static partial class ManagedConfiguration if (configMeta.ManagedMode is not ManagedConfigurationMode.EDITABLE_DEFAULT) return false; - configMeta.ClearEditableDefaultConfiguration(); + configMeta.ResetEditableDefaultConfiguration(keepCurrentValue: false); return true; } @@ -455,7 +475,17 @@ public static partial class ManagedConfiguration return false; Log.LogInformation($"Clearing the editable default of the setting '{configMeta.SettingName}': the configuration plugin '{editableDefaultState.ConfigPluginId}' is not available anymore."); - configMeta.ClearEditableDefaultConfiguration(); + configMeta.ResetEditableDefaultConfiguration(HasUserChangedEditableDefault(configMeta, editableDefaultState)); return ClearEditableDefaultState(configMeta.SettingName); } + + /// + /// Checks whether the user has changed an editable default themselves. + /// + /// + /// The user may change an editable default at any time. When the current value is not the one + /// the configuration plugin applied last, the user decided against that value, and their + /// decision outlives the plugin. + /// + private static bool HasUserChangedEditableDefault(ConfigMetaBase configMeta, ManagedEditableDefaultState editableDefaultState) => !string.Equals(configMeta.SerializeCurrentValue(), editableDefaultState.LastAppliedValue, StringComparison.Ordinal); } \ No newline at end of file