Problem
SettingsManager is a mutable god object with ~100 public properties. Every load/save cycle requires manual mapping in three places: the property declaration, the Load() method, and ToSettingsData(). This is extremely error-prone — it is easy to add a property and forget to persist it, causing silent data loss.
Additionally, Load() parses JSON twice: once via JsonDocument for legacy credential migration, then again via SettingsData.FromJson.
Impact
- Adding a new setting requires manual edits in 3+ locations.
- Silent bugs where the UI shows a setting but it never round-trips to disk (or vice versa).
- Legacy migration logic is mixed with active persistence logic.
Suggested fix
- Make
SettingsData the single source of truth — an immutable record with init-only properties.
- Have
SettingsManager hold a private SettingsData _data field and expose accessors that delegate to it:
public string GatewayUrl
{
get => _data.GatewayUrl;
set => _data = _data with { GatewayUrl = value };
}
- Replace the giant manual
Load() / ToSettingsData() copy blocks with a single JsonSerializer.Deserialize<SettingsData>(json) call, using fallback defaults (data ?? SettingsData.Default).
- Use a custom
JsonConverter for DPAPI-protected secrets so encryption is transparent to the model.
Files
src/OpenClaw.Tray.WinUI/Services/SettingsManager.cs
Problem
SettingsManageris a mutable god object with ~100 public properties. Every load/save cycle requires manual mapping in three places: the property declaration, theLoad()method, andToSettingsData(). This is extremely error-prone — it is easy to add a property and forget to persist it, causing silent data loss.Additionally,
Load()parses JSON twice: once viaJsonDocumentfor legacy credential migration, then again viaSettingsData.FromJson.Impact
Suggested fix
SettingsDatathe single source of truth — an immutable record with init-only properties.SettingsManagerhold a privateSettingsData _datafield and expose accessors that delegate to it:Load()/ToSettingsData()copy blocks with a singleJsonSerializer.Deserialize<SettingsData>(json)call, using fallback defaults (data ?? SettingsData.Default).JsonConverterfor DPAPI-protected secrets so encryption is transparent to the model.Files
src/OpenClaw.Tray.WinUI/Services/SettingsManager.cs