What's wrong
UndoRedoService.GetChangeVisualizations (UndoRedo/Services/UndoRedoService.cs) captures some values when it is called: commands.Count (inside Take) and currentPosition. It then returns a deferred LINQ Select over the live _stackManager.Commands, with the saveBoundaries list also read at enumeration time.
GetCommandsToUndo is deferred in the same way.
Enumerating the result after the stack changes mixes the old snapshot with the new state.
Failure scenario
service.Execute(Cmd("A"));
var viz = service.GetChangeVisualizations();
service.Execute(Cmd("C"));
foreach (var v in viz) Console.WriteLine($"{v.Command.Description} executed={v.IsExecuted}");
- Actual:
C is listed with IsExecuted = False, even though it is the current, applied command. currentPosition is stale while the command list is live.
- In a similar case, the list is cut at the old count while
HasSaveBoundary reflects the new boundaries.
GetCommandsToUndo(boundary) taken at position 1 and enumerated after an Undo() still returns [A, B].
Any UI that caches the returned IEnumerable (for example a history panel bound to it, or one enumerated on a later frame or on another thread) shows wrong executed/saved markers. With concurrent modification it can also hit InvalidOperationException from the underlying list.
Suggested fix
Materialize both results before returning them ([.. query] / .ToList()), so each call returns a consistent snapshot of its own moment.
Acceptance criteria
- A result taken before an
Execute/Undo and enumerated afterwards reflects the state at the time of the call.
- This is covered by a test for each of the two methods.
What's wrong
UndoRedoService.GetChangeVisualizations(UndoRedo/Services/UndoRedoService.cs) captures some values when it is called:commands.Count(insideTake) andcurrentPosition. It then returns a deferred LINQSelectover the live_stackManager.Commands, with thesaveBoundarieslist also read at enumeration time.GetCommandsToUndois deferred in the same way.Enumerating the result after the stack changes mixes the old snapshot with the new state.
Failure scenario
Cis listed withIsExecuted = False, even though it is the current, applied command.currentPositionis stale while the command list is live.HasSaveBoundaryreflects the new boundaries.GetCommandsToUndo(boundary)taken at position 1 and enumerated after anUndo()still returns[A, B].Any UI that caches the returned
IEnumerable(for example a history panel bound to it, or one enumerated on a later frame or on another thread) shows wrong executed/saved markers. With concurrent modification it can also hitInvalidOperationExceptionfrom the underlying list.Suggested fix
Materialize both results before returning them (
[.. query]/.ToList()), so each call returns a consistent snapshot of its own moment.Acceptance criteria
Execute/Undoand enumerated afterwards reflects the state at the time of the call.