From 6a8381819211696ab6f5b5062427dc39f4e2d92b Mon Sep 17 00:00:00 2001 From: storybored379 Date: Sat, 3 Oct 2026 17:28:49 -0700 Subject: [PATCH] Library: an album source serializes (library.state no longer panics) LibrarySource was an internally tagged enum (tag = "kind") whose Album variant holds an AlbumId, and serde cannot serialize an integer newtype in an internally tagged enum. With an album open: - library.state unwrapped the error and panicked; over the control channel this closed the desktop app. - view.json (source, filter, sort, selection) was written empty (to_vec_pretty(...).unwrap_or_default()), so the view was lost on the next launch. Adjacent tagging (content = "id") keeps the unit variants as {"kind": "all"} and writes the album as {"kind": "album", "id": 8}: the same shape library.source takes, so the reported source can be passed back as is. Existing view.json files read unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/engine/src/tests_organize.rs | 24 ++++++++++++++++++++++++ crates/engine/src/view.rs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/tests_organize.rs b/crates/engine/src/tests_organize.rs index 9c0b73cfe..f40511f8f 100644 --- a/crates/engine/src/tests_organize.rs +++ b/crates/engine/src/tests_organize.rs @@ -176,3 +176,27 @@ fn stacks_group_collapse_and_replay() { assert_eq!(s2.catalog.stacks().count(), 1); let _ = std::fs::remove_dir_all(&dir); } + +#[test] +fn album_source_reports_in_library_state_and_persists_the_view() { + // An album source made `library.state` panic (serde can't put an integer in an internally + // tagged enum) and `view.json` empty, so the view was lost on the next launch. + let dir = temp_dir("album-view"); + let mut s = open(&dir); + let id = s.execute("album.create", &json!({"name": "Trip"})).unwrap()["id"].as_u64().unwrap(); + s.execute("library.source", &json!({"kind": "album", "id": id})).unwrap(); + s.execute("library.sort", &json!({"key": "fileName"})).unwrap(); + let st = s.execute("library.state", &json!({})).unwrap(); + assert_eq!(st["source"], json!({"kind": "album", "id": id})); + // the reported source is what `library.source` takes + s.execute("library.source", &json!({"kind": "all"})).unwrap(); + s.execute("library.source", &st["source"]).unwrap(); + assert_eq!(s.source, LibrarySource::Album(lightcraft_catalog::AlbumId(id))); + assert_eq!(s.execute("library.state", &json!({})).unwrap()["source"], json!({"kind": "album", "id": id})); + s.close_library().unwrap(); + drop(s); + let s2 = open(&dir); + assert_eq!(s2.source, LibrarySource::Album(lightcraft_catalog::AlbumId(id))); + assert_eq!(serde_json::to_value(s2.sort).unwrap()["key"], "fileName", "the rest of the view is restored too"); + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/crates/engine/src/view.rs b/crates/engine/src/view.rs index ad607d2b4..8047225f6 100644 --- a/crates/engine/src/view.rs +++ b/crates/engine/src/view.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; /// The "My Photos" source in the left panel. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "kind", rename_all = "camelCase")] +#[serde(tag = "kind", content = "id", rename_all = "camelCase")] pub enum LibrarySource { #[default] All,