git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitea700d64c37737874ebd5f98f4600a3a8311bc6d
parent31cc59b23e
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-29 17:48
fix: a project loaded before the first configure opens clean

The startup load takes its saved baseline while the plates sit on the
placeholder window size; the first configure re-applies the saved plate
fractions at the real size, which moved the layout key and marked the
project changed on every launch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

 src/app.rs  |  9 +++++++++
 src/main.rs | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/src/app.rs b/src/app.rs
index a20768c..8ce821b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -8690,7 +8690,16 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
 
             self.window_configured = true;
             if let Some(pg) = self.pending_plates.take() {
+                // The load took its saved baseline with the plates on the
+                // placeholder size. Landing them on the real one is the load
+                // finishing, not an edit — unless something was already
+                // unsaved, which stays so.
+                let clean = !self.has_unsaved_changes();
                 self.apply_plate_geometry(pg);
+                if clean {
+                    self.last_saved_layout_json = self.pane_layout_json();
+                    self.update_window_title();
+                }
             }
 
             self.sync_layout();
diff --git a/src/main.rs b/src/main.rs
index 512c88a..5a62d82 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1429,6 +1429,38 @@ mod tests {
         let _ = fs::remove_dir_all(&dir);
     }
 
+    /// The startup load takes its saved baseline before the first configure,
+    /// with the plates on the placeholder size; the configure then lands
+    /// them on the real one. That is the load finishing, not an edit, so
+    /// the project opens clean.
+    #[test]
+    fn a_project_loaded_before_the_first_configure_opens_clean() {
+        let dir = std::env::temp_dir().join(format!("cce_designer_cleanopen_{}", std::process::id()));
+        let _ = fs::remove_dir_all(&dir);
+        fs::create_dir_all(&dir).unwrap();
+        let mut a = State::new(false);
+        a.resize(1400.0, 1080.0, 1.0);
+        a.floating_network_layout.2 = 350.0;
+        a.floating_param_width = 310.0;
+        a.rebuild_positions();
+        a.save_to_file(&dir).expect("save");
+
+        let mut b = State::new(false);
+        b.load_from_file(&dir).expect("load");
+        assert!(!b.has_unsaved_changes(), "a fresh load is clean");
+        b.resize(1400.0, 1080.0, 1.0);
+        assert!(!b.has_unsaved_changes(), "the first configure is not an edit");
+
+        // What was unsaved before the configure still is after it.
+        let mut c = State::new(false);
+        c.load_from_file(&dir).expect("load");
+        c.set_pane_collapsed(crate::slots::PARAM_IDX, true);
+        c.resize(1400.0, 1080.0, 1.0);
+        assert!(c.has_unsaved_changes(), "the configure must not hide an edit");
+
+        let _ = fs::remove_dir_all(&dir);
+    }
+
     /// A window briefly narrower than its plates squeezes them for as long as
     /// it lasts and no longer: the layout used to store the clamped size, so
     /// one transient shrink (a re-tile, a configure at startup) left every