From 696b64c30fca4c946eb485fa7180df234d8c4ab4 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:50:53 -0400 Subject: [PATCH] Session::opened identifies the open document It changes when file.open, file.recover or file.sample replaces the session and when geometry.open opens another design file or starts a new model; an edit, a solve or reopening the same design keep it. The desktop app frames the view on that change (#233). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/cmd/file.rs | 1 + crates/engine/src/cmd/geometry.rs | 5 ++++ crates/engine/src/lib.rs | 42 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 456bbc1..eeda4be 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -232,6 +232,7 @@ impl Session { opened.limits = self.limits; opened.file_access = std::mem::take(&mut self.file_access); opened.clear_history(); + opened.mark_opened(); *self = opened; } } diff --git a/crates/engine/src/cmd/geometry.rs b/crates/engine/src/cmd/geometry.rs index fcab884..3c2b30a 100644 --- a/crates/engine/src/cmd/geometry.rs +++ b/crates/engine/src/cmd/geometry.rs @@ -90,6 +90,11 @@ fn open(s: &mut Session, p: &Value) -> Result { "related": fit.related, "stale": {"mesh_bodies": fit.mesh_bodies, "sets": fit.sets, "seeds": fit.seeds}, }); + // Another design file (or a new model) is another document; the same file opened again after + // an edit is the same one. + if new_model || s.sources.design.as_ref().is_none_or(|d| d.path() != source.path()) { + s.mark_opened(); + } s.design = Some(Arc::new(design)); s.sources.design = Some(source); Ok(out) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 9549e17..ff123ed 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -228,6 +228,9 @@ pub struct Session { /// The folders commands may read and write files in (an automation client's roots, /// docs/mcp.md); unconfined by default. A file replacing the session keeps them. pub file_access: fieldcraft_io::roots::Roots, + /// Identifies the open document: it changes when another model, sample or design is opened + /// in place of the current one, never with an edit (see [`Session::opened`]). + opened: u64, } impl Session { @@ -280,6 +283,19 @@ impl Session { Ok(outputs) } + /// Identifies the open document: it changes when `file.open`, `file.recover` or `file.sample` + /// replaces the session and when `geometry.open` opens another design file or starts a new + /// model with one; an edit, a solve, undo, redo or opening the same design again after an + /// edit keep it. A view frames the model again when it changes. + pub fn opened(&self) -> u64 { + self.opened + } + + /// Make the session another document (see [`Session::opened`]). + pub(crate) fn mark_opened(&mut self) { + self.opened = next_revision(); + } + /// Identifies the current state (see [`Session::is_dirty`]). pub fn revision(&self) -> u64 { self.revision @@ -699,6 +715,32 @@ mod tests { s } + /// `opened` changes with another document (a sample, another design file, a new model) and + /// stays with an edit, a solve or the same design opened again: views frame on that change. + #[test] + fn the_open_document_changes_only_when_another_is_opened() { + let mut s = Session::default(); + let start = s.opened(); + s.execute("file.sample", &json!({"name": "cantilever"})).unwrap(); + let cantilever = s.opened(); + assert_ne!(cantilever, start); + s.execute("solve.run", &json!({})).unwrap(); + s.execute("model.material.create", &json!({"name": "Al", "elastic": {"type": "isotropic", "young": 70000, "poisson": 0.33}})).unwrap(); + assert_eq!(s.opened(), cantilever, "edits and solves keep the document"); + s.execute("file.sample", &json!({"name": "cantilever"})).unwrap(); + assert_ne!(s.opened(), cantilever, "a sample opened again is a fresh start"); + + let (_dir, path) = sample_design_file(&Plate::default()); + let before = s.opened(); + s.execute("geometry.open", &json!({"path": path})).unwrap(); + let design = s.opened(); + assert_ne!(design, before, "another design file"); + s.execute("geometry.open", &json!({"path": path})).unwrap(); + assert_eq!(s.opened(), design, "the same design opened again after an edit"); + s.execute("geometry.open", &json!({"path": path, "model": "new"})).unwrap(); + assert_ne!(s.opened(), design, "a new model"); + } + #[test] fn reopening_the_same_design_keeps_the_mesh_and_definitions() { let (_dir, path) = sample_design_file(&Plate::default());