From 6f5e1f876e8e6bacdfc7c873a713e0b989d70eef Mon Sep 17 00:00:00 2001 From: eliets2 <287273517+eliets2@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:17:01 +0300 Subject: [PATCH 1/3] Edit: add Select Only Active Photo (Cmd+Shift+D) A new library command narrows the selection to the active photo and keeps that photo active. It is bound to Cmd+Shift+D (Ctrl+Shift+D on Windows and Linux), listed in the Edit menu and the keyboard shortcut editor, and disabled while there is no active photo. The binding does not collide with any existing binding or alias. --- crates/ui-egui/src/menus.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 141d6d782..248682b4a 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -58,6 +58,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[ ("view.squareGrid", "Square Grid", None, "View"), // G: Photo Grid ↔ Square Grid (from other views: the photo grid) ("view.gridToggle", "Grid", Some("G"), ""), + ("library.selectOnlyActive", "Select Only Active Photo", Some("Cmd+Shift+D"), "Edit"), ("tool.guidedUpright", "Guided Upright", Some("Shift+G"), "Window>Tools"), ("view.detail", "Detail", Some("D"), "View"), ("view.compare", "Compare", Some("Shift+C"), "View"), @@ -477,6 +478,10 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option appearance_command(app, p), "view.theme" => theme_command(app, p), + "library.selectOnlyActive" => { + let Some(active) = app.session.active() else { return Some(Err("no active photo".into())) }; + app.run("library.select", json!({"ids": [active.0], "active": active.0})) + } "view.gridToggle" => { app.ui.view = if app.ui.view == ViewMode::PhotoGrid { ViewMode::SquareGrid } else { ViewMode::PhotoGrid }; Ok(Value::Null) @@ -1847,6 +1852,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option bool { match id { + "library.selectOnlyActive" => app.session.active().is_some(), "dialog.contactSheet" | "app.contactSheet" => !cfg!(target_arch = "wasm32") && app.session.active().is_some() && app.export.is_none(), s if s.starts_with("panel.") || s.starts_with("tool.") || s.starts_with("section.") => app.session.active().is_some() || s == "panel.close", "app.export" | "dialog.export" | "dialog.createPreset" | "dialog.rename" | "dialog.captureTime" | "dialog.copySettings" => { From fb47e4a1854751859f16711199d2cf344aba62ba Mon Sep 17 00:00:00 2001 From: eliets2 <287273517+eliets2@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:17:01 +0300 Subject: [PATCH 2/3] Tests for Select Only Active Photo Cover the chord in the photo grid, the square grid and the detail view (selection narrowed, active photo kept, last edit undo step untouched), a focused text field keeping the key, rebinding and unbinding in the keymap, and the command refusing to run without an active photo. --- crates/ui-egui/src/shortcuts.rs | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 1a21ab2fc..b10387c76 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -611,6 +611,74 @@ mod tests { assert_eq!(r["ok"], true, "{r}"); } + /// Press the default Select Only Active Photo chord (the command key on every platform). + fn select_only_active_chord(h: &mut crate::headless::Headless) { + let r = h.request("ui.key", json!({"key": "D", "cmd": true, "shift": true}), std::time::Duration::from_secs(20)); + assert_eq!(r["ok"], true, "{r}"); + } + + #[test] + fn select_only_active_keeps_the_active_photo_and_the_edit_undo_step() { + use crate::state::ViewMode; + use lightcraft_catalog::PhotoId; + for view in [ViewMode::PhotoGrid, ViewMode::SquareGrid, ViewMode::Detail] { + let mut h = library(); + h.app.ui.view = view; + h.app.session.execute("library.select", &json!({"ids": [1, 2, 3], "active": 2})).unwrap(); + h.app.session.execute("photo.rate", &json!({"rating": 4})).unwrap(); + select_only_active_chord(&mut h); + assert_eq!(h.app.session.selection.ids, vec![PhotoId(2)]); + assert_eq!(h.app.session.active(), Some(PhotoId(2))); + // Narrowing the selection does not replace the last photo-edit undo step. + h.app.session.execute("edit.undo", &json!({})).unwrap(); + for id in 1..=3 { + assert_eq!(h.app.session.catalog.photo(PhotoId(id)).unwrap().rating, 0); + } + } + } + + #[test] + fn select_only_active_yields_to_a_focused_text_field() { + use lightcraft_catalog::PhotoId; + let mut h = library(); + h.app.session.execute("library.select", &json!({"ids": [1, 2], "active": 2})).unwrap(); + let r = h.request("ui.clickWidget", json!({"id": "field:search"}), std::time::Duration::from_secs(20)); + assert_eq!(r["ok"], true, "{r}"); + select_only_active_chord(&mut h); + assert_eq!(h.app.session.selection.ids, vec![PhotoId(1), PhotoId(2)]); + assert_eq!(h.app.session.active(), Some(PhotoId(2))); + } + + #[test] + fn select_only_active_follows_the_keymap() { + use lightcraft_catalog::PhotoId; + let mut h = library(); + let both = vec![PhotoId(1), PhotoId(2)]; + h.app.session.execute("library.select", &json!({"ids": [1, 2], "active": 2})).unwrap(); + assert_eq!(super::shortcut_of(&h.app.ui.settings.keymap, "library.selectOnlyActive"), Some("Cmd+Shift+D")); + // unbound: the default chord does nothing + super::assign(&mut h.app.ui.settings.keymap, "library.selectOnlyActive", None).unwrap(); + select_only_active_chord(&mut h); + assert_eq!(h.app.session.selection.ids, both); + // rebound: the new key narrows the selection and the old chord still does not + super::assign(&mut h.app.ui.settings.keymap, "library.selectOnlyActive", Some("F9")).unwrap(); + select_only_active_chord(&mut h); + assert_eq!(h.app.session.selection.ids, both); + key(&mut h, "F9", false); + assert_eq!(h.app.session.selection.ids, vec![PhotoId(2)]); + } + + #[test] + fn select_only_active_without_active_photo_does_not_change_selection() { + let mut h = library(); + // Deselect All keeps the active photo selected, so clear the whole selection state instead. + h.app.session.selection = Default::default(); + assert!(!crate::menus::ui_enabled(&h.app, "library.selectOnlyActive")); + assert!(h.app.run("library.selectOnlyActive", json!({})).is_err()); + select_only_active_chord(&mut h); + assert!(h.app.session.selection.ids.is_empty()); + } + #[test] fn library_rating_labels_flags_and_undo() { use lightcraft_catalog::{ColorLabel, Flag, PhotoId}; From c38bf96ef98230957c3edbe970027f34dccd0d58 Mon Sep 17 00:00:00 2001 From: eliets2 <287273517+eliets2@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:42:56 +0300 Subject: [PATCH 3/3] Docs: Select Only Active Photo in the shortcut list and the parity checklist Add the Cmd+Shift+D row to the library shortcut table with a short note on what it does and where it can be rebound, add a checklist row for it with status partial (covered by headless key tests; the native macOS menu path is not verified), and update the checklist's Last updated line, revision history and the summary counts that this one row changes. --- docs/library-shortcuts.md | 5 +++++ docs/parity-checklist.md | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/library-shortcuts.md b/docs/library-shortcuts.md index 414e9166b..ca3100fc8 100644 --- a/docs/library-shortcuts.md +++ b/docs/library-shortcuts.md @@ -4,6 +4,7 @@ Select photos in Photo Grid or Square Grid, then use: | Key | Action | |---|---| +| `Cmd+Shift+D` (macOS) / `Ctrl+Shift+D` (Windows/Linux) | Select only the active photo | | `0` | Clear the star rating | | `1`–`5` | Set the star rating | | `[` / `]` | Decrease / increase each selected photo's rating by one, within 0–5 | @@ -13,6 +14,10 @@ Select photos in Photo Grid or Square Grid, then use: | `U` | Clear the flag | | `Shift` + `0`–`9`, `P`, `X` or `U` | Apply the action and select the next photo | +Select Only Active Photo (Edit menu) narrows the selection to the active photo. It does not change +the photo, its edits or the last edit Undo step, and it works in any view that has an active photo. +It can be reassigned or disabled in Help → Keyboard Shortcuts; text fields and dialogs keep the key. + Purple and clearing a colour label are available in Photo → Set Color Label and the photo context menu. Purple has no default number key, matching Lightroom Classic. Rejection marks a photo for culling; it does not delete the original or remove it from the library. diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 76d91ba3e..c2452a22a 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 (KEYC-SELECTONLYACTIVE: Select Only Active Photo, ⌘⇧D; 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 @@ -51,11 +51,11 @@ cited here still exists. Formerly docs/parity.md. | W. Cloud & AI infrastructure (CLOUD / AI) | 1 | 0 | 1 | 2 | — | — | | X. Cross-cutting behaviours (BEHAV) | 23 | 6 | 1 | 1 | 8/8 (100%) | 7/10 (70%) | | 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%) | +| Z. Keyboard shortcuts (desktop) | 76 | 4 | 3 | 1 | 49/52 (94%) | 23/25 (92%) | | Lightroom Classic extras | 34 | 16 | 35 | 8 | — | 24/26 (92%) | -| **Total** | 408 | 52 | 72 | 35 | 193/201 (96%) | 145/157 (92%) | +| **Total** | 408 | 53 | 72 | 35 | 193/201 (96%) | 145/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.5%** of 533 in-scope rows — P0 98.0% of 201 · P1 94.9% of 158 · P2 50.0% of 173. ## Top gaps and shortcut notes @@ -610,6 +610,7 @@ From `06-shortcuts.md` part 1. Evidence is our binding; conflicts are explained | KEY-ZOOM | Zoom in / out — ⌘= / ⌘− | P0 | ✅ | `cmd:view.zoomIn`, `cmd:view.zoomOut`, `crates/ui-egui/src/tests_zoom_keys.rs` | the keys zoom the photo only: egui's own interface zoom on ⌘= / ⌘− / ⌘0 (Ctrl on Windows and Linux, where no native menu bar took the keys first) is off (issue #566) | | KEY-SELECTALL | Select all — ⌘A | P0 | ✅ | `cmd:library.selectAll` | | | KEY-SELECTNONE | Select none — ⌘D | P0 | ✅ | `cmd:library.selectNone` | secondary binding (primary ⌘⇧A) | +| KEYC-SELECTONLYACTIVE | Select only active photo — ⌘⇧D / Ctrl+Shift+D | P1 | 🟡 | `cmd:library.selectOnlyActive`, `crates/ui-egui/src/shortcuts.rs` (`select_only_active_*` tests) | Edit → Select Only Active Photo; keeps the active photo, narrows the selection to it and leaves the edit Undo step alone; works in any view that has an active photo; rebindable in Help → Keyboard Shortcuts; text fields and dialogs keep their keys; covered by headless key tests, the macOS native-menu path is not verified | | KEY-PASTESELECTED | Paste selected — ⇧⌘V | P0 | ✅ | `cmd:dialog.pasteSettings` | | | KEY-PREFS | Settings — ⌘, | P0 | ✅ | `cmd:app.settings` | | | KEY-SEARCH | Search — ⌘F | P0 | ✅ | `cmd:view.focusSearch` | | @@ -789,6 +790,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| +| 2026-10-13 | minor | KEYC-SELECTONLYACTIVE: Edit → Select Only Active Photo (⌘⇧D) lands as a new row; status 🟡 (headless-tested, native macOS menu path unverified) | | 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 (🟡) |