ui: File › New starts an empty model (file.new, #144); an unknown sample name opens the samples (#200)

file.new replaces the session with an empty one: no design, mesh, definitions, results or
journal, nothing to save, the undo history empty. The File menu and Cmd/Ctrl+N run it, asking
first over unsaved changes. A sample name the app does not know (a typo, an old ?sample= link)
opens the samples to choose from, and the status names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 11:07:46 -04:00
1 parent 92b6225a72
commit 0625125ed7
7 files changed
+81 -5

No files matched your search

+2 -1
View File
@@ -585,7 +585,8 @@ pub static CATALOGUE: &[Capability] = &[
// ---- Tools ----
// Renaming starts from a definition's item menu (Rename…); the search finds it here too.
ready_more("model.info", "Model Info", Tools, "Model", "Node and element counts and the model summary", &["model.rename"]),
ready("file.open", "Open Model", Tools, "File", "Open a .fieldcraft model"),
// New starts from the File menu; the search finds it here too.
ready_more("file.open", "Open Model", Tools, "File", "Open a .fieldcraft model", &["file.new"]),
ready("file.sample", "Samples", Tools, "File", "Open a sample model, ready to solve"),
ready("file.save", "Save", Tools, "File", "Save the model to its .fieldcraft file"),
ready("file.save_as", "Save As", Tools, "File", "Save the model to a new .fieldcraft file"),
+32
View File
@@ -19,6 +19,16 @@ use fieldcraft_io::roots::{Access, Roots};
use super::{file_param, req};
use crate::{CommandKind, CommandSpec, EngineError, Result, Session, next_revision};
pub const NEW: CommandSpec = CommandSpec {
id: "file.new",
label: "New Model",
params: r#"{} — starts an empty model in place of the current one: no design, mesh, definitions, results or journal; unsaved changes are discarded (the window asks first) and the undo history starts empty"#,
form: Some(&[]),
kind: CommandKind::Session,
requires: &[],
run: new_model,
};
pub const OPEN: CommandSpec = CommandSpec {
id: "file.open",
label: "Open Model",
@@ -89,6 +99,15 @@ pub const SAMPLE: CommandSpec = CommandSpec {
run: open_sample,
};
fn new_model(s: &mut Session, _: &Value) -> Result<Value> {
let mut empty = Session::default();
// Nothing to save until a command changes it.
empty.saved_revision = Some(empty.revision);
empty.saved_view = empty.view.clone();
s.replace_with(empty);
Ok(summary(s, &[]))
}
fn open_sample(s: &mut Session, p: &Value) -> Result<Value> {
let name: &str = req(p, "name")?;
let names = || crate::sample::SAMPLES.iter().map(|x| x.name).collect::<Vec<_>>().join(", ");
@@ -375,6 +394,19 @@ mod tests {
s.execute("model.material.create", &json!({"name": "Steel", "elastic": {"type": "isotropic", "young": 210000.0, "poisson": 0.3}})).unwrap();
}
/// `file.new` empties the model, its journal and its undo history, and has nothing to save.
#[test]
fn new_starts_an_empty_model() {
let mut s = Session::default();
s.execute("file.sample", &json!({"name": "cantilever"})).unwrap();
s.execute("mesh.node.add", &json!({"label": 10_000, "xyz": [0, 0, 0]})).unwrap();
let r = s.execute("file.new", &json!({})).unwrap();
assert_eq!((r["nodes"].as_u64(), r["elements"].as_u64(), r["journal"].as_u64()), (Some(0), Some(0), Some(0)), "{r}");
assert!(s.model.materials().is_empty() && s.model.steps().is_empty() && s.design.is_none() && s.sample.is_none());
assert!(!s.is_dirty(), "an empty model has nothing to save");
assert!(s.execute("edit.undo", &json!({})).is_err(), "the undo history starts empty");
}
#[test]
fn save_and_open_keep_the_model_journal_and_design() {
let d = dir("roundtrip");
+1
View File
@@ -135,6 +135,7 @@ pub static ALL: &[CommandSpec] = &[
define::STEP_ACTIVATE,
define::RENAME,
define::ACTIVITY,
file::NEW,
file::OPEN,
file::SAVE,
file::SAVE_AS,