Files
ClassicAssist.Avalonia/.qoder/repowiki/knowledge/en/JSON-based Profile and Settings System with ISettingProvider Plugin Architecture/JSON-based Profile and Settings System with ISettingProvider Plugin Architecture.md
T
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

49 lines
6.0 KiB
Markdown

---
kind: configuration_system
name: JSON-based Profile and Settings System with ISettingProvider Plugin Architecture
category: configuration_system
scope:
- '**'
source_files:
- ClassicAssist.Shared/Data/Options.cs
- ClassicAssist.Shared/Data/AssistantOptions.cs
- ClassicAssist.Shared/Misc/ISettingProvider.cs
- ClassicAssist/Engine.cs
- ClassicAssist.Avalonia/Engine.cs
- ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs
- ClassicAssist.Launcher/ClassicOptions.cs
- ClassicAssist.Launcher/ClassicOptionAttribute.cs
---
ClassicAssist uses a multi-layered, JSON-backed configuration system centered on per-user profiles, global assistant settings, and a pluggable serialization contract. There is no centralized config framework — instead, the codebase defines explicit model classes for each concern and wires them together via events and reflection.
## What system/approach is used
- **File format**: Plain JSON files serialized/deserialized with Newtonsoft.Json (JObject, JArray). No YAML/TOML/env-file support.
- **Profile model**: Options (in ClassicAssist.Shared/Data/Options.cs) represents user-editable runtime options; it exposes an INotifyPropertyChanged property bag plus static Load(string, Options) / Save(Options) that read/write a file named by Name under a Profiles/ directory next to the executable. A default profile name constant is "settings.json".
- **Global assistant settings**: AssistantOptions (in ClassicAssist.Shared/Data/AssistantOptions.cs) is a static class holding cross-cutting state such as last profile, language override, window size, auto-backup policy, saved passwords, and plugin assembly list. It persists to Assistant.json in the startup path.
- **Launcher CLI flags**: The WPF launcher (ClassicAssist.Launcher) parses command-line arguments into a ClassicOptions POCO using a custom [ClassicOption] attribute and emits a Launcher.json file. This is separate from the main client's settings.
- **Pluggable section serializers**: Each UI tab ViewModel implements ISettingProvider (Serialize(JObject), Deserialize(JObject, Options)). During load/save, the WPF/Avalonia Engine walks all registered BaseViewModel instances, casts to ISettingProvider, and delegates per-section persistence. This lets each feature own its JSON keys without a central schema registry.
## Key files and packages
- ClassicAssist.Shared/Data/Options.cs — main profile model, CurrentOptions singleton, Load/Save, profile enumeration, SmartTargetOption flag enum.
- ClassicAssist.Shared/Data/AssistantOptions.cs — global app-level settings, password vault, profile linking by player serial, language/culture switching, auto-backup of the Profiles/ folder.
- ClassicAssist.Shared/Misc/ISettingProvider.cs — contract every feature ViewModel must implement to participate in profile save/load.
- ClassicAssist/Engine.cs (WPF host) and ClassicAssist.Avalonia/Engine.cs (Avalonia host) — subscribe to Options.SaveEvent / Options.LoadEvent, iterate BaseViewModel.Instances, call Serialize/Deserialize on any ISettingProvider.
- ClassicAssist.Shared/UI/ViewModels/*TabViewModel.cs — concrete ISettingProvider implementations (General, Hotkeys, Macros, Agents tabs, etc.) that add their own JSON sections keyed by category names like "General".
- ClassicAssist.Launcher/ClassicOptions.cs + ClassicAssist.Launcher/ClassicOptionAttribute.cs — attribute-driven CLI-to-POCO mapping for the launcher process.
## Architecture and conventions
- **Per-profile JSON**: User data lives in <StartupPath>/Profiles/<profile-name>.json. Profiles are plain JSON objects whose shape is defined by the union of all ISettingProvider implementations plus the top-level Name field.
- **Global vs profile split**: AssistantOptions holds application-wide, non-gameplay settings (language, window geometry, backup policy, saved passwords, plugin assemblies). Options.CurrentOptions holds gameplay/profile-specific toggles. They are persisted to different files (Assistant.json vs Profiles/*.json).
- **Event-driven composition**: Options raises SaveEvent / LoadEvent carrying a shared JObject. Each ISettingProvider adds or reads its own sub-object (e.g., obj["General"] = ...). The Engine orchestrates discovery via BaseViewModel.Instances; there is no DI container.
- **Feature-owned sections**: New features register themselves by implementing ISettingProvider and adding a new key under the root JSON object. There is no central schema validator — forward/backward compatibility relies on defensive null checks when reading unknown keys.
- **Password handling**: Saved credentials are stored in Assistant.json under a "SavedPasswords" array; values are encrypted via Crypter.Encrypt/Decrypt before write/read.
- **Launcher separation**: The launcher is a distinct process with its own ClassicOptions POCO and Launcher.json. It does not share the Options/AssistantOptions models.
## Rules developers should follow
1. Add a new setting → add a property to Options (with a backing field and SetProperty helper), wire it into one or more ISettingProvider implementations' Serialize/Deserialize, and ensure defaults are handled defensively in Deserialize.
2. Persisting a new feature → implement ISettingProvider on the owning ViewModel and add a unique top-level JSON key (e.g., "MyFeature") inside Serialize; read it back in Deserialize with null-safe accessors.
3. Do not mutate Options.CurrentOptions directly from background threads — use the dispatcher/UI thread where required, mirroring how existing properties trigger side effects (e.g., LightLevel sends a packet).
4. Keep sensitive data out of Options — use AssistantOptions.SavedPasswords and rely on Crypter for encryption; never store plaintext secrets in profile JSON.
5. Profile migration — because loading is JSON-passthrough, introduce new fields with sensible fallbacks in Deserialize so older profiles remain valid.
6. Launcher flags — define new CLI switches by adding a property to ClassicOptions decorated with [ClassicOption(...)]; do not mix these with the client's Options model.