Skip to content

Move the stack position only after Undo/Redo succeeds - #78

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/undo-redo-exception-safety
Sep 26, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/undo-redo-exception-safety

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #75

Summary

UndoAsync, RedoAsync and the loop in UndoToSaveBoundaryAsync moved the stack position before running the command. If the command threw, the position had moved but the application state had not:

  • A failed Undo left CanRedo == true for a command that was never undone. The next Undo skipped it.
  • A failed Redo left the command marked as applied. The next Undo would call Undo() on a change that was never made.

Each path now runs the command first and moves the position only on success. This is the same ordering the exception-safe Execute() already uses (#70). UndoToSaveBoundaryAsync stops at the first failure, with the position on the command that failed.

Tests

Three new tests in UndoRedoStackTests:

  • Undo_CommandThrowsException_LeavesPositionUnchanged: position, CanUndo and CanRedo are unchanged, and the next Undo retries B rather than skipping to A.
  • Redo_CommandThrowsException_LeavesPositionUnchanged: the failed command is still redoable and is not undoable.
  • UndoToSaveBoundary_CommandThrowsException_StopsWithAccuratePosition

With the fix reverted, all three fail on the position assertion. With the fix, the whole suite passes (53/53) on net10.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L2BLMsT5ih3DnMNxTyGUHh


Generated by Claude Code

UndoAsync, RedoAsync and UndoToSaveBoundaryAsync moved the position
before running the command, so a command that threw left the stack
out of step with the application: the next Undo skipped the failed
command, and a failed Redo could later be undone without ever having
been applied. Run the command first and move only on success, matching
the exception-safe Execute().

Fixes #75

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2BLMsT5ih3DnMNxTyGUHh
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit e7089e3 into main Sep 26, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the fix/undo-redo-exception-safety branch September 26, 2026 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Undo/Redo move the stack position before running the command, so a throwing command desyncs the stack from the app

2 participants