What's wrong
JsonUndoRedoSerializer writes NavigationContext and Metadata for every command. On load (ConvertFromSerializableCommand in UndoRedo/Services/JsonUndoRedoSerializer.cs), only the PlaceholderCommand fallback reads them back.
A command that implements ISerializableCommand is rebuilt with Activator.CreateInstance(commandType) followed by DeserializeData(data). It keeps whatever its parameterless constructor set:
NavigationContext is null;
ChangeMetadata is the default (size 1, timestamp = load time, and so on).
So a serializable command, the only kind that survives a round trip as a real, undoable command, is the one that loses its navigation and metadata.
Failure scenario
sealed class SetText : BaseCommand, ISerializableCommand
{
public string Text = "";
public SetText() : base(ChangeType.Modify, ["doc"]) { }
public SetText(string t, string nav, int size) : base(ChangeType.Modify, ["doc"], nav, size) { Text = t; }
public override string Description => $"Set {Text}";
public override void Execute() { }
public override void Undo() { }
public string SerializeData() => Text;
public void DeserializeData(string d) => Text = d;
}
source.Execute(new SetText("hello", "line:42", 7));
string data = await source.SaveStateAsync();
await target.LoadStateAsync(data); // target has a navigation provider set
await target.UndoAsync();
- Actual: after the load the command is a
SetText with NavigationContext == null, change size 1 and a timestamp equal to the load time. UndoAsync navigates nowhere.
- Expected:
NavigationContext == "line:42", the size and timestamp are preserved, and UndoAsync navigates to line:42, as it does before the save.
In an editor, this means undo/redo stops jumping to the edited location after the app is restarted and history is restored. That is the main point of persisting navigation context.
This is related to #91, which covers save-boundary timestamps, but it is a separate gap: per-command navigation and metadata are dropped on reconstruction.
Suggested fix
Add an internal hook to BaseCommand, such as RestoreSerializedState(string? navigationContext, ChangeMetadata metadata). The relevant setters are already protected set. Call it in ConvertFromSerializableCommand after DeserializeData, whenever the instance is a BaseCommand.
Acceptance criteria
- A round-trip test through
SaveStateAsync/LoadStateAsync for an ISerializableCommand preserves NavigationContext and Metadata (change type, affected items, size, timestamp).
- After the load,
UndoAsync calls the navigation provider with the original context.
What's wrong
JsonUndoRedoSerializerwritesNavigationContextandMetadatafor every command. On load (ConvertFromSerializableCommandinUndoRedo/Services/JsonUndoRedoSerializer.cs), only thePlaceholderCommandfallback reads them back.A command that implements
ISerializableCommandis rebuilt withActivator.CreateInstance(commandType)followed byDeserializeData(data). It keeps whatever its parameterless constructor set:NavigationContextisnull;ChangeMetadatais the default (size 1, timestamp = load time, and so on).So a serializable command, the only kind that survives a round trip as a real, undoable command, is the one that loses its navigation and metadata.
Failure scenario
SetTextwithNavigationContext == null, change size 1 and a timestamp equal to the load time.UndoAsyncnavigates nowhere.NavigationContext == "line:42", the size and timestamp are preserved, andUndoAsyncnavigates toline:42, as it does before the save.In an editor, this means undo/redo stops jumping to the edited location after the app is restarted and history is restored. That is the main point of persisting navigation context.
This is related to #91, which covers save-boundary timestamps, but it is a separate gap: per-command navigation and metadata are dropped on reconstruction.
Suggested fix
Add an internal hook to
BaseCommand, such asRestoreSerializedState(string? navigationContext, ChangeMetadata metadata). The relevant setters are alreadyprotected set. Call it inConvertFromSerializableCommandafterDeserializeData, whenever the instance is aBaseCommand.Acceptance criteria
SaveStateAsync/LoadStateAsyncfor anISerializableCommandpreservesNavigationContextandMetadata(change type, affected items, size, timestamp).UndoAsynccalls the navigation provider with the original context.