Skip to content

Stop treating position -1 as clean after trimming or saving - #79

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/unsaved-changes-at-initial-position
Sep 26, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/unsaved-changes-at-initial-position

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #76

Summary

SaveBoundaryManager assumed position -1 always holds the clean initial document. That stops being true in two cases, and it caused two bugs:

  • HasUnsavedChanges returned false at -1 whenever no boundaries existed. After MaxStackSize trimming, or after a branch removed the only save point, -1 holds a state that was never saved. An app that prompts "save before closing?" would skip the prompt and lose the edit.
  • AdjustPositions dropped any boundary shifted to -1, even though -1 is a reachable position that can be saved.

The fix tracks whether the initial state is clean with a private flag instead of assuming it:

  • The flag is set on construction and on Clear().
  • It is cleared by CreateSaveBoundary. Once something is saved, the file on disk no longer holds the initial state. The existing behavior already reports -1 as unsaved when boundaries exist.
  • It is cleared by a trimming AdjustPositions, because -1 then holds the result of the trimmed commands.
  • A boundary is now removed only when it moves below -1.

I did not seed a visible implicit boundary at -1 as the issue suggested. SaveBoundaries is public, and the existing tests assert its count, so a hidden flag gives the same result without changing what callers see.

Tests

Three new tests in UndoRedoStackTests, one for each scenario in the issue:

  • HasUnsavedChanges_AtStartAfterTrimming_IsTrue (A)
  • HasUnsavedChanges_AtStartAfterBranchRemovesTheOnlyBoundary_IsTrue (B)
  • AdjustPositions_BoundaryShiftedToStart_IsKept (C): exactly one boundary is left, at -1, and undoing to it reports saved.

With the fix reverted, all three fail. With the fix, the whole suite passes (53/53) on net10.0, including the existing HasUnsavedChanges and serialization round-trip tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L2BLMsT5ih3DnMNxTyGUHh


Generated by Claude Code

HasUnsavedChanges assumed position -1 was the untouched initial state
whenever no boundaries existed, so it reported false after MaxStackSize
trimming shifted a never-saved edit there, or after a branch removed
the only save point. AdjustPositions also discarded a boundary shifted
to exactly -1, although -1 is a reachable position.

Track whether the initial state is still clean explicitly: it is on
construction and Clear(), and stops being so once anything is saved or
commands are trimmed. Keep boundaries that land on -1.

Fixes #76

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2BLMsT5ih3DnMNxTyGUHh
Keeps them clear of the exception-safety tests added alongside, so the
two PRs merge independently.

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 6112b84 into main Sep 26, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the fix/unsaved-changes-at-initial-position branch September 26, 2026 03:48
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.

HasUnsavedChanges reports false for never-saved states at position -1 (after trimming or branching), risking silent data loss on close

2 participants