LRC-LIB-SMARTCOLL: a stale album rule no longer drops the filter bar from the view

From review: view_rules merged the filter bar's settings onto a smart
album's rules through merge_rules, which now also checks the rules, and
fell back to the bare album rules on any problem. A smart album testing
an album that was later deleted therefore lost the filter bar (e.g.
Rating ≥ 3) from Create Smart Album from Filter and the dialog's count,
without a word.

merge_filter does the merge alone; merge_rules is merge_filter plus the
check, for album.createSmart / setRules. view_rules uses merge_filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-09 10:54:57 -04:00
co-authored by Claude Opus 5.5
parent 3c80695e97
commit 2d29dde4d0
2 changed files with 27 additions and 3 deletions
+11 -3
View File
@@ -1278,10 +1278,16 @@ pub fn specs() -> Vec<CommandSpec> {
}
/// `base` with a partial Filter (JSON) merged on top.
fn merge_rules(base: &lightcraft_catalog::Filter, patch: &Value, c: &str, cat: &lightcraft_catalog::Catalog) -> Result<lightcraft_catalog::Filter> {
/// `patch` merged onto `base`, unchecked (see [`merge_rules`]).
fn merge_filter(base: &lightcraft_catalog::Filter, patch: &Value, c: &str) -> Result<lightcraft_catalog::Filter> {
let mut v = serde_json::to_value(base).unwrap_or_default();
lightcraft_develop::presets::deep_merge(&mut v, patch);
let f: lightcraft_catalog::Filter = serde_json::from_value(v).map_err(|e| bad(c, e.to_string()))?;
serde_json::from_value(v).map_err(|e| bad(c, e.to_string()))
}
/// `patch` merged onto `base`, refused when its rule set has problems (`RuleSet::check`).
fn merge_rules(base: &lightcraft_catalog::Filter, patch: &Value, c: &str, cat: &lightcraft_catalog::Catalog) -> Result<lightcraft_catalog::Filter> {
let f = merge_filter(base, patch, c)?;
let problems = f.rule_set.as_ref().map(|r| r.check(cat)).unwrap_or_default();
if !problems.is_empty() {
return Err(bad(c, problems.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ")));
@@ -1320,7 +1326,9 @@ fn view_rules(s: &Session) -> lightcraft_catalog::Filter {
}
}
}
merge_rules(&base, &Value::Object(patch), "", &s.catalog).unwrap_or(base)
// the view as it is: a rule that no longer checks (its album was deleted) just matches
// nothing there, and mustn't cost the filter bar's settings
merge_filter(&base, &Value::Object(patch), "").unwrap_or(base)
}
None => {
let mut f = s.source.to_filter(&s.filter, &s.catalog);
+16
View File
@@ -79,6 +79,22 @@ fn smart_album_from_smart_album_view_keeps_its_rules() {
assert_eq!(s.source, LibrarySource::Album(lightcraft_catalog::AlbumId(id)));
}
/// Saving the view of a smart album keeps the filter bar's settings even when the album holds a
/// rule that no longer checks (it tests an album that has since been deleted).
#[test]
fn smart_album_view_keeps_the_filter_bar_over_a_stale_rule() {
let mut s = Session::with_demo();
let gone = s.execute("album.create", &json!({"name": "Gone", "addSelected": false})).unwrap()["id"].as_u64().unwrap();
let rules = json!({"ruleSet": {"rules": [{"field": "album", "op": "isNot", "value": gone}]}});
let id = s.execute("album.createSmart", &json!({"name": "Not in Gone", "rules": rules})).unwrap()["id"].as_u64().unwrap();
s.execute("album.delete", &json!({"id": gone})).unwrap();
s.execute("library.source", &json!({"kind": "album", "id": id})).unwrap();
s.execute("library.filter", &json!({"rating": 3})).unwrap();
let r = s.view_rules();
assert_eq!(r.rating, 3, "the filter bar's rating survives");
assert!(r.rule_set.is_some(), "and so do the album's own rules");
}
#[test]
fn virtual_copies_are_independent_and_persist() {
let dir = temp_dir("vc");