From 5fe7a46f5870de45b6202f4a47132705a6f24377 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Wed, 28 Oct 2026 11:12:33 -0400 Subject: [PATCH] LR-BEHAV-ACCESS: the photo in Detail is named with its file name for screen readers --- crates/ui-egui/locales/cs-formats.json | 3 +- crates/ui-egui/locales/de-formats.json | 3 +- crates/ui-egui/locales/es-formats.json | 3 +- crates/ui-egui/locales/fi-formats.json | 3 +- crates/ui-egui/locales/fr-formats.json | 3 +- crates/ui-egui/locales/id-formats.json | 3 +- crates/ui-egui/locales/it-formats.json | 3 +- crates/ui-egui/locales/ja-formats.json | 3 +- crates/ui-egui/locales/ka-formats.json | 3 +- crates/ui-egui/locales/pl-formats.json | 3 +- crates/ui-egui/locales/pt-br-formats.json | 3 +- crates/ui-egui/locales/ru-formats.json | 3 +- crates/ui-egui/locales/sv-formats.json | 3 +- crates/ui-egui/locales/uk-formats.json | 3 +- crates/ui-egui/locales/vi-formats.json | 3 +- crates/ui-egui/locales/zh-hans-formats.json | 3 +- crates/ui-egui/locales/zh-hant-formats.json | 3 +- crates/ui-egui/src/panels/detail.rs | 8 +++- crates/ui-egui/src/panels/grid.rs | 2 +- crates/ui-egui/tests/it/main.rs | 1 + crates/ui-egui/tests/it/photo_name.rs | 53 +++++++++++++++++++++ docs/parity-checklist.md | 5 +- docs/whats-new.md | 6 +++ 23 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 crates/ui-egui/tests/it/photo_name.rs diff --git a/crates/ui-egui/locales/cs-formats.json b/crates/ui-egui/locales/cs-formats.json index 1eb882f09..5330ad49a 100644 --- a/crates/ui-egui/locales/cs-formats.json +++ b/crates/ui-egui/locales/cs-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "Soubor {name} se nepodařilo přečíst: {e}", "Added {n} photo{}": "Přidáno fotografií: {n}{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Kam má jít katalog Lightroomu „{catalog}“?", - "Merge into the open library “{library}” ({count} photo{})": "Sloučit s otevřenou knihovnou „{library}“ (fotografie: {count}{:.0})" + "Merge into the open library “{library}” ({count} photo{})": "Sloučit s otevřenou knihovnou „{library}“ (fotografie: {count}{:.0})", + "Photo: {name}": "Fotografie: {name}" } diff --git a/crates/ui-egui/locales/de-formats.json b/crates/ui-egui/locales/de-formats.json index 79390f136..aa946aefa 100644 --- a/crates/ui-egui/locales/de-formats.json +++ b/crates/ui-egui/locales/de-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "{name} konnte nicht gelesen werden: {e}", "Added {n} photo{}": "{n} Foto{} hinzugefügt", "Where should the Lightroom catalog “{catalog}” go?": "Wohin soll der Lightroom-Katalog „{catalog}“?", - "Merge into the open library “{library}” ({count} photo{})": "Mit der geöffneten Bibliothek „{library}“ zusammenführen ({count} Foto{})" + "Merge into the open library “{library}” ({count} photo{})": "Mit der geöffneten Bibliothek „{library}“ zusammenführen ({count} Foto{})", + "Photo: {name}": "Foto: {name}" } diff --git a/crates/ui-egui/locales/es-formats.json b/crates/ui-egui/locales/es-formats.json index 33e5f52fc..69180388a 100644 --- a/crates/ui-egui/locales/es-formats.json +++ b/crates/ui-egui/locales/es-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "No se pudo leer {name}: {e}", "Added {n} photo{}": "{n} foto{0} añadida{0}", "Where should the Lightroom catalog “{catalog}” go?": "¿Dónde debe ir el catálogo de Lightroom “{catalog}”?", - "Merge into the open library “{library}” ({count} photo{})": "Combinar con la biblioteca abierta “{library}” ({count} foto{})" + "Merge into the open library “{library}” ({count} photo{})": "Combinar con la biblioteca abierta “{library}” ({count} foto{})", + "Photo: {name}": "Foto: {name}" } diff --git a/crates/ui-egui/locales/fi-formats.json b/crates/ui-egui/locales/fi-formats.json index 1daab3dab..eead9a31a 100644 --- a/crates/ui-egui/locales/fi-formats.json +++ b/crates/ui-egui/locales/fi-formats.json @@ -305,5 +305,6 @@ "Drop to import {n} preset{}": "Pudota tuodaksesi esiasetuksia: {n}{:.0}", "Drop to review {n} item{} for import": "Pudota tarkistaaksesi ennen tuontia (kohteita: {n}){:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Minne Lightroom-luettelo ”{catalog}” tuodaan?", - "Merge into the open library “{library}” ({count} photo{})": "Yhdistä avoimeen kirjastoon ”{library}” (kuvia: {count}{:.0})" + "Merge into the open library “{library}” ({count} photo{})": "Yhdistä avoimeen kirjastoon ”{library}” (kuvia: {count}{:.0})", + "Photo: {name}": "Kuva: {name}" } diff --git a/crates/ui-egui/locales/fr-formats.json b/crates/ui-egui/locales/fr-formats.json index 294273781..95a8eba84 100644 --- a/crates/ui-egui/locales/fr-formats.json +++ b/crates/ui-egui/locales/fr-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "Impossible de lire {name} : {e}", "Added {n} photo{}": "{n} photo{} ajoutée(s)", "Where should the Lightroom catalog “{catalog}” go?": "Où importer le catalogue Lightroom « {catalog} » ?", - "Merge into the open library “{library}” ({count} photo{})": "Fusionner avec la bibliothèque ouverte « {library} » ({count} photo{})" + "Merge into the open library “{library}” ({count} photo{})": "Fusionner avec la bibliothèque ouverte « {library} » ({count} photo{})", + "Photo: {name}": "Photo : {name}" } diff --git a/crates/ui-egui/locales/id-formats.json b/crates/ui-egui/locales/id-formats.json index 7fc325522..3f04f9663 100644 --- a/crates/ui-egui/locales/id-formats.json +++ b/crates/ui-egui/locales/id-formats.json @@ -305,5 +305,6 @@ "Drop to review {n} item{} for import": "Jatuhkan untuk meninjau {n} item sebelum impor{:.0}", "Inside: {names}": "Di dalam: {names}", "Where should the Lightroom catalog “{catalog}” go?": "Ke mana katalog Lightroom “{catalog}” diimpor?", - "Merge into the open library “{library}” ({count} photo{})": "Gabungkan ke pustaka yang terbuka “{library}” ({count} foto{:.0})" + "Merge into the open library “{library}” ({count} photo{})": "Gabungkan ke pustaka yang terbuka “{library}” ({count} foto{:.0})", + "Photo: {name}": "Foto: {name}" } diff --git a/crates/ui-egui/locales/it-formats.json b/crates/ui-egui/locales/it-formats.json index 7efae90e9..0cab53750 100644 --- a/crates/ui-egui/locales/it-formats.json +++ b/crates/ui-egui/locales/it-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "Impossibile leggere {name}: {e}", "Added {n} photo{}": "Foto aggiunte: {n}{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Dove importare il catalogo Lightroom “{catalog}”?", - "Merge into the open library “{library}” ({count} photo{})": "Unisci alla libreria aperta “{library}” (foto: {count}{:.0})" + "Merge into the open library “{library}” ({count} photo{})": "Unisci alla libreria aperta “{library}” (foto: {count}{:.0})", + "Photo: {name}": "Foto: {name}" } diff --git a/crates/ui-egui/locales/ja-formats.json b/crates/ui-egui/locales/ja-formats.json index 99c14bf16..37f337fca 100644 --- a/crates/ui-egui/locales/ja-formats.json +++ b/crates/ui-egui/locales/ja-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "{name} を読み取れませんでした: {e}", "Added {n} photo{}": "{n}枚を追加しました{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Lightroom カタログ「{catalog}」をどこに読み込みますか?", - "Merge into the open library “{library}” ({count} photo{})": "開いているライブラリ「{library}」に統合(写真{count}枚){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "開いているライブラリ「{library}」に統合(写真{count}枚){:.0}", + "Photo: {name}": "写真: {name}" } diff --git a/crates/ui-egui/locales/ka-formats.json b/crates/ui-egui/locales/ka-formats.json index 460368261..8c6ad71c7 100644 --- a/crates/ui-egui/locales/ka-formats.json +++ b/crates/ui-egui/locales/ka-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "ვერ წაიკითხა {name}: {e}", "Added {n} photo{}": "დაემატა ფოტო: {n}{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "სად გადავიტანოთ Lightroom-ის კატალოგი „{catalog}“?", - "Merge into the open library “{library}” ({count} photo{})": "გაერთიანება ღია ბიბლიოთეკასთან „{library}“ (ფოტოები: {count}){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "გაერთიანება ღია ბიბლიოთეკასთან „{library}“ (ფოტოები: {count}){:.0}", + "Photo: {name}": "ფოტო: {name}" } diff --git a/crates/ui-egui/locales/pl-formats.json b/crates/ui-egui/locales/pl-formats.json index 20c7f840d..b7c46e524 100644 --- a/crates/ui-egui/locales/pl-formats.json +++ b/crates/ui-egui/locales/pl-formats.json @@ -305,5 +305,6 @@ "Added {n} photo{}": "Dodano zdjęcia: {n}{:.0}", "Inside: {names}": "Wewnątrz: {names}", "Where should the Lightroom catalog “{catalog}” go?": "Dokąd zaimportować katalog Lightroom „{catalog}”?", - "Merge into the open library “{library}” ({count} photo{})": "Scal z otwartą biblioteką „{library}” (zdjęcia: {count}){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "Scal z otwartą biblioteką „{library}” (zdjęcia: {count}){:.0}", + "Photo: {name}": "Zdjęcie: {name}" } diff --git a/crates/ui-egui/locales/pt-br-formats.json b/crates/ui-egui/locales/pt-br-formats.json index f17f2c0fd..9094b59f4 100644 --- a/crates/ui-egui/locales/pt-br-formats.json +++ b/crates/ui-egui/locales/pt-br-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "Não foi possível ler {name}: {e}", "Added {n} photo{}": "{n} foto{0} adicionada{0}", "Where should the Lightroom catalog “{catalog}” go?": "Para onde vai o catálogo do Lightroom “{catalog}”?", - "Merge into the open library “{library}” ({count} photo{})": "Mesclar na biblioteca aberta “{library}” ({count} foto{})" + "Merge into the open library “{library}” ({count} photo{})": "Mesclar na biblioteca aberta “{library}” ({count} foto{})", + "Photo: {name}": "Foto: {name}" } diff --git a/crates/ui-egui/locales/ru-formats.json b/crates/ui-egui/locales/ru-formats.json index 76b7af427..5e271911d 100644 --- a/crates/ui-egui/locales/ru-formats.json +++ b/crates/ui-egui/locales/ru-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "Не удалось прочитать {name}: {e}", "Added {n} photo{}": "Добавлено фото: {n}{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Куда импортировать каталог Lightroom «{catalog}»?", - "Merge into the open library “{library}” ({count} photo{})": "Объединить с открытой библиотекой «{library}» (фото: {count}){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "Объединить с открытой библиотекой «{library}» (фото: {count}){:.0}", + "Photo: {name}": "Фото: {name}" } diff --git a/crates/ui-egui/locales/sv-formats.json b/crates/ui-egui/locales/sv-formats.json index 9a71a0288..d40b5fb4f 100644 --- a/crates/ui-egui/locales/sv-formats.json +++ b/crates/ui-egui/locales/sv-formats.json @@ -305,5 +305,6 @@ "Drop to review {n} item{} for import": "Släpp för att granska {n} objekt före import{:.0}", "Inside: {names}": "Inuti: {names}", "Where should the Lightroom catalog “{catalog}” go?": "Vart ska Lightroom-katalogen ”{catalog}” importeras?", - "Merge into the open library “{library}” ({count} photo{})": "Slå samman med det öppna biblioteket ”{library}” ({count} foto{:.0})" + "Merge into the open library “{library}” ({count} photo{})": "Slå samman med det öppna biblioteket ”{library}” ({count} foto{:.0})", + "Photo: {name}": "Foto: {name}" } diff --git a/crates/ui-egui/locales/uk-formats.json b/crates/ui-egui/locales/uk-formats.json index 36022ac1a..740b31294 100644 --- a/crates/ui-egui/locales/uk-formats.json +++ b/crates/ui-egui/locales/uk-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "Не вдалося прочитати {name}: {e}", "Added {n} photo{}": "Додано фото: {n}{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Куди імпортувати каталог Lightroom «{catalog}»?", - "Merge into the open library “{library}” ({count} photo{})": "Об’єднати з відкритою бібліотекою «{library}» (фото: {count}){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "Об’єднати з відкритою бібліотекою «{library}» (фото: {count}){:.0}", + "Photo: {name}": "Фото: {name}" } diff --git a/crates/ui-egui/locales/vi-formats.json b/crates/ui-egui/locales/vi-formats.json index d5e28bff0..ce95624c3 100644 --- a/crates/ui-egui/locales/vi-formats.json +++ b/crates/ui-egui/locales/vi-formats.json @@ -305,5 +305,6 @@ "Drop to import {n} preset{}": "Thả để nhập {n} thiết lập sẵn{:.0}", "Drop to review {n} item{} for import": "Thả để xem lại {n} mục trước khi nhập{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Nhập danh mục Lightroom «{catalog}» vào đâu?", - "Merge into the open library “{library}” ({count} photo{})": "Gộp vào thư viện đang mở «{library}» ({count} ảnh{:.0})" + "Merge into the open library “{library}” ({count} photo{})": "Gộp vào thư viện đang mở «{library}» ({count} ảnh{:.0})", + "Photo: {name}": "Ảnh: {name}" } diff --git a/crates/ui-egui/locales/zh-hans-formats.json b/crates/ui-egui/locales/zh-hans-formats.json index 7406c39b8..711d5a5b6 100644 --- a/crates/ui-egui/locales/zh-hans-formats.json +++ b/crates/ui-egui/locales/zh-hans-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "无法读取 {name}:{e}", "Added {n} photo{}": "已添加 {n} 张照片{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Lightroom 目录“{catalog}”要导入到哪里?", - "Merge into the open library “{library}” ({count} photo{})": "合并到当前打开的图库“{library}”({count} 张照片){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "合并到当前打开的图库“{library}”({count} 张照片){:.0}", + "Photo: {name}": "照片:{name}" } diff --git a/crates/ui-egui/locales/zh-hant-formats.json b/crates/ui-egui/locales/zh-hant-formats.json index d5c4eb279..8315ab2e2 100644 --- a/crates/ui-egui/locales/zh-hant-formats.json +++ b/crates/ui-egui/locales/zh-hant-formats.json @@ -305,5 +305,6 @@ "Could not read {name}: {e}": "無法讀取 {name}:{e}", "Added {n} photo{}": "已加入 {n} 張照片{:.0}", "Where should the Lightroom catalog “{catalog}” go?": "Lightroom 目錄「{catalog}」要匯入到哪裡?", - "Merge into the open library “{library}” ({count} photo{})": "合併到目前開啟的照片庫「{library}」({count} 張照片){:.0}" + "Merge into the open library “{library}” ({count} photo{})": "合併到目前開啟的照片庫「{library}」({count} 張照片){:.0}", + "Photo: {name}": "照片:{name}" } diff --git a/crates/ui-egui/src/panels/detail.rs b/crates/ui-egui/src/panels/detail.rs index 2b58595d2..4b4a15b05 100644 --- a/crates/ui-egui/src/panels/detail.rs +++ b/crates/ui-egui/src/panels/detail.rs @@ -164,7 +164,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { // tool's overlays need the frame's own geometry. let display_aspect = visible_texture_aspect.filter(|a| !crop_tool && (a / aspect - 1.0).abs() > 0.02).unwrap_or(aspect); let resp = ui.interact(canvas, egui::Id::unique("loupe"), Sense::click_and_drag()); - crate::widgets::announce(&resp, crate::i18n::tr("Photo")); + announce_photo(app, &resp, &photo); let mut target_rect = fit_rect(main_area, display_aspect, app.ui.zoom, native, ppp, app.ui.pan); let mut img_rect = animated_rect(ui.ctx(), &mut app.ui.zoom_anim, target_rect); let in_pane = |pane: Rect, image: Rect| image.translate(pane.center() - main_area.center()); @@ -356,6 +356,12 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { loupe_context_menu(app, &resp, id); } +/// Names the loupe for screen readers as the grid names the same photo (file, rating, flag, label), not +/// just "Photo". +fn announce_photo(app: &LightcraftApp, resp: &egui::Response, photo: &lightcraft_catalog::Photo) { + crate::widgets::announce(resp, crate::i18n::tr_format!("Photo: {name}", name = super::grid::cell_description(app, photo))); +} + /// The loupe's right-click menu: zoom commands and the grid's photo menu. fn loupe_context_menu(app: &mut LightcraftApp, resp: &egui::Response, id: PhotoId) { crate::widgets::context_menu(resp, |ui| { diff --git a/crates/ui-egui/src/panels/grid.rs b/crates/ui-egui/src/panels/grid.rs index 5064b8d40..b6e73c1f4 100644 --- a/crates/ui-egui/src/panels/grid.rs +++ b/crates/ui-egui/src/panels/grid.rs @@ -797,7 +797,7 @@ fn cell(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId, r: Rect, square cell_interaction(app, ui, &resp, id, &photo); } -fn cell_description(app: &LightcraftApp, photo: &lightcraft_catalog::Photo) -> String { +pub(crate) fn cell_description(app: &LightcraftApp, photo: &lightcraft_catalog::Photo) -> String { let mut spoken = photo.file_name.clone(); if photo.rating > 0 { spoken.push_str(&crate::i18n::tr_format!(", {} star{}", photo.rating, if photo.rating == 1 { "" } else { "s" })); diff --git a/crates/ui-egui/tests/it/main.rs b/crates/ui-egui/tests/it/main.rs index 4b9fa7eb7..33d0de3a6 100644 --- a/crates/ui-egui/tests/it/main.rs +++ b/crates/ui-egui/tests/it/main.rs @@ -3,3 +3,4 @@ mod accessibility; mod docking_workflows; +mod photo_name; diff --git a/crates/ui-egui/tests/it/photo_name.rs b/crates/ui-egui/tests/it/photo_name.rs new file mode 100644 index 000000000..9684b861c --- /dev/null +++ b/crates/ui-egui/tests/it/photo_name.rs @@ -0,0 +1,53 @@ +//! The photo shown in Detail has a name a screen reader can read: its file, and its rating, flag and +//! label as the grid says them, not the bare word "Photo". +use egui::accesskit::Role; +use egui::vec2; +use egui_kittest::{Harness, kittest::NodeT}; +use lightcraft_ui_egui::{ + LightcraftApp, Services, + state::{RightPanel, ViewMode}, +}; + +fn detail() -> Harness<'static, LightcraftApp> { + let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services { png: None, ..Default::default() }); + app.ui.view = ViewMode::Detail; + app.ui.right = RightPanel::None; + let mut h = Harness::builder().with_size(vec2(1500.0, 950.0)).build_ui_state( + |ui, app: &mut LightcraftApp| { + app.logic(&ui.ctx().clone()); + app.ui(ui); + }, + app, + ); + h.run_steps(10); + h +} + +/// The names of the photo's nodes ("Photo: …"); the in-window "Photo" menu button is not the photo. +fn photo_names(h: &Harness<'_, LightcraftApp>) -> Vec { + h.root() + .children_recursive() + .map(|n| n.accesskit_node()) + .filter(|n| n.role() == Role::Button) + .filter_map(|n| n.label()) + .filter(|l| l.starts_with("Photo: ")) + .collect() +} + +#[test] +fn the_photo_in_detail_is_named_with_its_file() { + let h = detail(); + let app = h.state(); + let file = app.session.active().and_then(|id| app.session.catalog.photo(id)).map(|p| p.file_name.clone()).expect("a photo is shown"); + assert_eq!(photo_names(&h), vec![format!("Photo: {file}")]); +} + +#[test] +fn the_photo_in_detail_says_its_rating_and_flag_as_the_grid_does() { + let mut h = detail(); + h.state_mut().run("photo.rate", serde_json::json!({"rating": 4})).expect("rates"); + h.state_mut().run("photo.flag", serde_json::json!({"flag": "pick"})).expect("flags"); + h.run_steps(4); + let names = photo_names(&h); + assert!(names.len() == 1 && names[0].ends_with(", 4 stars, picked"), "{names:?}"); +} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 90cfc248f..ae4203a28 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-25 · **Change:** minor (LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-28 · **Change:** minor (LR-BEHAV-ACCESS: the Detail photo is named with its file name; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **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 @@ -479,7 +479,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-BEHAV-TOOLTIPS | Tooltips with shortcuts | P0 | ✅ | `crates/ui-egui/src/panels/bottombar.rs` | | | LR-BEHAV-TEXTFIELD | Text fields behave alike | P1 | 🟡 | `crates/ui-egui/src/widgets/text_field.rs`, `crates/ui-egui/src/panels/topbar.rs`, `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/panels/right.rs` (`meta_field`), `crates/ui-egui/src/dialogs.rs`, `crates/ui-egui/src/panels/unnamed.rs` | one shared field: right-click Cut / Copy / Paste / Select All (no Paste in the browser), Esc gives back the text from before the edit, Return / leaving / Esc reported to the caller, optional select-on-focus; agents use `ui.clipboard` (MCP `clipboard`) and `ui.inspect` → `copied`. Used by Search Photos, slider values, the Info panel's metadata fields (multi-line captions), the one-field dialogs (New / Rename Album, New Smart Album, Rename / Merge Keywords, text prompts) and the unnamed faces' name field so far; the other text fields (most dialogs, import, settings, masking, keywords …) still use egui's own and have no menu | | LR-BEHAV-HEADLESS | Headless UI snapshots | P2 | ✅ | `apps/lightcraft-cli/src/main.rs`, `crates/ui-egui/src/headless.rs`, `docs/control-protocol.md` | `snapshot` rasterizes the UI and defaults photo development to the CPU before the first frame, without adapter discovery; scaled dimensions are validated before allocation, and UI zoom preserves the requested physical viewport; JSON-lines control scripts are supported | -| LR-BEHAV-ACCESS | Accessibility | P2 | 🟡 | `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/widgets/text_field.rs`, `crates/ui-egui/src/panels/grid.rs`, `crates/ui-egui/tests/it/accessibility.rs` | AccessKit (VoiceOver / Narrator / AT-SPI): sliders announce control, value, range and step and take Increment / Decrement, buttons / icon buttons / dropdowns / section headers / sources their labels and state, text fields their caption, grid thumbnails file name, rating, flag and label. `tests/it/accessibility.rs` fails on an unnamed control, a slider without range or an untitled dialog in every view, Develop tool and 13 dialogs, and checks that Crop and Remove sliders are reached with Tab and that Tab walks the right panels top to bottom. Canvas tools by keyboard (`crates/ui-egui/src/canvas_keys.rs`, `crates/ui-egui/src/tests/canvas_keys.rs`): crop corners, edges and box, spot targets and sources, a New spot point (Return adds), radial / linear gradient handles and mask pins take the focus, are announced with their position and move with the arrows (Shift: further). Dialogs with one name field (New Album, New Smart Album, Save Metadata Preset, the renames) take the focus once when they open, so Tab and Shift+Tab reach their check box, Cancel and OK, and Return and Esc still confirm and cancel (`crates/ui-egui/src/tests/dialog_tab.rs`). Brush strokes and red-eye ellipses are pointer-only; not tried with a real screen reader ([audit](ui-parity.md#accessibility-audit)) | +| LR-BEHAV-ACCESS | Accessibility | P2 | 🟡 | `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/widgets/text_field.rs`, `crates/ui-egui/src/panels/grid.rs`, `crates/ui-egui/tests/it/accessibility.rs` | AccessKit (VoiceOver / Narrator / AT-SPI): sliders announce control, value, range and step and take Increment / Decrement, buttons / icon buttons / dropdowns / section headers / sources their labels and state, text fields their caption, grid thumbnails file name, rating, flag and label. `tests/it/accessibility.rs` fails on an unnamed control, a slider without range or an untitled dialog in every view, Develop tool and 13 dialogs, and checks that Crop and Remove sliders are reached with Tab and that Tab walks the right panels top to bottom. Canvas tools by keyboard (`crates/ui-egui/src/canvas_keys.rs`, `crates/ui-egui/src/tests/canvas_keys.rs`): crop corners, edges and box, spot targets and sources, a New spot point (Return adds), radial / linear gradient handles and mask pins take the focus, are announced with their position and move with the arrows (Shift: further). Dialogs with one name field (New Album, New Smart Album, Save Metadata Preset, the renames) take the focus once when they open, so Tab and Shift+Tab reach their check box, Cancel and OK, and Return and Esc still confirm and cancel (`crates/ui-egui/src/tests/dialog_tab.rs`). The photo in Detail is named like its grid cell, `Photo: , , ,