From 23c8dfa9743f9608ef9432c4609aaffb5004b8bc Mon Sep 17 00:00:00 2001 From: Mat Strange Date: Tue, 20 Oct 2026 13:20:50 +0100 Subject: [PATCH] A version snapshot belongs to the document it was taken from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Session::set_document` resets everything about the open document — the document id, the document, the selection, the undo history, the password, the file name — but not the version snapshots. A snapshot taken of one document therefore survived `file.new`, `file.open`, `file.newFromTemplate` and `file.recover`, and `file.versions {"restore": index}` then put the previous document's content into the open one. The snapshots go with the document they were taken from: `set_document` clears them, so a restore can only ever bring back a version of the document that is open. Every caller replaces the document with another one (a new or opened file, a recovered copy, a merge result, a sheet of labels), so none of them loses anything it needs. Co-Authored-By: opencode --- crates/engine/src/session.rs | 4 ++++ crates/engine/src/tests.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/engine/src/session.rs b/crates/engine/src/session.rs index 1b79dbbdd0..d522e0a11d 100644 --- a/crates/engine/src/session.rs +++ b/crates/engine/src/session.rs @@ -1216,6 +1216,10 @@ impl Session { self.go_back_step = 0; self.view.outline.collapsed.clear(); self.reset_history(); + // A version snapshot belongs to the document it was taken from: keeping the old ones + // would let a restore put the previous document's content into this one (#1327 + // follow-up). + self.versions.clear(); self.touch(); self.dirty = false; self.relayout(); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index fda7aa5c09..eb157a80b1 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -8300,6 +8300,35 @@ fn rename_unsaved_document_names_the_file_not_the_document() { assert!(s.file_name.is_none() && s.path.is_none()); } +/// A version snapshot belongs to the document it was taken from: opening another document (or +/// starting a new one) starts a new version history, so a restore can't put the previous +/// document's content into the open one (#1327 follow-up). +#[test] +fn a_version_snapshot_does_not_survive_a_document_switch() { + let mut s = Session::new(wordcraft_doc::Document::from_text("First document.")); + s.run("file.versions", &json!({"save": "snapshot"})).unwrap(); + assert_eq!(s.versions.len(), 1); + + // A new document: the snapshot of the previous one is gone with it. + s.run("file.new", &json!({})).unwrap(); + assert!(s.versions.is_empty(), "the snapshot of the previous document survived the switch"); + assert!(s.run("file.versions", &json!({"restore": 0})).is_err(), "the old snapshot is still restorable"); + assert_ne!(s.doc.plain_text(StoryRef::Body), "First document.", "the old document's content came back"); + + // The same for an opened file. + s.run("file.versions", &json!({"save": "snapshot"})).unwrap(); + s.run("file.open", &json!({"path": "other.txt", "data": "aGk="})).unwrap(); + assert!(s.versions.is_empty(), "the snapshot of the previous document survived the switch"); + assert!(s.run("file.versions", &json!({"restore": 0})).is_err(), "the old snapshot is still restorable"); + assert_eq!(s.doc.plain_text(StoryRef::Body), "hi"); + + // A snapshot of the open document still restores into it. + s.run("file.versions", &json!({"save": "snapshot"})).unwrap(); + s.run("text.insert", &json!({"text": " there"})).unwrap(); + s.run("file.versions", &json!({"restore": 0})).unwrap(); + assert_eq!(s.doc.plain_text(StoryRef::Body), "hi"); +} + /// #1344: File › Info › Inspect Document finds hidden text by its effective formatting (direct, /// character style, paragraph style) in every story — body, header, footer, footnote, comment, /// text box — and Remove All deletes exactly that text, in one undo step, through a DOCX save.