What's wrong
The static helpers AppData<T>.QueueSave() and SaveIfRequired() (AppDataStorage/AppData.cs:615,620) act on Get(), which is InternalState.Value, a Lazy<T> that runs its own LoadOrCreate (AppData.cs:410,415). The parameterless LoadOrCreate() (AppData.cs:~560-576) always deserializes a new object and never sets or returns InternalState.
So MySettings.LoadOrCreate() and MySettings.Get() are two independent in-memory copies of the same file:
- Edits made on one are invisible to the other.
- Whichever saves last overwrites the file.
The repo's CLAUDE.md usage example implies they are the same object:
var data = MyAppData.LoadOrCreate();
var same = MyAppData.Get(); // Singleton-like access
Failure scenarios
- Edits are silently lost.
var s = MySettings.LoadOrCreate();
s.Theme = "dark";
MySettings.QueueSave(); // queues the Get() singleton, not s
// ...3 s later...
MySettings.SaveIfRequired(); // writes the singleton's "light"
The file ends up with "Theme": "light". s never had a save queued, so its dispose-on-exit writes nothing. Verified with a scratch console app: same instance as Get(): False, and the disk shows "Theme": "light".
- Stale data overwrites newer data. Code saves
s.Save() from a LoadOrCreate() instance. Anything that later queues a save on a Get() singleton loaded earlier writes the old values back over the file.
Suggested fix
Pick one:
- Make the parameterless
LoadOrCreate() return InternalState.Value. The default-location instance is then the singleton, and the subdirectory and file-name overloads remain separate instances.
- Or let the first parameterless
LoadOrCreate() seed InternalState, and have later calls return it.
- At minimum, document on
LoadOrCreate() and on the static QueueSave/SaveIfRequired that they target different instances, and fix the CLAUDE.md example.
Acceptance criteria
ReferenceEquals(MySettings.LoadOrCreate(), MySettings.Get()) holds, or the docs clearly state it does not and the CLAUDE.md sample is corrected.
- A test shows that an edit made through the documented access path and saved via the static
QueueSave/SaveIfRequired reaches disk.
What's wrong
The static helpers
AppData<T>.QueueSave()andSaveIfRequired()(AppDataStorage/AppData.cs:615,620) act onGet(), which isInternalState.Value, aLazy<T>that runs its ownLoadOrCreate(AppData.cs:410,415). The parameterlessLoadOrCreate()(AppData.cs:~560-576) always deserializes a new object and never sets or returnsInternalState.So
MySettings.LoadOrCreate()andMySettings.Get()are two independent in-memory copies of the same file:The repo's CLAUDE.md usage example implies they are the same object:
Failure scenarios
"Theme": "light".snever had a save queued, so its dispose-on-exit writes nothing. Verified with a scratch console app:same instance as Get(): False, and the disk shows"Theme": "light".s.Save()from aLoadOrCreate()instance. Anything that later queues a save on aGet()singleton loaded earlier writes the old values back over the file.Suggested fix
Pick one:
LoadOrCreate()returnInternalState.Value. The default-location instance is then the singleton, and the subdirectory and file-name overloads remain separate instances.LoadOrCreate()seedInternalState, and have later calls return it.LoadOrCreate()and on the staticQueueSave/SaveIfRequiredthat they target different instances, and fix the CLAUDE.md example.Acceptance criteria
ReferenceEquals(MySettings.LoadOrCreate(), MySettings.Get())holds, or the docs clearly state it does not and the CLAUDE.md sample is corrected.QueueSave/SaveIfRequiredreaches disk.