From d8f62a29b2f06f71a1f5fa20b36088def66d76eb Mon Sep 17 00:00:00 2001 From: Matthew James Sivak Date: Mon, 12 Oct 2026 15:11:27 +0200 Subject: [PATCH 1/3] LR-EDIT-SECTION-SOLO: Solo Mode for Edit sections Window > Edit Sections > Solo Mode (view.soloSections {on?}) and a right-click on any section header turn it on: opening one section closes the others. Option-click on a header opens just that section once. Kept with the UI state (singlePanel). Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/locales/de.json | 1 + crates/ui-egui/locales/es.json | 1 + crates/ui-egui/locales/fr.json | 1 + crates/ui-egui/locales/ja.json | 1 + crates/ui-egui/locales/pt-br.json | 1 + crates/ui-egui/locales/ru.json | 1 + crates/ui-egui/locales/uk.json | 1 + crates/ui-egui/locales/zh-hans.json | 1 + crates/ui-egui/locales/zh-hant.json | 1 + crates/ui-egui/src/lib.rs | 2 + crates/ui-egui/src/menubar.rs | 1 + crates/ui-egui/src/menus.rs | 7 ++ crates/ui-egui/src/panels/edit.rs | 18 ++++- crates/ui-egui/src/state.rs | 13 ++- crates/ui-egui/src/tests_solo_sections.rs | 96 +++++++++++++++++++++++ docs/parity-checklist.md | 14 ++-- 16 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 crates/ui-egui/src/tests_solo_sections.rs diff --git a/crates/ui-egui/locales/de.json b/crates/ui-egui/locales/de.json index 25c411ce0..516d85101 100644 --- a/crates/ui-egui/locales/de.json +++ b/crates/ui-egui/locales/de.json @@ -529,6 +529,7 @@ "Filmstrip (/)": "Filmstreifen (/)", "Filter": "Filter", "Filter Bar": "Filterleiste", + "Solo Mode": "Solo-Modus", "Filter Presets": "Filtervorgaben", "Filter bar": "Filterleiste", "Filter fields": "Filterfelder", diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json index ba3e1e331..444791c77 100644 --- a/crates/ui-egui/locales/es.json +++ b/crates/ui-egui/locales/es.json @@ -511,6 +511,7 @@ "Filmstrip (/)": "Tira de película (/)", "Filter": "Filtro", "Filter Bar": "Barra de filtros", + "Solo Mode": "Modo solo", "Filter Presets": "Ajustes preestablecidos de filtro", "Filter bar": "Barra de filtros", "Filter fields": "Campos de filtro", diff --git a/crates/ui-egui/locales/fr.json b/crates/ui-egui/locales/fr.json index b0becb154..1644721b1 100644 --- a/crates/ui-egui/locales/fr.json +++ b/crates/ui-egui/locales/fr.json @@ -494,6 +494,7 @@ "Filmstrip (/)": "Pellicule (/)", "Filter": "Filtre", "Filter Bar": "Barre de filtres", + "Solo Mode": "Mode solo", "Filter Presets": "Préréglages de filtre", "Filter bar": "Barre de filtres", "Filter fields": "Champs de filtre", diff --git a/crates/ui-egui/locales/ja.json b/crates/ui-egui/locales/ja.json index 7a282e676..42e9f15e7 100644 --- a/crates/ui-egui/locales/ja.json +++ b/crates/ui-egui/locales/ja.json @@ -709,6 +709,7 @@ "Filmstrip": "フィルムストリップ", "Filter": "フィルター", "Filter Bar": "フィルターバー", + "Solo Mode": "ソロモード", "Filter Presets": "フィルタープリセット", "Filter bar": "フィルターバー", "Filter fields": "項目を絞り込む", diff --git a/crates/ui-egui/locales/pt-br.json b/crates/ui-egui/locales/pt-br.json index 2e21c727c..32b84bf99 100644 --- a/crates/ui-egui/locales/pt-br.json +++ b/crates/ui-egui/locales/pt-br.json @@ -502,6 +502,7 @@ "Filmstrip (/)": "Película (/)", "Filter": "Filtro", "Filter Bar": "Barra de filtros", + "Solo Mode": "Modo solo", "Filter Presets": "Predefinições de filtro", "Filter bar": "Barra de filtros", "Filter fields": "Campos de filtro", diff --git a/crates/ui-egui/locales/ru.json b/crates/ui-egui/locales/ru.json index c840218a0..b8250c70d 100644 --- a/crates/ui-egui/locales/ru.json +++ b/crates/ui-egui/locales/ru.json @@ -412,6 +412,7 @@ "Filmstrip (/)": "Лента (/)", "Filter": "Фильтр", "Filter Bar": "Панель фильтров", + "Solo Mode": "Режим соло", "Filter Presets": "Пресеты фильтров", "Filter bar": "Панель фильтров", "Filter fields": "Поля фильтра", diff --git a/crates/ui-egui/locales/uk.json b/crates/ui-egui/locales/uk.json index 85a838098..960351baf 100644 --- a/crates/ui-egui/locales/uk.json +++ b/crates/ui-egui/locales/uk.json @@ -915,6 +915,7 @@ "Fill Edges": "Заповнити краї", "Filmstrip": "Стрічка", "Filter Bar": "Панель фільтрів", + "Solo Mode": "Режим соло", "Filter Presets": "Набори налаштувань фільтрів", "Filter bar": "Панель фільтрів", "Filter fields": "Поля фільтрів", diff --git a/crates/ui-egui/locales/zh-hans.json b/crates/ui-egui/locales/zh-hans.json index ad379f1ef..b80b4f45b 100644 --- a/crates/ui-egui/locales/zh-hans.json +++ b/crates/ui-egui/locales/zh-hans.json @@ -652,6 +652,7 @@ "Filmstrip (/)": "胶片窗格 (/)", "Filter": "过滤器", "Filter Bar": "过滤器栏", + "Solo Mode": "单独模式", "Filter Presets": "过滤器预设", "Filter bar": "过滤器栏", "Filter fields": "过滤字段", diff --git a/crates/ui-egui/locales/zh-hant.json b/crates/ui-egui/locales/zh-hant.json index 647d153d0..5de08731f 100644 --- a/crates/ui-egui/locales/zh-hant.json +++ b/crates/ui-egui/locales/zh-hant.json @@ -652,6 +652,7 @@ "Filmstrip (/)": "底片列(/)", "Filter": "篩選", "Filter Bar": "篩選列", + "Solo Mode": "單獨模式", "Filter Presets": "篩選預設集", "Filter bar": "篩選列", "Filter fields": "篩選欄位", diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 8248ee5a1..59df7e913 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -110,6 +110,8 @@ mod tests_quit_unsaved; #[cfg(test)] mod tests_scroll; #[cfg(test)] +mod tests_solo_sections; +#[cfg(test)] mod tests_switch_library; #[cfg(test)] mod tests_sync; diff --git a/crates/ui-egui/src/menubar.rs b/crates/ui-egui/src/menubar.rs index 04eea2439..0e16f3e4e 100644 --- a/crates/ui-egui/src/menubar.rs +++ b/crates/ui-egui/src/menubar.rs @@ -328,6 +328,7 @@ pub fn checked(app: &LightcraftApp, id: &str) -> Option { "view.beforeAfterSplit" => Some(u.before_after == crate::state::BeforeAfter::Split), "view.autoAdvance" => Some(u.auto_advance), "view.filterBar" => Some(u.filter_bar), + "view.soloSections" => Some(u.single_panel), "view.navigator" => Some(u.navigator), "view.fullScreenPreview" => Some(u.fullscreen), "view.lightsOut" => Some(u.lights_out != crate::state::LightsOut::On), diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 141d6d782..eda08c733 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -129,6 +129,8 @@ pub const UI_COMMANDS: &[UiCommand] = &[ ("section.effects", "Effects", Some("Cmd+3"), "Window>Edit Sections"), ("section.detail", "Detail", Some("Cmd+4"), "Window>Edit Sections"), ("section.optics", "Optics", Some("Cmd+5"), "Window>Edit Sections"), + // {on?}; no params toggles. Opening one Edit section closes the others. + ("view.soloSections", "Solo Mode", None, "Window>Edit Sections"), ("tool.brush", "Brush", Some("B"), "Window>Tools"), ("tool.linear", "Linear Gradient", Some("L"), "Window>Tools"), ("tool.radial", "Radial Gradient", Some("R"), "Window>Tools"), @@ -953,6 +955,11 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option { + let on = p.get("on").and_then(Value::as_bool).unwrap_or(!app.ui.single_panel); + app.ui.set_single_panel(on); + Ok(json!({"soloSections": app.ui.single_panel})) + } "view.filterBar" => { app.ui.filter_bar = !app.ui.filter_bar; if app.ui.filter_bar && !matches!(app.ui.view, ViewMode::PhotoGrid | ViewMode::SquareGrid) { diff --git a/crates/ui-egui/src/panels/edit.rs b/crates/ui-egui/src/panels/edit.rs index 76a54171f..d4a7aa11f 100644 --- a/crates/ui-egui/src/panels/edit.rs +++ b/crates/ui-egui/src/panels/edit.rs @@ -531,8 +531,24 @@ fn section( if let Some(on) = toggled { let _ = app.run("develop.sectionEnabled", json!({"section": id, "enabled": on})); } else if resp.clicked() { - app.ui.toggle_section(id); + // ⌥-click opens just this section, as in Lightroom Classic + if ui.input(|i| i.modifiers.alt) { + app.ui.solo_section(id); + } else { + app.ui.toggle_section(id); + } } + resp.context_menu(|ui| { + let mut solo = app.ui.single_panel; + let item = ui.checkbox(&mut solo, crate::i18n::tr("Solo Mode")); + register(ui.ctx(), "sectionMenu:solo", item.rect); + if item.clicked() { + if let Err(e) = app.run("view.soloSections", json!({"on": solo})) { + app.toast(ui.ctx(), e); + } + ui.close(); + } + }); if open { body(app, ui, d); } diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 6c4dd3f6a..828e72041 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -523,7 +523,7 @@ pub struct UiState { pub open_sections: Vec, /// Open flyouts (curve, mixer, grading…). pub open_flyouts: Vec, - /// Single-panel mode: opening one section closes the others. + /// Solo Mode (`view.soloSections`): opening one Edit section closes the others. pub single_panel: bool, pub show_clipping: bool, pub histogram: bool, @@ -1177,6 +1177,17 @@ impl UiState { self.open_sections.push(id.to_string()); } } + /// Opens `id` and closes every other Edit section (⌥-click on a section header). + pub fn solo_section(&mut self, id: &str) { + self.open_sections = vec![id.to_string()]; + } + /// Turns Solo Mode on or off. Turning it on keeps only the section opened last. + pub fn set_single_panel(&mut self, on: bool) { + self.single_panel = on; + if on && let Some(last) = self.open_sections.pop() { + self.open_sections = vec![last]; + } + } /// Whether the sidebar section with this id is folded shut (an unknown id: no). pub fn sidebar_section_collapsed(&self, id: &str) -> bool { crate::sidebar::SidebarSection::from_id(id).is_some_and(|s| self.sidebar.is_collapsed(s)) diff --git a/crates/ui-egui/src/tests_solo_sections.rs b/crates/ui-egui/src/tests_solo_sections.rs new file mode 100644 index 000000000..35c74de48 --- /dev/null +++ b/crates/ui-egui/src/tests_solo_sections.rs @@ -0,0 +1,96 @@ +//! Headless tests of Solo Mode: opening one Edit section closes the others (Lightroom Classic's +//! panel Solo Mode), from the Window menu command, the section header's menu and ⌥-click. + +use std::time::Duration; + +use serde_json::json; + +use crate::headless::Headless; +use crate::{LightcraftApp, Services}; + +const T: Duration = Duration::from_secs(20); + +/// Detail view with the Edit panel open and Light (the default) expanded. +fn edit_open() -> Headless { + let services = Services { png: None, ..Default::default() }; + let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), services); + let mut h = Headless::new(app, [1200.0, 1400.0], 1.0); + let r = h.request("ui.set", json!({"view": "detail"}), T); + assert_eq!(r["ok"], true, "{r}"); + h.app.ui.right = crate::state::RightPanel::Edit; + h.app.ui.open_sections = vec!["light".into()]; + h.step(); + h.step(); + h +} + +fn exec(h: &mut Headless, command: &str, params: serde_json::Value) -> serde_json::Value { + let r = h.request("engine.execute", json!({"command": command, "params": params}), T); + assert_eq!(r["ok"], true, "{command}: {r}"); + r["result"].clone() +} + +fn click(h: &mut Headless, id: &str, button: Option<&str>) { + let params = match button { + Some(b) => json!({"id": id, "button": b}), + None => json!({"id": id}), + }; + let r = h.request("ui.clickWidget", params, T); + assert_eq!(r["ok"], true, "click {id}: {r}"); + h.step(); +} + +#[test] +fn solo_mode_closes_the_other_sections_when_one_opens() { + let mut h = edit_open(); + click(&mut h, "section:color", None); + assert_eq!(h.app.ui.open_sections, ["light", "color"], "off: sections open side by side"); + + assert_eq!(exec(&mut h, "view.soloSections", json!({})), json!({"soloSections": true})); + assert_eq!(h.app.ui.open_sections, ["color"], "turning it on keeps the section opened last"); + assert_eq!(crate::menubar::checked(&h.app, "view.soloSections"), Some(true)); + h.step(); + + click(&mut h, "section:detail", None); + assert_eq!(h.app.ui.open_sections, ["detail"]); + // the Window > Edit Sections shortcuts follow it too + exec(&mut h, "section.optics", json!({})); + assert_eq!(h.app.ui.open_sections, ["optics"]); + // closing the open one leaves none open + exec(&mut h, "section.optics", json!({})); + assert!(h.app.ui.open_sections.is_empty()); + + assert_eq!(exec(&mut h, "view.soloSections", json!({"on": false})), json!({"soloSections": false})); + exec(&mut h, "section.light", json!({})); + exec(&mut h, "section.color", json!({})); + assert_eq!(h.app.ui.open_sections, ["light", "color"]); +} + +#[test] +fn the_section_header_menu_turns_solo_mode_on() { + let mut h = edit_open(); + click(&mut h, "section:color", None); + click(&mut h, "section:effects", Some("right")); + click(&mut h, "sectionMenu:solo", None); + assert!(h.app.ui.single_panel, "the header's Solo Mode item turns it on"); + assert_eq!(h.app.ui.open_sections, ["color"]); +} + +#[test] +fn option_click_opens_just_that_section() { + let mut h = edit_open(); + click(&mut h, "section:color", None); + let r = h.request("ui.clickWidget", json!({"id": "section:detail", "alt": true}), T); + assert_eq!(r["ok"], true, "{r}"); + h.step(); + assert_eq!(h.app.ui.open_sections, ["detail"], "⌥-click closes the others"); + assert!(!h.app.ui.single_panel, "⌥-click doesn't turn Solo Mode on"); +} + +#[test] +fn solo_mode_survives_a_restart() { + let mut ui = crate::state::UiState::default(); + ui.set_single_panel(true); + let back: crate::state::UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap(); + assert!(back.single_panel); +} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 76d91ba3e..d20e7b358 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -1,6 +1,6 @@ # Lightroom parity checklist -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (LR-MERGE-HDR: merges of raws without colour matrices keep their colour (#965); rapid slider entry; LR-PROF-CAMERACOLOR: tone curve from black-and-white camera JPEGs; LR-VIEW-ZOOM, LR-VIEW-COMPARE: Compare and Reference zoom windows; LR-IMP-FORMATS, LR-IMP-CAMERA-COVERAGE: EOS R5 Mark II CR3 decodes; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md; filesystem history retry/Undo/Redo evidence; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (LR-EDIT-SECTION-SOLO: Solo Mode for Edit sections; LR-MERGE-HDR: merges of raws without colour matrices keep their colour (#965); rapid slider entry; LR-PROF-CAMERACOLOR: tone curve from black-and-white camera JPEGs; LR-VIEW-ZOOM, LR-VIEW-COMPARE: Compare and Reference zoom windows; LR-IMP-FORMATS, LR-IMP-CAMERA-COVERAGE: EOS R5 Mark II CR3 decodes; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md; filesystem history retry/Undo/Redo evidence; renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good @@ -31,12 +31,12 @@ cited here still exists. Formerly docs/parity.md. | C. Views & navigation (VIEW) | 15 | 1 | 1 | 0 | 8/9 (89%) | 4/4 (100%) | | D. Search & filter (FILT) | 11 | 1 | 1 | 0 | 4/4 (100%) | 4/4 (100%) | | E. Metadata (META) | 5 | 1 | 0 | 0 | 2/2 (100%) | 2/2 (100%) | -| F. Edit panel — global adjustments (EDIT) | 43 | 2 | 3 | 1 | 28/28 (100%) | 13/14 (93%) | +| F. Edit panel — global adjustments (EDIT) | 44 | 2 | 3 | 1 | 28/28 (100%) | 13/14 (93%) | | G. Profiles (PROF) | 7 | 1 | 3 | 0 | 3/4 (75%) | 3/3 (100%) | | H. Crop & rotate (CROP) | 9 | 0 | 0 | 1 | 6/6 (100%) | 3/3 (100%) | | I. Remove / healing (REM) | 8 | 1 | 2 | 2 | 4/4 (100%) | 2/3 (67%) | | J. Red eye (EYE) | 2 | 0 | 0 | 0 | — | 1/1 (100%) | -| K. Masking (MASK) | 13 | 5 | 5 | 0 | 7/8 (88%) | 5/5 (100%) | +| K. Masking (MASK) | 14 | 5 | 5 | 0 | 7/8 (88%) | 6/6 (100%) | | L. Presets (PRE) | 6 | 0 | 1 | 1 | 2/2 (100%) | 2/2 (100%) | | M. Versions & history (VER) | 5 | 0 | 0 | 0 | 1/1 (100%) | 3/3 (100%) | | N. Copy / paste / sync (SYNC) | 5 | 0 | 0 | 0 | 3/3 (100%) | 1/1 (100%) | @@ -53,9 +53,9 @@ cited here still exists. Formerly docs/parity.md. | Y. Menus | 84 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) | | Z. Keyboard shortcuts (desktop) | 76 | 3 | 3 | 1 | 49/52 (94%) | 23/24 (96%) | | Lightroom Classic extras | 34 | 16 | 35 | 8 | — | 24/26 (92%) | -| **Total** | 408 | 52 | 72 | 35 | 193/201 (96%) | 145/157 (92%) | +| **Total** | 410 | 52 | 72 | 35 | 193/201 (96%) | 146/158 (92%) | -Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **81.6%** of 532 in-scope rows — P0 98.0% of 201 · P1 95.2% of 157 · P2 50.0% of 173. +Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **81.6%** of 534 in-scope rows — P0 98.0% of 201 · P1 95.3% of 158 · P2 50.3% of 174. ## Top gaps and shortcut notes @@ -222,6 +222,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-EDIT-LENSBLUR | Lens blur | P2 | ⬜ | | settings field reserved, not rendered | | LR-EDIT-CALIB | Calibration [Classic] | P1 | ✅ | `ctl:calibration.*` | shadows tint, red/green/blue primary hue and saturation; read/written in XMP | | LR-EDIT-SECTION-TOGGLE | Section on/off | P1 | ✅ | `cmd:develop.sectionEnabled`, `crates/develop/src/lib.rs` (`effective`) | the eye on a section header hides its adjustments: Light (with the curve), Color (white balance, presence, mixers, grading, point colour), Effects, Detail, Optics, Geometry, Calibration; masks stay on (issue #316 fixed Light, Color and Detail, which had no effect) | +| LR-EDIT-SECTION-SOLO | Solo Mode for Edit sections | P2 | ✅ | `cmd:view.soloSections`, `crates/ui-egui/src/state.rs` (`toggle_section`, `set_single_panel`, `solo_section`), `crates/ui-egui/src/panels/edit.rs` (`section`), `crates/ui-egui/src/tests_solo_sections.rs` | Lightroom Classic's panel Solo Mode: while it is on, opening one Edit section (a header click or ⌘1–⌘5) closes the others. Window ▸ Edit Sections ▸ Solo Mode (checked while on) or a right-click on any section header (`sectionMenu:solo`) turns it on and off; turning it on keeps only the section opened last. ⌥-click on a header opens just that section once, without turning the mode on. `view.soloSections` `{on?}` (no params toggles) answers `{soloSections}`. Kept with the UI state (`singlePanel`) | | LR-EDIT-RESET | Reset all / section / slider | P0 | ✅ | `cmd:develop.reset`, `cmd:develop.resetSection`, `cmd:develop.resetControl`, `crates/ui-egui/src/widgets.rs` (double-click) | no "reset to open" | | LR-EDIT-SHOWORIG | Show original | P0 | ✅ | `cmd:view.showOriginal` | | @@ -764,7 +765,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06- | LRC-WEB-IMAGEINFO | Web gallery image info | OOS | 🚫 | | | | LRC-WEB-OUTPUT | Web gallery output | OOS | 🚫 | | | | LRC-WEB-UPLOAD | Web gallery upload | OOS | 🚫 | | | -| KEYC-PANELS | Classic panel keys (Tab, ⇧Tab, T, F5–F8, solo) | P2 | ⬜ | | | +| KEYC-PANELS | Classic panel keys (Tab, ⇧Tab, T, F5–F8, solo) | P2 | ⬜ | | solo itself (⌥-click a header, Solo Mode) is done: see LR-EDIT-SECTION-SOLO | | KEYC-MODULES | Classic module switching (⌘⌥1–7) | P2 | 🚫 | | no modules in LightCraft | | KEYC-VIEWS | Classic view keys (E, G, C, N, L, F, I, ⇧R, ⌘⌥0) | P2 | 🟡 | `cmd:view.photoGrid`, `cmd:view.zoom100` | G works; E opens Edit (not loupe); no compare/survey/lights-out/screen modes | | KEYC-SECONDWINDOW | Classic secondary-window keys | P2 | ⬜ | | | @@ -789,6 +790,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| +| 2026-10-13 | minor | LR-EDIT-SECTION-SOLO: Solo Mode for Edit sections (Window ▸ Edit Sections, section-header menu, ⌥-click); KEYC-PANELS notes it | | 2026-10-11 | minor | LR-MERGE-HDR: HDR and panorama merges of raws without colour matrices (CR2, NEF, ARW…) write the colour they open with as DNG tags (issue #965); status unchanged | | 2026-10-12 | minor | LR-META-XMP: Read Metadata from File no longer auto-writes the sidecar it has just read, which added `lc:settings` and hid another app's later `crs:` edits (#934); status unchanged (✅) | | 2026-10-12 | patch | LR-IMP-CAMERA-COVERAGE: CR2 white level from the active area, not the saturated masked rows above it (issue #1272); status unchanged (🟡) | From 12015179eacbe6714c604b8175fe2ca63ebe2713 Mon Sep 17 00:00:00 2001 From: Matthew James Sivak Date: Mon, 12 Oct 2026 17:49:54 +0200 Subject: [PATCH 2/3] LR-EDIT-SECTION-SOLO: section. {solo?} and header clicks run the command Option-click goes through section. {solo: true}, so agents can open just one section; view.soloSections and section.* refuse flags that are not true / false (bool_param); toggle_section reuses solo_section. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/menus.rs | 22 +++++++----- crates/ui-egui/src/panels/edit.rs | 7 ++-- crates/ui-egui/src/state.rs | 13 +++---- crates/ui-egui/src/tests_solo_sections.rs | 43 ++++++++++++----------- docs/parity-checklist.md | 2 +- 5 files changed, 46 insertions(+), 41 deletions(-) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index eda08c733..1a41e4675 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -124,6 +124,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[ ("panel.versions", "Versions", Some("Shift+V"), "Window"), ("panel.activity", "History", None, "Window"), ("panel.close", "Close Panel", None, ""), + // {solo?}: true opens just that section (⌥-click on its header); otherwise opens or closes it ("section.light", "Light", Some("Cmd+1"), "Window>Edit Sections"), ("section.color", "Color", Some("Cmd+2"), "Window>Edit Sections"), ("section.effects", "Effects", Some("Cmd+3"), "Window>Edit Sections"), @@ -955,11 +956,10 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option { - let on = p.get("on").and_then(Value::as_bool).unwrap_or(!app.ui.single_panel); - app.ui.set_single_panel(on); - Ok(json!({"soloSections": app.ui.single_panel})) - } + "view.soloSections" => bool_param(id, p, "on").map(|on| { + app.ui.set_single_panel(on.unwrap_or(!app.ui.single_panel)); + json!({"soloSections": app.ui.single_panel}) + }), "view.filterBar" => { app.ui.filter_bar = !app.ui.filter_bar; if app.ui.filter_bar && !matches!(app.ui.view, ViewMode::PhotoGrid | ViewMode::SquareGrid) { @@ -1040,14 +1040,18 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option { + s if s.starts_with("section.") => bool_param(s, p, "solo").map(|solo| { let sec = &s["section.".len()..]; if app.ui.right != RightPanel::Edit { app.ui.right = RightPanel::Edit; } - app.ui.toggle_section(sec); - Ok(Value::Null) - } + if solo == Some(true) { + app.ui.solo_section(sec); + } else { + app.ui.toggle_section(sec); + } + Value::Null + }), s if s.starts_with("tool.") => { let tool = &s["tool.".len()..]; match tool { diff --git a/crates/ui-egui/src/panels/edit.rs b/crates/ui-egui/src/panels/edit.rs index d4a7aa11f..8418b39e5 100644 --- a/crates/ui-egui/src/panels/edit.rs +++ b/crates/ui-egui/src/panels/edit.rs @@ -532,10 +532,9 @@ fn section( let _ = app.run("develop.sectionEnabled", json!({"section": id, "enabled": on})); } else if resp.clicked() { // ⌥-click opens just this section, as in Lightroom Classic - if ui.input(|i| i.modifiers.alt) { - app.ui.solo_section(id); - } else { - app.ui.toggle_section(id); + let solo = ui.input(|i| i.modifiers.alt); + if let Err(e) = app.run(&format!("section.{id}"), json!({"solo": solo})) { + app.toast(ui.ctx(), e); } } resp.context_menu(|ui| { diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 828e72041..ae51deeeb 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -1170,22 +1170,23 @@ impl UiState { pub fn toggle_section(&mut self, id: &str) { if self.section_open(id) { self.open_sections.retain(|s| s != id); + } else if self.single_panel { + self.solo_section(id); } else { - if self.single_panel { - self.open_sections.clear(); - } self.open_sections.push(id.to_string()); } } /// Opens `id` and closes every other Edit section (⌥-click on a section header). pub fn solo_section(&mut self, id: &str) { - self.open_sections = vec![id.to_string()]; + self.open_sections.clear(); + self.open_sections.push(id.to_string()); } /// Turns Solo Mode on or off. Turning it on keeps only the section opened last. pub fn set_single_panel(&mut self, on: bool) { self.single_panel = on; - if on && let Some(last) = self.open_sections.pop() { - self.open_sections = vec![last]; + if on { + let keep = self.open_sections.len().saturating_sub(1); + self.open_sections.drain(..keep); } } /// Whether the sidebar section with this id is folded shut (an unknown id: no). diff --git a/crates/ui-egui/src/tests_solo_sections.rs b/crates/ui-egui/src/tests_solo_sections.rs index 35c74de48..cea569382 100644 --- a/crates/ui-egui/src/tests_solo_sections.rs +++ b/crates/ui-egui/src/tests_solo_sections.rs @@ -30,20 +30,17 @@ fn exec(h: &mut Headless, command: &str, params: serde_json::Value) -> serde_jso r["result"].clone() } -fn click(h: &mut Headless, id: &str, button: Option<&str>) { - let params = match button { - Some(b) => json!({"id": id, "button": b}), - None => json!({"id": id}), - }; - let r = h.request("ui.clickWidget", params, T); - assert_eq!(r["ok"], true, "click {id}: {r}"); +/// A real click through the control channel: `{id, button?, alt?}` as `ui.clickWidget` takes them. +fn click(h: &mut Headless, params: serde_json::Value) { + let r = h.request("ui.clickWidget", params.clone(), T); + assert_eq!(r["ok"], true, "click {params}: {r}"); h.step(); } #[test] fn solo_mode_closes_the_other_sections_when_one_opens() { let mut h = edit_open(); - click(&mut h, "section:color", None); + click(&mut h, json!({"id": "section:color"})); assert_eq!(h.app.ui.open_sections, ["light", "color"], "off: sections open side by side"); assert_eq!(exec(&mut h, "view.soloSections", json!({})), json!({"soloSections": true})); @@ -51,7 +48,7 @@ fn solo_mode_closes_the_other_sections_when_one_opens() { assert_eq!(crate::menubar::checked(&h.app, "view.soloSections"), Some(true)); h.step(); - click(&mut h, "section:detail", None); + click(&mut h, json!({"id": "section:detail"})); assert_eq!(h.app.ui.open_sections, ["detail"]); // the Window > Edit Sections shortcuts follow it too exec(&mut h, "section.optics", json!({})); @@ -69,9 +66,9 @@ fn solo_mode_closes_the_other_sections_when_one_opens() { #[test] fn the_section_header_menu_turns_solo_mode_on() { let mut h = edit_open(); - click(&mut h, "section:color", None); - click(&mut h, "section:effects", Some("right")); - click(&mut h, "sectionMenu:solo", None); + click(&mut h, json!({"id": "section:color"})); + click(&mut h, json!({"id": "section:effects", "button": "right"})); + click(&mut h, json!({"id": "sectionMenu:solo"})); assert!(h.app.ui.single_panel, "the header's Solo Mode item turns it on"); assert_eq!(h.app.ui.open_sections, ["color"]); } @@ -79,18 +76,22 @@ fn the_section_header_menu_turns_solo_mode_on() { #[test] fn option_click_opens_just_that_section() { let mut h = edit_open(); - click(&mut h, "section:color", None); - let r = h.request("ui.clickWidget", json!({"id": "section:detail", "alt": true}), T); - assert_eq!(r["ok"], true, "{r}"); - h.step(); + click(&mut h, json!({"id": "section:color"})); + click(&mut h, json!({"id": "section:detail", "alt": true})); assert_eq!(h.app.ui.open_sections, ["detail"], "⌥-click closes the others"); assert!(!h.app.ui.single_panel, "⌥-click doesn't turn Solo Mode on"); + // agents do the same with the section's command (Calibration has no menu item but runs too) + exec(&mut h, "section.calibration", json!({"solo": true})); + assert_eq!(h.app.ui.open_sections, ["calibration"]); } #[test] -fn solo_mode_survives_a_restart() { - let mut ui = crate::state::UiState::default(); - ui.set_single_panel(true); - let back: crate::state::UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap(); - assert!(back.single_panel); +fn flags_that_are_not_true_or_false_are_refused() { + let mut h = edit_open(); + for (command, params) in [("view.soloSections", json!({"on": "yes"})), ("section.color", json!({"solo": 1}))] { + let r = h.request("engine.execute", json!({"command": command, "params": params}), T); + assert_eq!(r["ok"], false, "{command} {params}: {r}"); + } + assert!(!h.app.ui.single_panel); + assert_eq!(h.app.ui.open_sections, ["light"]); } diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index d20e7b358..1a9dc79b5 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -222,7 +222,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-EDIT-LENSBLUR | Lens blur | P2 | ⬜ | | settings field reserved, not rendered | | LR-EDIT-CALIB | Calibration [Classic] | P1 | ✅ | `ctl:calibration.*` | shadows tint, red/green/blue primary hue and saturation; read/written in XMP | | LR-EDIT-SECTION-TOGGLE | Section on/off | P1 | ✅ | `cmd:develop.sectionEnabled`, `crates/develop/src/lib.rs` (`effective`) | the eye on a section header hides its adjustments: Light (with the curve), Color (white balance, presence, mixers, grading, point colour), Effects, Detail, Optics, Geometry, Calibration; masks stay on (issue #316 fixed Light, Color and Detail, which had no effect) | -| LR-EDIT-SECTION-SOLO | Solo Mode for Edit sections | P2 | ✅ | `cmd:view.soloSections`, `crates/ui-egui/src/state.rs` (`toggle_section`, `set_single_panel`, `solo_section`), `crates/ui-egui/src/panels/edit.rs` (`section`), `crates/ui-egui/src/tests_solo_sections.rs` | Lightroom Classic's panel Solo Mode: while it is on, opening one Edit section (a header click or ⌘1–⌘5) closes the others. Window ▸ Edit Sections ▸ Solo Mode (checked while on) or a right-click on any section header (`sectionMenu:solo`) turns it on and off; turning it on keeps only the section opened last. ⌥-click on a header opens just that section once, without turning the mode on. `view.soloSections` `{on?}` (no params toggles) answers `{soloSections}`. Kept with the UI state (`singlePanel`) | +| LR-EDIT-SECTION-SOLO | Solo Mode for Edit sections | P2 | ✅ | `cmd:view.soloSections`, `crates/ui-egui/src/state.rs` (`toggle_section`, `set_single_panel`, `solo_section`), `crates/ui-egui/src/panels/edit.rs` (`section`), `crates/ui-egui/src/tests_solo_sections.rs` | Lightroom Classic's panel Solo Mode: while it is on, opening one Edit section (a header click or ⌘1–⌘5) closes the others. Window ▸ Edit Sections ▸ Solo Mode (checked while on) or a right-click on any section header (`sectionMenu:solo`) turns it on and off; turning it on keeps only the section opened last. ⌥-click on a header opens just that section once, without turning the mode on (agents: `section.` `{solo: true}`; header clicks run `section.` too). `view.soloSections` `{on?}` (no params toggles) answers `{soloSections}`. Kept with the UI state (`singlePanel`) | | LR-EDIT-RESET | Reset all / section / slider | P0 | ✅ | `cmd:develop.reset`, `cmd:develop.resetSection`, `cmd:develop.resetControl`, `crates/ui-egui/src/widgets.rs` (double-click) | no "reset to open" | | LR-EDIT-SHOWORIG | Show original | P0 | ✅ | `cmd:view.showOriginal` | | From eeb2ca2f6ad4de231f2d9d03191582f8a19a68a5 Mon Sep 17 00:00:00 2001 From: Matthew James Sivak Date: Mon, 12 Oct 2026 18:22:02 +0200 Subject: [PATCH 3/3] LR-EDIT-SECTION-SOLO: register section.calibration, refuse unknown sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calibration's header runs section.calibration, now a registered command (no menu item); section. refuses an id the Edit panel hasn't got instead of closing the real sections for a phantom one. "Solo Mode" sits with the other So… keys in the catalogs. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/locales/de.json | 2 +- crates/ui-egui/locales/es.json | 2 +- crates/ui-egui/locales/fr.json | 2 +- crates/ui-egui/locales/ja.json | 2 +- crates/ui-egui/locales/pt-br.json | 2 +- crates/ui-egui/locales/ru.json | 2 +- crates/ui-egui/locales/uk.json | 2 +- crates/ui-egui/locales/zh-hans.json | 2 +- crates/ui-egui/locales/zh-hant.json | 2 +- crates/ui-egui/src/menus.rs | 9 +++++++-- crates/ui-egui/src/tests_solo_sections.rs | 8 +++++--- docs/parity-checklist.md | 2 +- 12 files changed, 22 insertions(+), 15 deletions(-) diff --git a/crates/ui-egui/locales/de.json b/crates/ui-egui/locales/de.json index 516d85101..16fa3050d 100644 --- a/crates/ui-egui/locales/de.json +++ b/crates/ui-egui/locales/de.json @@ -529,7 +529,6 @@ "Filmstrip (/)": "Filmstreifen (/)", "Filter": "Filter", "Filter Bar": "Filterleiste", - "Solo Mode": "Solo-Modus", "Filter Presets": "Filtervorgaben", "Filter bar": "Filterleiste", "Filter fields": "Filterfelder", @@ -1433,6 +1432,7 @@ "Soft Pastel": "Sanftes Pastell", "Soft Proofing": "Softproof", "Soft Skin": "Weiche Haut", + "Solo Mode": "Solo-Modus", "Sony ARWs from before about 2017 (RX100, RX100 II–V, RX10, NEX, SLT, ILCE-6000, A7 / A7 II / A7R II and their": "Sony-ARWs von vor etwa 2017, RX100, RX100 II–V, RX10, NEX, SLT, ILCE-6000, A7, A7 II, A7R II und verwandte", "Sony ILCE-7M4 downsized lossless ARWs now decode subsampled YCbCr tiles into linear RGB,": "Verkleinerte verlustfreie ARW-Dateien der Sony ILCE-7M4 werden jetzt aus unterabgetasteten YCbCr-Kacheln in lineares RGB decodiert.", "Sort": "Sortieren", diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json index 444791c77..cd4026b58 100644 --- a/crates/ui-egui/locales/es.json +++ b/crates/ui-egui/locales/es.json @@ -511,7 +511,6 @@ "Filmstrip (/)": "Tira de película (/)", "Filter": "Filtro", "Filter Bar": "Barra de filtros", - "Solo Mode": "Modo solo", "Filter Presets": "Ajustes preestablecidos de filtro", "Filter bar": "Barra de filtros", "Filter fields": "Campos de filtro", @@ -1400,6 +1399,7 @@ "Soft Pastel": "Pastel suave", "Soft Proofing": "Prueba en pantalla", "Soft Skin": "Piel suave", + "Solo Mode": "Modo solo", "Sort": "Ordenar", "Sort Albums by Name": "Ordenar álbumes por nombre", "Sort by": "Ordenar por", diff --git a/crates/ui-egui/locales/fr.json b/crates/ui-egui/locales/fr.json index 1644721b1..5ca3561de 100644 --- a/crates/ui-egui/locales/fr.json +++ b/crates/ui-egui/locales/fr.json @@ -494,7 +494,6 @@ "Filmstrip (/)": "Pellicule (/)", "Filter": "Filtre", "Filter Bar": "Barre de filtres", - "Solo Mode": "Mode solo", "Filter Presets": "Préréglages de filtre", "Filter bar": "Barre de filtres", "Filter fields": "Champs de filtre", @@ -1366,6 +1365,7 @@ "Soft Pastel": "Pastel doux", "Soft Proofing": "Épreuvage à l'écran", "Soft Skin": "Peau douce", + "Solo Mode": "Mode solo", "Sort": "Trier", "Sort by": "Trier par", "Source": "Source", diff --git a/crates/ui-egui/locales/ja.json b/crates/ui-egui/locales/ja.json index 42e9f15e7..10e5e5558 100644 --- a/crates/ui-egui/locales/ja.json +++ b/crates/ui-egui/locales/ja.json @@ -709,7 +709,6 @@ "Filmstrip": "フィルムストリップ", "Filter": "フィルター", "Filter Bar": "フィルターバー", - "Solo Mode": "ソロモード", "Filter Presets": "フィルタープリセット", "Filter bar": "フィルターバー", "Filter fields": "項目を絞り込む", @@ -1311,6 +1310,7 @@ "Soft Pastel": "ソフトパステル", "Soft Proofing": "ソフト校正", "Soft Skin": "ソフトスキン", + "Solo Mode": "ソロモード", "Sort": "並べ替え", "Sort by": "並べ替え", "Source": "読み込み元", diff --git a/crates/ui-egui/locales/pt-br.json b/crates/ui-egui/locales/pt-br.json index 32b84bf99..d2972d761 100644 --- a/crates/ui-egui/locales/pt-br.json +++ b/crates/ui-egui/locales/pt-br.json @@ -502,7 +502,6 @@ "Filmstrip (/)": "Película (/)", "Filter": "Filtro", "Filter Bar": "Barra de filtros", - "Solo Mode": "Modo solo", "Filter Presets": "Predefinições de filtro", "Filter bar": "Barra de filtros", "Filter fields": "Campos de filtro", @@ -1389,6 +1388,7 @@ "Soft Pastel": "Pastel suave", "Soft Proofing": "Prova suave", "Soft Skin": "Pele suave", + "Solo Mode": "Modo solo", "Sort": "Classificar", "Sort by": "Classificar por", "Source": "Origem", diff --git a/crates/ui-egui/locales/ru.json b/crates/ui-egui/locales/ru.json index b8250c70d..7447a1307 100644 --- a/crates/ui-egui/locales/ru.json +++ b/crates/ui-egui/locales/ru.json @@ -412,7 +412,6 @@ "Filmstrip (/)": "Лента (/)", "Filter": "Фильтр", "Filter Bar": "Панель фильтров", - "Solo Mode": "Режим соло", "Filter Presets": "Пресеты фильтров", "Filter bar": "Панель фильтров", "Filter fields": "Поля фильтра", @@ -1153,6 +1152,7 @@ "Soft Pastel": "Мягкий пастельный", "Soft Proofing": "Экранная цветопроба", "Soft Skin": "Мягкая кожа", + "Solo Mode": "Режим соло", "Sort": "Сортировка", "Sort by": "Сортировать по", "Source": "Источник", diff --git a/crates/ui-egui/locales/uk.json b/crates/ui-egui/locales/uk.json index 960351baf..80e172946 100644 --- a/crates/ui-egui/locales/uk.json +++ b/crates/ui-egui/locales/uk.json @@ -915,7 +915,6 @@ "Fill Edges": "Заповнити краї", "Filmstrip": "Стрічка", "Filter Bar": "Панель фільтрів", - "Solo Mode": "Режим соло", "Filter Presets": "Набори налаштувань фільтрів", "Filter bar": "Панель фільтрів", "Filter fields": "Поля фільтрів", @@ -1375,6 +1374,7 @@ "Soft Pastel": "М’яка пастель", "Soft Proofing": "Екранна кольоропроба", "Soft Skin": "М’яка шкіра", + "Solo Mode": "Режим соло", "Sort by": "Сортувати за", "Spherical": "Сферична", "Split Stack": "Розділити стос", diff --git a/crates/ui-egui/locales/zh-hans.json b/crates/ui-egui/locales/zh-hans.json index b80b4f45b..78e27b459 100644 --- a/crates/ui-egui/locales/zh-hans.json +++ b/crates/ui-egui/locales/zh-hans.json @@ -652,7 +652,6 @@ "Filmstrip (/)": "胶片窗格 (/)", "Filter": "过滤器", "Filter Bar": "过滤器栏", - "Solo Mode": "单独模式", "Filter Presets": "过滤器预设", "Filter bar": "过滤器栏", "Filter fields": "过滤字段", @@ -1374,6 +1373,7 @@ "Soft Pastel": "柔和粉彩", "Soft Proofing": "软打样", "Soft Skin": "柔和肤质", + "Solo Mode": "单独模式", "Sort": "排序", "Sort by": "排序方式", "Source": "来源", diff --git a/crates/ui-egui/locales/zh-hant.json b/crates/ui-egui/locales/zh-hant.json index 5de08731f..50ef6020a 100644 --- a/crates/ui-egui/locales/zh-hant.json +++ b/crates/ui-egui/locales/zh-hant.json @@ -652,7 +652,6 @@ "Filmstrip (/)": "底片列(/)", "Filter": "篩選", "Filter Bar": "篩選列", - "Solo Mode": "單獨模式", "Filter Presets": "篩選預設集", "Filter bar": "篩選列", "Filter fields": "篩選欄位", @@ -1374,6 +1373,7 @@ "Soft Pastel": "柔和粉彩", "Soft Proofing": "軟式校樣", "Soft Skin": "柔和膚質", + "Solo Mode": "單獨模式", "Sort": "排序", "Sort by": "排序依據", "Source": "來源", diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 1a41e4675..46c4bac7a 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -130,6 +130,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[ ("section.effects", "Effects", Some("Cmd+3"), "Window>Edit Sections"), ("section.detail", "Detail", Some("Cmd+4"), "Window>Edit Sections"), ("section.optics", "Optics", Some("Cmd+5"), "Window>Edit Sections"), + ("section.calibration", "Calibration", None, ""), // {on?}; no params toggles. Opening one Edit section closes the others. ("view.soloSections", "Solo Mode", None, "Window>Edit Sections"), ("tool.brush", "Brush", Some("B"), "Window>Tools"), @@ -1040,7 +1041,11 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option bool_param(s, p, "solo").map(|solo| { + s if s.starts_with("section.") => bool_param(s, p, "solo").and_then(|solo| { + // only the Edit sections listed above: an unknown one would close the real ones + if !UI_COMMANDS.iter().any(|c| c.0 == s) { + return Err(format!("{s}: no such Edit section")); + } let sec = &s["section.".len()..]; if app.ui.right != RightPanel::Edit { app.ui.right = RightPanel::Edit; @@ -1050,7 +1055,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option { let tool = &s["tool.".len()..]; diff --git a/crates/ui-egui/src/tests_solo_sections.rs b/crates/ui-egui/src/tests_solo_sections.rs index cea569382..70d1a6aa6 100644 --- a/crates/ui-egui/src/tests_solo_sections.rs +++ b/crates/ui-egui/src/tests_solo_sections.rs @@ -80,15 +80,17 @@ fn option_click_opens_just_that_section() { click(&mut h, json!({"id": "section:detail", "alt": true})); assert_eq!(h.app.ui.open_sections, ["detail"], "⌥-click closes the others"); assert!(!h.app.ui.single_panel, "⌥-click doesn't turn Solo Mode on"); - // agents do the same with the section's command (Calibration has no menu item but runs too) + // agents do the same with the section's command (Calibration has one, though not in the menu) exec(&mut h, "section.calibration", json!({"solo": true})); assert_eq!(h.app.ui.open_sections, ["calibration"]); } #[test] -fn flags_that_are_not_true_or_false_are_refused() { +fn bad_flags_and_unknown_sections_are_refused() { let mut h = edit_open(); - for (command, params) in [("view.soloSections", json!({"on": "yes"})), ("section.color", json!({"solo": 1}))] { + for (command, params) in + [("view.soloSections", json!({"on": "yes"})), ("section.color", json!({"solo": 1})), ("section.typo", json!({"solo": true}))] + { let r = h.request("engine.execute", json!({"command": command, "params": params}), T); assert_eq!(r["ok"], false, "{command} {params}: {r}"); } diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 1a9dc79b5..dea4434c9 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -222,7 +222,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-EDIT-LENSBLUR | Lens blur | P2 | ⬜ | | settings field reserved, not rendered | | LR-EDIT-CALIB | Calibration [Classic] | P1 | ✅ | `ctl:calibration.*` | shadows tint, red/green/blue primary hue and saturation; read/written in XMP | | LR-EDIT-SECTION-TOGGLE | Section on/off | P1 | ✅ | `cmd:develop.sectionEnabled`, `crates/develop/src/lib.rs` (`effective`) | the eye on a section header hides its adjustments: Light (with the curve), Color (white balance, presence, mixers, grading, point colour), Effects, Detail, Optics, Geometry, Calibration; masks stay on (issue #316 fixed Light, Color and Detail, which had no effect) | -| LR-EDIT-SECTION-SOLO | Solo Mode for Edit sections | P2 | ✅ | `cmd:view.soloSections`, `crates/ui-egui/src/state.rs` (`toggle_section`, `set_single_panel`, `solo_section`), `crates/ui-egui/src/panels/edit.rs` (`section`), `crates/ui-egui/src/tests_solo_sections.rs` | Lightroom Classic's panel Solo Mode: while it is on, opening one Edit section (a header click or ⌘1–⌘5) closes the others. Window ▸ Edit Sections ▸ Solo Mode (checked while on) or a right-click on any section header (`sectionMenu:solo`) turns it on and off; turning it on keeps only the section opened last. ⌥-click on a header opens just that section once, without turning the mode on (agents: `section.` `{solo: true}`; header clicks run `section.` too). `view.soloSections` `{on?}` (no params toggles) answers `{soloSections}`. Kept with the UI state (`singlePanel`) | +| LR-EDIT-SECTION-SOLO | Solo Mode for Edit sections | P2 | ✅ | `cmd:view.soloSections`, `cmd:section.calibration`, `crates/ui-egui/src/state.rs` (`toggle_section`, `set_single_panel`, `solo_section`), `crates/ui-egui/src/panels/edit.rs` (`section`), `crates/ui-egui/src/tests_solo_sections.rs` | Lightroom Classic's panel Solo Mode: while it is on, opening one Edit section (a header click or ⌘1–⌘5) closes the others. Window ▸ Edit Sections ▸ Solo Mode (checked while on) or a right-click on any section header (`sectionMenu:solo`) turns it on and off; turning it on keeps only the section opened last. ⌥-click on a header opens just that section once, without turning the mode on (agents: `section.` `{solo: true}`, refused for a section the Edit panel has not got; header clicks run `section.` too, Calibration included). `view.soloSections` `{on?}` (no params toggles) answers `{soloSections}`. Kept with the UI state (`singlePanel`) | | LR-EDIT-RESET | Reset all / section / slider | P0 | ✅ | `cmd:develop.reset`, `cmd:develop.resetSection`, `cmd:develop.resetControl`, `crates/ui-egui/src/widgets.rs` (double-click) | no "reset to open" | | LR-EDIT-SHOWORIG | Show original | P0 | ✅ | `cmd:view.showOriginal` | |