Skip to content

fix(undo): drop a document's undo stack when its file is rewritten while switched away#78

Merged
luca-chen198 merged 1 commit into
mainfrom
fix/undo-stale-range-on-external-rewrite
Jun 23, 2026
Merged

fix(undo): drop a document's undo stack when its file is rewritten while switched away#78
luca-chen198 merged 1 commit into
mainfrom
fix/undo-stale-range-on-external-rewrite

Conversation

@luca-chen198

Copy link
Copy Markdown
Member

Problem

Per-document undo (#77) makes a document's undo stack survive switching away and back. But AppKit's text undo records absolute character ranges, so if a document's file is rewritten externally while it's switched away, switch-back reloads the changed text and Cmd+Z replays stale ranges against it.

Concrete repro in the embedder (wiki-link notes): edit note A (which links [[B]]) → switch to B → rename B (after the debounce the rename rewrites A's file on disk, changing the [[label]] length) → switch back to A → Cmd+Z corrupts A's text (misplaced edit), or silently no-ops when the stale range is now out of bounds.

Before #77 this couldn't happen — undo was wiped on every switch, so switch-back Cmd+Z was an empty no-op. Surviving undo is the feature; this PR makes it safe.

Fix

  • Snapshot each document's content (storage form) on switch-away (undoContentSnapshots, from lastSyncedText).
  • On switch-back, invalidateUndoIfContentDiverged(for:incomingText:) drops that document's undo stack when the reloaded text differs from the snapshot (timing-independent; the SwiftUI two-phase switch is covered by the existing lastSyncedText == text early-return, so the switch branch runs once with the real reloaded text).
  • Snapshots are pruned alongside the per-document undo managers.
  • Corrects the now-stale documentId doc comment ("Changing this invalidates undo history" → undo now survives switches).

Net: +/- across 3 source files + a focused test. No public API change.

Test

swift test — 131 pass (128 existing + 3 new in PerDocumentUndoTests): stable manager per document / distinct across / original returned on switch-back; undo dropped only on diverged content; no snapshot never clears. Also verified end-to-end in a Debug build (rename-linked-node repro no longer corrupts; normal Cmd+Z-survives-switch still works).

🤖 Generated with Claude Code

…ile switched away

Per-document undo (#77) makes a document's undo stack survive switching away
and back. But the stack records absolute text ranges, so if the document's
file is rewritten externally while backgrounded — e.g. renaming a node rewrites
the [[label]] in every file that links it — switch-back reloads the changed
text and Cmd+Z replays stale ranges, corrupting the text (or silently no-op'ing
when a range is out of bounds).

Snapshot each document's content (storage form) on switch-away and, on
switch-back, drop its undo stack when the reloaded text differs. Snapshots are
pruned alongside the per-document undo managers. Also corrects the now-stale
`documentId` doc comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@luca-chen198 luca-chen198 merged commit 71bb6e6 into main Jun 23, 2026
1 check passed
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.

1 participant