diff --git a/crates/engine/src/cmd/browse.rs b/crates/engine/src/cmd/browse.rs index a681ec14b..29b7d65e6 100644 --- a/crates/engine/src/cmd/browse.rs +++ b/crates/engine/src/cmd/browse.rs @@ -49,6 +49,16 @@ fn browse(s: &mut Session, p: &Value) -> Result { Ok(json!({"path": dir_s, "subfolders": subfolders, "photos": shown, "new": report.imported.len(), "failed": report.failed.len()})) } +impl Session { + /// Record that the folder `root` was browsed now, with the folders of `files` (the photos it showed): what + /// `library.browse` does after reading a folder, for a UI that reads it on a worker thread instead. Untouched + /// Local photos are forgotten only from folders not browsed for the set number of days, so a browse that + /// isn't recorded lets a folder in daily use be forgotten. + pub fn note_browsed(&mut self, root: &str, files: &[String]) { + stamp_browsed(self, root, files); + } +} + /// Folders whose browse time is refreshed at most this often (keeps repeated browsing from /// growing the log; far finer than the days the eviction counts in). const RESTAMP_SECS: i64 = 3600; diff --git a/crates/ui-egui/src/headless.rs b/crates/ui-egui/src/headless.rs index 0507ab31f..1fa154b18 100644 --- a/crates/ui-egui/src/headless.rs +++ b/crates/ui-egui/src/headless.rs @@ -2983,6 +2983,30 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + /// Browsing a folder from the UI (read on a worker thread) records it as browsed, as `library.browse` does: + /// untouched Local photos are only forgotten from folders not browsed for the set number of days. + #[test] + fn browsing_from_the_ui_records_the_folder() { + let mut h = demo([1200.0, 900.0]); + let t = Duration::from_secs(10); + let dir = std::env::temp_dir().join(format!("lc-browse-stamp-{}", std::process::id())); + let day = dir.join("2026/10/14"); + std::fs::create_dir_all(&day).unwrap(); + let img = lightcraft_raster::Rgba8::from_fn(8, 8, |x, y| [(x * 9) as u8, (y * 12) as u8, 80, 255]); + let o = lightcraft_engine::export::ExportOptions { format: lightcraft_engine::export::ExportFormat::Png, ..Default::default() }; + std::fs::write(day.join("a.png"), lightcraft_engine::export::encode_image(&img, &o).unwrap()).unwrap(); + let root = dir.to_string_lossy().trim_end_matches(['/', '\\']).to_string(); + let day_s = day.to_string_lossy().to_string(); + assert!(h.app.session.catalog.last_browsed(&root).is_none()); + let r = h.request("engine.execute", json!({"command": "library.browse", "params": {"path": root, "subfolders": true}}), t); + assert_eq!(r["result"]["scanning"], true, "{r}"); + h.settle(SETTLE); + assert_eq!(h.app.session.visible_cloned().len(), 1); + assert!(h.app.session.catalog.last_browsed(&root).is_some(), "the browsed folder"); + assert!(h.app.session.catalog.last_browsed(&day_s).is_some(), "the folder its photo is in"); + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn add_folder_scans_in_the_background() { let mut h = demo([1200.0, 900.0]); diff --git a/crates/ui-egui/src/import.rs b/crates/ui-egui/src/import.rs index a1a720a8e..64e90acb8 100644 --- a/crates/ui-egui/src/import.rs +++ b/crates/ui-egui/src/import.rs @@ -559,6 +559,12 @@ pub fn poll_scan(app: &mut LightcraftApp, ctx: &egui::Context) { } app.session.import_probes = out.probes; if task.browse { + // the folder and the ones its photos are in count as browsed now, as `library.browse` records them, so + // their untouched Local photos aren't forgotten while the folder is in use + if let Some(root) = app.session.browse.as_ref().map(|b| b.path.clone()) { + let shown: Vec = out.candidates.iter().map(|c| c.path.clone()).collect(); + app.session.note_browsed(&root, &shown); + } // what the library doesn't know yet joins the Local view let queue: Vec = out.candidates.iter().filter(|c| c.duplicate != Some("path".into()) && c.error.is_none()).map(|c| c.path.clone()).collect(); diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 7bb187811..5626345c7 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-14 · **Change:** minor (integration batch 35: KEYC-SELECTONLYACTIVE, Select Only Active Photo ⌘⇧D; LR-BEHAV-AUTOSAVE, damaged snapshots repaired on open; LR-EDIT-OPTICS-PROFILE, Nikon Z 6II / Z 8 / Z f in-file distortion; LRC-LIB-CATALOG-IMPORT, WAL-only catalogs open; LR-EXP-DIM, enlargements stop at 512 MP; LR-IMP-CAMERA-COVERAGE, Canon EOS RP CR3 verified; model folder retry) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (LR-IMP-LOCAL: browsing from the sidebar records the folder; integration batch 35: KEYC-SELECTONLYACTIVE, Select Only Active Photo ⌘⇧D; LR-BEHAV-AUTOSAVE, damaged snapshots repaired on open; LR-EDIT-OPTICS-PROFILE, Nikon Z 6II / Z 8 / Z f in-file distortion; LRC-LIB-CATALOG-IMPORT, WAL-only catalogs open; LR-EXP-DIM, enlargements stop at 512 MP; LR-IMP-CAMERA-COVERAGE, Canon EOS RP CR3 verified; model folder retry) · **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 @@ -790,6 +790,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| +| 2026-10-14 | patch | LR-IMP-LOCAL: browsing a folder from the sidebar records it as browsed, as `library.browse` does, so its untouched photos aren't forgotten while in use; status unchanged (✅) | | 2026-10-13 | patch | LR-VIEW-COMPARE, LRC-LIB-COMPARE: ⌘/Ctrl- and Shift-clicks on the filmstrip in Compare change the selection as in the grid; the two photos shown stay selected and the active photo is unchanged (#187); statuses unchanged | | 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-13 | patch | LRC-LIB-CATALOG-IMPORT: a catalog whose tables are all still in its `-wal` file (main file schema format 0) opens instead of failing as an unsupported schema format; status unchanged | diff --git a/docs/whats-new.md b/docs/whats-new.md index 1bf44c3f4..2a184da48 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -2,6 +2,14 @@ ## 2026-10-14 +### Local folders you browse are kept +- Browsing a folder under Local now records it as browsed, so the photos in folders you use aren't forgotten after + the number of days set in Settings → Performance. Only the `library.browse` command recorded it before, so a + folder browsed every day from the sidebar was forgotten anyway and read again from scratch on the next visit, + which is slow on a network share. + +## 2026-10-14 + ### Hard-edged Heal and Clone spots - A spot with Feather at 0 no longer draws black pixels exactly on its radius. This also fixes spots at 0% Opacity changing pixels along their edge. The hard edge now leaves pixels on the radius unchanged.