From d6f328df33aa666e2825ef48a10a52d54a0eb979 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Thu, 22 Oct 2026 21:19:21 -0400 Subject: [PATCH] LR-LIB-SMARTALBUM: editing a smart album made from an album view keeps the album album.setRules with replace (what the rules dialog sends) started from an empty filter and kept only the library folder, so a view of an album saved as a smart album matched the whole library after OK. It keeps the album too, unless the call gives album: null. --- crates/engine/src/cmd/library/specs.rs | 7 ++++++- crates/engine/src/tests/library.rs | 18 ++++++++++++++++++ docs/whats-new.md | 3 +++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/library/specs.rs b/crates/engine/src/cmd/library/specs.rs index 07ab6f221..4c5cb595f 100644 --- a/crates/engine/src/cmd/library/specs.rs +++ b/crates/engine/src/cmd/library/specs.rs @@ -874,7 +874,7 @@ pub fn specs() -> Vec { view } else { let replace = bool_or(p, "replace", false); - let folder = cur.library_folder.clone(); + let (folder, album) = (cur.library_folder.clone(), cur.album); let base = if replace { Default::default() } else { cur }; let mut r = merge_rules(&base, p.get("rules").unwrap_or(&Value::Null), "album.setRules", &s.catalog, Some(id))?; // the rules dialog has no folder field: replacing its rules keeps the folder @@ -882,6 +882,11 @@ pub fn specs() -> Vec { if replace && p.get("rules").and_then(|r| r.get("libraryFolder")).is_none() { r.library_folder = folder; } + // nor one for the album or folder of albums a saved view is limited to + // (`album: null` drops it) + if replace && p.get("rules").and_then(|r| r.get("album")).is_none() { + r.album = album; + } r }; let set = Op::SetAlbumRules { id, rules: Box::new(rules) }; diff --git a/crates/engine/src/tests/library.rs b/crates/engine/src/tests/library.rs index b2eb8cb94..2fafdb9df 100644 --- a/crates/engine/src/tests/library.rs +++ b/crates/engine/src/tests/library.rs @@ -654,6 +654,24 @@ fn set_rules_with_a_name_is_one_step() { assert_eq!(album(&s).name, "Old"); } +/// The rules dialog sends `replace` and has no album field: a view of an album saved as a smart +/// album stays limited to that album after OK, until a call says `album: null`. +#[test] +fn editing_a_smart_album_made_from_an_album_view_keeps_the_album() { + use lightcraft_catalog::AlbumId; + let mut s = crate::Session::with_demo(); + let trip = s.execute("album.create", &serde_json::json!({"name": "Trip", "addSelected": false})).unwrap()["id"].as_u64().unwrap(); + let all = s.catalog.photos().map(|p| p.id.0).collect::>(); + s.execute("album.addPhotos", &serde_json::json!({"id": trip, "ids": [all[0], all[1]]})).unwrap(); + s.execute("library.source", &serde_json::json!({"kind": "album", "id": trip})).unwrap(); + let id = s.execute("album.createSmart", &serde_json::json!({"name": "From Trip"})).unwrap()["id"].as_u64().unwrap(); + let album_of = |s: &crate::Session| s.catalog.album(AlbumId(id)).unwrap().smart.as_ref().unwrap().album; + let r = s.execute("album.setRules", &serde_json::json!({"id": id, "replace": true, "rules": {"ruleSet": {"rules": []}}})).unwrap(); + assert_eq!((album_of(&s), r["count"].as_u64()), (Some(AlbumId(trip)), Some(2)), "still limited to the album: {r}"); + let r = s.execute("album.setRules", &serde_json::json!({"id": id, "replace": true, "rules": {"album": null}})).unwrap(); + assert_eq!(album_of(&s), None, "and dropped on purpose: {r}"); +} + /// albums.list says which smart albums have rules that no longer check, and why. #[test] fn album_list_reports_problems() { diff --git a/docs/whats-new.md b/docs/whats-new.md index c6c259200..6691716aa 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -82,6 +82,9 @@ library you open and are imported into it, as files given on the command line are; presets dropped then are refused with a note, since they belong to a library. +### Editing a smart album made from an album keeps the album +- A smart album saved from the view of an album stays limited to that album when you edit its rules and press OK. It used to match the whole library afterwards, because the rules dialog has no album field and replaced the album along with the rules. + ## 2026-10-22 ### Reliability