What's wrong
The #124 fix (PR #139) made KeybindingManager.SaveAsync (Keybinding/KeybindingManager.cs:113-146) keep stored profiles this manager never loaded, and SaveWithoutInitializeTests.SaveAsync_WithoutInitialize_KeepsStoredProfiles pins that. The other two files SaveAsync writes are still overwritten wholesale, though:
- L118-119:
Repository.SaveCommandsAsync(Commands.GetAllCommands()) replaces commands.json with only the commands in memory.
- L144-145:
Repository.SaveActiveProfileAsync(activeProfile?.Id) replaces active-profile.json, writing null when nothing is active.
JsonKeybindingRepository writes both files outright, with no merge.
Failure scenario (reproduced with a throwaway MSTest)
- Manager A calls
InitializeAsync, registers file.save, calls CreateDefaultProfile(), binds Ctrl+S → file.save, then calls SaveAsync.
- Manager B uses the same directory and never calls
InitializeAsync. It calls Profiles.CreateProfile("other", "Other"), then SaveAsync.
- Manager C calls
InitializeAsync.
Result: the profiles are other, default, there are 0 commands, and active is null. ExecuteChord("default", Ctrl+S) returns null.
The profile and its binding survive, as #124 intended, but the command the binding points to is gone. ExecuteChord skips unregistered commands, so the binding is dead, and the user's active profile is lost too. Keeping the profiles was the point of #124, and this makes that only half-effective.
Suggested fix
- Handle commands the way profiles are handled now. Track the command ids this manager loaded or saved. On save, merge with the stored
commands.json, and remove only tracked ids that have since been unregistered.
- Write
active-profile.json only if this manager loaded it or changed the active profile. Otherwise leave it alone.
Acceptance criteria
- The scenario above ends with
file.save still registered, default still active, and Ctrl+S resolving to file.save.
- A command unregistered by a manager that did load it is still removed on save.
- Tests extend
SaveWithoutInitializeTests to cover both.
Related but distinct: #148 (concurrent saves) and #93 (atomic writes).
What's wrong
The #124 fix (PR #139) made
KeybindingManager.SaveAsync(Keybinding/KeybindingManager.cs:113-146) keep stored profiles this manager never loaded, andSaveWithoutInitializeTests.SaveAsync_WithoutInitialize_KeepsStoredProfilespins that. The other two filesSaveAsyncwrites are still overwritten wholesale, though:Repository.SaveCommandsAsync(Commands.GetAllCommands())replacescommands.jsonwith only the commands in memory.Repository.SaveActiveProfileAsync(activeProfile?.Id)replacesactive-profile.json, writingnullwhen nothing is active.JsonKeybindingRepositorywrites both files outright, with no merge.Failure scenario (reproduced with a throwaway MSTest)
InitializeAsync, registersfile.save, callsCreateDefaultProfile(), binds Ctrl+S →file.save, then callsSaveAsync.InitializeAsync. It callsProfiles.CreateProfile("other", "Other"), thenSaveAsync.InitializeAsync.Result: the profiles are
other, default, there are 0 commands, and active is null.ExecuteChord("default", Ctrl+S)returnsnull.The profile and its binding survive, as #124 intended, but the command the binding points to is gone.
ExecuteChordskips unregistered commands, so the binding is dead, and the user's active profile is lost too. Keeping the profiles was the point of #124, and this makes that only half-effective.Suggested fix
commands.json, and remove only tracked ids that have since been unregistered.active-profile.jsononly if this manager loaded it or changed the active profile. Otherwise leave it alone.Acceptance criteria
file.savestill registered,defaultstill active, and Ctrl+S resolving tofile.save.SaveWithoutInitializeTeststo cover both.Related but distinct: #148 (concurrent saves) and #93 (atomic writes).