From fb8de10b99670933175e0fc0e0186fab2e63b809 Mon Sep 17 00:00:00 2001 From: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> Date: Sun, 18 Oct 2026 04:25:51 +1000 Subject: [PATCH] LRC-DEV-AUTOSYNC: sync HDR state while retaining per-photo tuning Turning HDR off with Auto Sync active now turns it off for the other selected photos. Enabling HDR preserves existing per-photo HDR tuning. Co-authored-by: Codex --- crates/engine/src/lib.rs | 7 ++++- crates/engine/src/tests.rs | 61 ++++++++++++++++++++++++++++++++++++++ docs/parity-checklist.md | 1 + 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 8a282ce3e..bb035e944 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -900,7 +900,12 @@ impl Session { return Vec::new(); } let Some(old) = self.develop_of(id) else { return Vec::new() }; - let Some(mut delta) = json_delta(&old.to_json(), &new.to_json()) else { return Vec::new() }; + // Compare actual HDR leaves even when persisted defaults omit the whole HDR object. + let mut old_json = old.to_json(); + let mut new_json = new.to_json(); + old_json["hdr"] = serde_json::json!(old.hdr); + new_json["hdr"] = serde_json::json!(new.hdr); + let Some(mut delta) = json_delta(&old_json, &new_json) else { return Vec::new() }; if let Some(o) = delta.as_object_mut() { for k in ["spots", "red_eye", "version", "process"] { o.remove(k); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 400135ce0..abfde0fad 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -917,6 +917,24 @@ fn auto_sync_carries_only_the_changed_settings() { assert_eq!(s.develop_of(PhotoId(ids[1])).unwrap().light.exposure, 0.7); } +#[test] +fn auto_sync_hdr_off_returns_selected_photos_to_sdr() { + use lightcraft_catalog::PhotoId; + let mut s = demo(); + let ids: Vec = s.catalog.photos().take(2).map(|p| p.id.0).collect(); + assert_eq!(ids.len(), 2); + for id in &ids { + assert_eq!(s.develop_of(PhotoId(*id)).unwrap().hdr, lightcraft_develop::Hdr::default(), "default-HDR fixture"); + } + s.execute("library.select", &json!({"ids": ids, "active": ids[0]})).unwrap(); + s.execute("develop.autoSync", &json!({"on": true})).unwrap(); + let enabled = |s: &Session| ids.iter().map(|id| s.develop_of(PhotoId(*id)).unwrap().hdr.enabled).collect::>(); + s.execute("develop.hdr", &json!({"enabled": true})).unwrap(); + assert_eq!(enabled(&s), vec![true, true], "HDR On must reach both selected photos"); + s.execute("develop.hdr", &json!({"enabled": false})).unwrap(); + assert_eq!(enabled(&s), vec![false, false], "HDR Off must reach both selected photos"); +} + #[test] fn json_delta_keeps_only_changes() { let a = json!({"light": {"exposure": 0, "contrast": 5}, "masks": [1], "x": 1}); @@ -1682,3 +1700,46 @@ fn malformed_photo_targets_are_refused_not_widened() { let preset = s.presets.first().unwrap().id.clone(); s.execute("preset.apply", &json!({"id": preset})).unwrap(); } + +#[test] +fn auto_sync_hdr_enabled_preserves_per_photo_tuning() { + use lightcraft_develop::{DevelopSettings, Hdr, ProcessVersion}; + let mut s = demo(); + let ids: Vec<_> = s.catalog.photos().take(2).map(|p| p.id).collect(); + assert_eq!(ids.len(), 2); + s.execute("develop.autoSync", &json!({"on": false})).unwrap(); + // Keep distinct stored processes explicit: the default is now V2; no rendering claim is made. + let mut expected = [DevelopSettings::default(), DevelopSettings { process: ProcessVersion(2), ..DevelopSettings::default() }]; + expected[0].process = ProcessVersion(1); + expected[0].light.exposure = 0.5; + expected[0].light.contrast = 12.0; + expected[1].light.exposure = -0.25; + expected[1].light.contrast = -22.0; + expected[1].hdr = Hdr { + enabled: false, + max_ev: 4.0, + sdr_brightness: 11.0, + sdr_contrast: -12.0, + sdr_highlights: 13.0, + sdr_shadows: -14.0, + sdr_whites: 15.0, + sdr_clarity: -16.0, + }; + for (id, settings) in ids.iter().zip(&expected) { + s.set_develop(*id, settings.clone(), "HDR tuning fixture").unwrap(); + } + s.execute("library.select", &json!({"ids": ids.iter().map(|id| id.0).collect::>(), "active": ids[0].0})).unwrap(); + s.execute("develop.autoSync", &json!({"on": true})).unwrap(); + let selection = s.selection.clone(); + for enabled in [true, false] { + s.execute("develop.hdr", &json!({"enabled": enabled})).unwrap(); + for (id, settings) in ids.iter().zip(&mut expected) { + settings.hdr.enabled = enabled; + let actual = s.develop_of(*id).unwrap(); + assert_eq!(actual.hdr, settings.hdr, "HDR tuning must survive enabled={enabled} for photo {id:?}"); + assert_eq!(actual.as_ref(), &*settings, "other per-photo edits and process must survive enabled={enabled}"); + } + assert_eq!(s.selection, selection, "selection and active photo must survive enabled={enabled}"); + assert!(s.auto_sync, "Auto Sync must remain enabled"); + } +} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 95dcd2aef..c014f03b9 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -834,6 +834,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | 2026-10-12 | minor | LR-IMP-CAMERA-COVERAGE: Pentax PEFs without area tags lose their masked border lines (issue #1271); status unchanged (🟡) | | 2026-10-12 | minor | Camera-look deep shadows follow the JPEG toe (#1307); camera calibration remains partial | | 2026-10-11 | minor | LR-IMP-PREVIEW-READ: a preview reads the raw's header and its embedded JPEG instead of the whole file (a 56 MB RAF: 56.5 MB → 5.9 MB, ~10 s → 0.96 s over SMB, byte-identical pixels), and an unedited raw's grid thumbnail is that camera JPEG itself, so no sensor decode is queued behind it. A CR3's JPEG track and a TIFF raw whose preview tag sits past the prefix are read through `lightcraft_raw::sparse::preview_plan`, which names the ranges `embedded_preview` reads; the formats it cannot describe (CRW/MRW/X3F, NEF/ORF/PEF) keep the whole-file path | +| 2026-10-11 | minor | LRC-DEV-AUTOSYNC: preserve per-photo HDR tuning when synchronizing HDR enabled state; status unchanged. | | 2026-10-12 | minor | LR-BEHAV-ACCESS: canvas handles by keyboard with AccessKit names and values; Tab order test; status stays 🟡 | | 2026-10-12 | minor | LR-BEHAV-ACCESS: AccessKit tree audit test; text fields named, sliders expose range and Increment / Decrement; status stays 🟡 | | 2026-10-12 | minor | LR-PREF-DISPLAY: on Linux, previews follow the display profile the desktop assigns in colord |