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

commit94a71015a87a52e6103c65b7b3063b9780aef401
parentcb9a6cfe9a
authorLucas Galante <[email protected]>
date2026-08-28 12:16
fix: the node View toggle is project data

geometry_visible was #[serde(skip)], so toggling a node's View flag never
changed the serialized tree: the toggle was silently dropped on save, every
node loaded visible, and has_unsaved_changes() — the window-title asterisk —
could not see it. Serialize it (keeping the default-on fallback, so existing
saves load unchanged), which persists the flag and dirties the title for free.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/app.rs  |  1 -
 src/main.rs | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/app.rs b/src/app.rs
index e89c90d..9cbe520 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -129,7 +129,6 @@ pub struct FsNode {
     pub children: Vec<FsNode>,
     #[serde(default)]
     pub params: Vec<ParamDef>,
-    #[serde(skip)]
     #[serde(default = "default_node_geometry_visible")]
     pub geometry_visible: bool,
     #[serde(default = "default_node_position")]
diff --git a/src/main.rs b/src/main.rs
index a4f2656..aae1c3a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -102,6 +102,39 @@ mod tests {
         assert_eq!(DesignSettings::from_kdl_str(&none_kdl).default_project, None);
     }
 
+    /// The node View toggle is project data: it survives the JSON round trip
+    /// and counts as an unsaved change (the title asterisk). It was
+    /// `#[serde(skip)]`, which silently discarded every toggle on save and
+    /// kept `has_unsaved_changes` blind to it.
+    #[test]
+    fn test_view_toggle_is_project_data() {
+        let node = FsNode {
+            id: "t".to_string(),
+            name: "t".to_string(),
+            node_type: "node".to_string(),
+            children: vec![],
+            params: vec![],
+            geometry_visible: false,
+            position: (0.0, 0.0),
+            inputs: 1,
+            outputs: 1,
+        };
+        let back: FsNode =
+            serde_json::from_str(&serde_json::to_string(&node).unwrap()).unwrap();
+        assert!(!back.geometry_visible, "View toggle lost in the JSON round trip");
+
+        // Absence still defaults on, so pre-existing saves load unchanged.
+        let legacy: FsNode = serde_json::from_str(r#"{"name":"n"}"#).unwrap();
+        assert!(legacy.geometry_visible);
+
+        let mut state = State::new(false);
+        state.fs_root.children.push(back);
+        state.last_saved_root_json = serde_json::to_string(&state.fs_root).unwrap();
+        assert!(!state.has_unsaved_changes());
+        state.fs_root.children.last_mut().unwrap().geometry_visible = true;
+        assert!(state.has_unsaved_changes(), "the toggle must dirty the title");
+    }
+
     /// The button must exist on Main, inside the File section, before Exit.
     #[test]
     fn test_main_node_offers_set_as_default() {