From b8744f6d3310454d0332bf2b7476108a9f9003d2 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:16:46 +0200 Subject: [PATCH 1/2] Stamp File Info dates only after a save succeeds (#1721) --- crates/engine/src/cmd/fileio/save.rs | 44 +++++++++++++++++++-------- crates/engine/src/cmd/fileio/tests.rs | 38 +++++++++++++++++++++++ 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/crates/engine/src/cmd/fileio/save.rs b/crates/engine/src/cmd/fileio/save.rs index b4279335..99f29e27 100644 --- a/crates/engine/src/cmd/fileio/save.rs +++ b/crates/engine/src/cmd/fileio/save.rs @@ -93,7 +93,10 @@ impl SavePlan { /// File Info's dates for a save to a file: the modified date (and the created date, when it has /// none) become `at`. Not an undo step. pub fn stamp_save_dates(st: &mut DocState, at: i64) { - let d = std::sync::Arc::make_mut(&mut st.doc); + stamp_doc_dates(std::sync::Arc::make_mut(&mut st.doc), at); +} + +fn stamp_doc_dates(d: &mut Document, at: i64) { d.metadata.created.get_or_insert(at); d.metadata.modified = Some(at); } @@ -296,6 +299,10 @@ pub struct SaveJob { params: Value, /// The document when the job was made: what counts as saved once the file is written. snapshot: Arc, + /// The live document `snapshot` was made from, and the date stamped on `snapshot` (not on the + /// live document until the file is written). + source: Arc, + stamped: Option, /// The artboards also saved to files of their own (`separateArtboards`). boards: Vec, } @@ -417,24 +424,29 @@ pub(super) fn encode_ai(cmd: &str, f: &Format, doc: &Document, p: &Value) -> Res Ok(Encoded { warnings, ..Encoded::one(bytes) }) } -/// Snapshot the active document for `plan` (stamping File Info's dates when the file becomes the -/// document's own). Bad options fail here, before anything is encoded. +/// Snapshot the active document for `plan` (stamping File Info's dates on the snapshot when the +/// file becomes the document's own; the live document gets them in [`SaveJob::finish`]). Bad +/// options fail here, before anything is encoded. pub fn save_job(s: &mut Session, plan: SavePlan) -> Result { // The web build, without a clock, leaves the dates unless given one. - if plan.path.is_some() - && plan.retargets() - && let Some(at) = clock_date(s, "modified", plan.modified) - { - stamp_save_dates(s.doc_mut()?, at); - } - job_for(s, s.doc()?, plan) + let at = if plan.path.is_some() && plan.retargets() { clock_date(s, "modified", plan.modified) } else { None }; + job_for_stamped(s, s.doc()?, plan, at) } /// Snapshot `st` (any open document of `s`) for `plan`, without stamping dates (Data Recovery /// snapshots documents that aren't active). pub(crate) fn job_for(s: &Session, st: &DocState, plan: SavePlan) -> Result { + job_for_stamped(s, st, plan, None) +} + +fn job_for_stamped(s: &Session, st: &DocState, plan: SavePlan, stamped: Option) -> Result { let cmd = plan.mode.command(); - let own = doc_to_save(st, plan.format); + let mut own = doc_to_save(st, plan.format); + let mut snapshot = st.doc.clone(); + if let Some(at) = stamped { + stamp_doc_dates(Arc::make_mut(&mut own), at); + stamp_doc_dates(Arc::make_mut(&mut snapshot), at); + } // A native file records its links' paths relative to where it is written. let relative = plan.path.as_deref().filter(|_| is_native(plan.format)).and_then(|p| crate::cmd::links::with_relative_paths(&own, p)); let doc = relative.map_or(own, Arc::new); @@ -455,7 +467,7 @@ pub(crate) fn job_for(s: &Session, st: &DocState, plan: SavePlan) -> Result Date: Wed, 14 Oct 2026 03:02:45 +0700 Subject: [PATCH 2/2] Background save edited meanwhile: undoing back to the saved state is unmodified (#1721) --- crates/engine/src/cmd/fileio/save.rs | 5 ++++- crates/engine/src/cmd/fileio/tests.rs | 3 +++ crates/ui-egui/src/tests_background.rs | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/fileio/save.rs b/crates/engine/src/cmd/fileio/save.rs index 99f29e27..fdc8584c 100644 --- a/crates/engine/src/cmd/fileio/save.rs +++ b/crates/engine/src/cmd/fileio/save.rs @@ -609,8 +609,11 @@ impl SaveJob { if Arc::ptr_eq(&st.doc, &self.source) { st.doc = self.snapshot.clone(); } else { - // Edited while the save ran: the dates still go on, the edits stay unsaved. + // Edited while the save ran: the dates still go on, the edits stay unsaved. The + // undo history holds the document as it was before the dates, so that is the + // saved state: undoing back to it leaves the document unmodified. stamp_save_dates(st, at); + return st.mark_saved_as(&self.source); } } st.mark_saved_as(&self.snapshot); diff --git a/crates/engine/src/cmd/fileio/tests.rs b/crates/engine/src/cmd/fileio/tests.rs index 540ef77c..c3b49ca2 100644 --- a/crates/engine/src/cmd/fileio/tests.rs +++ b/crates/engine/src/cmd/fileio/tests.rs @@ -523,4 +523,7 @@ fn a_failed_save_does_not_stamp_the_documents_dates() { assert_eq!(st.doc.metadata.modified, Some(777)); assert_eq!(st.path.as_deref(), Some("/x/z.vectorcraft")); assert!(st.is_dirty()); + // Undoing the edit is back at what the file holds. + s.execute("edit.undo", &json!({})).unwrap(); + assert!(!s.doc().unwrap().is_dirty()); } diff --git a/crates/ui-egui/src/tests_background.rs b/crates/ui-egui/src/tests_background.rs index c0b92a49..bd10987e 100644 --- a/crates/ui-egui/src/tests_background.rs +++ b/crates/ui-egui/src/tests_background.rs @@ -70,8 +70,9 @@ fn a_50k_path_save_returns_at_once_and_the_file_appears_later() { assert!(written(&files).is_empty(), "nothing written yet"); assert_eq!(app.background.jobs.len(), 1); assert!(app.ui.status.starts_with("Saving big.vectorcraft")); - // The document is saved once the file is written. - assert!(app.session.doc().unwrap().is_dirty() && app.session.doc().unwrap().path.is_none()); + // The file is the document's once it is written (and its dates are stamped then, #1721: until + // then this document, never edited, is as it was). + assert!(!app.session.doc().unwrap().is_dirty() && app.session.doc().unwrap().path.is_none()); gate.open(); background::wait_all(&mut app); assert_eq!(written(&files), ["/docs/big.vectorcraft"]);