#2672: a stale destination window refuses Create a new library too; tests read confirm's error, not the last toast

Review follow-up on #2685. CodeRabbit: the library-switch check ran for Merge only, so a window left over from before a switch could still make a library from defaults that described the old one; it now covers both choices (new test). The Mac session: on macOS the switch's own notice can replace the error toast before the test reads it, so the stale-window tests check confirm's error directly. docs/gaps.md gap 13 updated with the parity row (CodeRabbit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
This commit is contained in:
Claude
2026-10-23 14:37:06 +00:00
parent e9b3839780
commit 9bb072fe4a
3 changed files with 43 additions and 6 deletions
+3 -2
View File
@@ -188,12 +188,13 @@ fn params(catalog: &str, new_library: bool, name: &str, location: &str) -> Value
/// The window's confirm button: import as chosen. "Don't ask again" is kept once a merge has /// The window's confirm button: import as chosen. "Don't ask again" is kept once a merge has
/// started (it only applies to merging). Another library opened while the window was up (by an /// started (it only applies to merging). Another library opened while the window was up (by an
/// agent, the control channel) is not merged into. /// agent, the control channel) makes the window stale for both choices: nothing is merged into
/// it, and no new library is made from defaults that described the library it replaced.
pub fn confirm(app: &mut LightcraftApp, dialog: &Dialog) -> Result<Value, String> { pub fn confirm(app: &mut LightcraftApp, dialog: &Dialog) -> Result<Value, String> {
let Dialog::LightroomDestination { catalog, new_library, name, location, dont_ask, generation, .. } = dialog else { let Dialog::LightroomDestination { catalog, new_library, name, location, dont_ask, generation, .. } = dialog else {
return Err("not the Lightroom catalog destination window".into()); return Err("not the Lightroom catalog destination window".into());
}; };
if !*new_library && *generation != app.session.library_generation() { if *generation != app.session.library_generation() {
return Err(crate::i18n::tr("Another library was opened since this window appeared: nothing was imported").to_string()); return Err(crate::i18n::tr("Another library was opened since this window appeared: nothing was imported").to_string());
} }
let r = app.run("file.importLightroom", params(catalog, *new_library, name, location))?; let r = app.run("file.importLightroom", params(catalog, *new_library, name, location))?;
@@ -380,12 +380,47 @@ fn merge_is_refused_when_another_library_was_opened_meanwhile() {
let other = f.root.join("Other"); let other = f.root.join("Other");
let dialog = h.app.ui.dialog.take(); let dialog = h.app.ui.dialog.take();
h.app.run("app.openLibrary", json!({"path": other.to_string_lossy()})).unwrap(); h.app.run("app.openLibrary", json!({"path": other.to_string_lossy()})).unwrap();
let stale = dialog.clone();
h.app.ui.dialog = dialog; h.app.ui.dialog = dialog;
h.step(); h.step();
click(&mut h, "button:dialogOk"); click(&mut h, "button:dialogOk");
assert!(h.app.ui.dialog.is_none(), "the window closes: the library it named is gone"); assert!(h.app.ui.dialog.is_none(), "the window closes: the library it named is gone");
let toast = h.app.ui.toast.clone().map(|t| t.0).unwrap_or_default(); // confirm's own error, not the last toast: another notice (the library switch's, on macOS)
assert!(toast.contains("nothing was imported"), "{toast}"); // can replace the toast before it is read
let stale = stale.unwrap();
let err = crate::lightroom_destination::confirm(&mut h.app, &stale).unwrap_err();
assert!(err.contains("nothing was imported"), "{err}");
assert!(h.app.lightroom.is_none() && h.app.lightroom_last.is_none()); assert!(h.app.lightroom.is_none() && h.app.lightroom_last.is_none());
assert_eq!(open_dir(&h), Some(other)); assert_eq!(open_dir(&h), Some(other));
} }
/// The same for *Create a new library* (CodeRabbit on #2685): a window left over from before a
/// library switch makes no library and imports nothing.
#[test]
fn a_new_library_is_refused_when_another_library_was_opened_meanwhile() {
let f = Fixture::new("stale-new");
let mut h = f.app(false);
h.app.run("file.importLightroom", json!({"path": f.lrcat()})).unwrap();
h.step();
click(&mut h, "radio:lightroomDestination-new");
let other = f.root.join("Other");
let dialog = h.app.ui.dialog.take();
let target = match &dialog {
Some(crate::state::Dialog::LightroomDestination { name, location, .. }) => std::path::Path::new(location).join(name),
_ => panic!("the destination window is up"),
};
h.app.run("app.openLibrary", json!({"path": other.to_string_lossy()})).unwrap();
let stale = dialog.clone();
h.app.ui.dialog = dialog;
h.step();
click(&mut h, "button:dialogOk");
assert!(h.app.ui.dialog.is_none(), "the window closes: the library it named is gone");
// confirm's own error, not the last toast: another notice (the library switch's, on macOS)
// can replace the toast before it is read
let stale = stale.unwrap();
let err = crate::lightroom_destination::confirm(&mut h.app, &stale).unwrap_err();
assert!(err.contains("nothing was imported"), "{err}");
assert!(h.app.lightroom.is_none() && h.app.lightroom_last.is_none());
assert_eq!(open_dir(&h), Some(other));
assert!(!target.exists(), "no library was made at {}", target.display());
}
+3 -2
View File
@@ -1,6 +1,6 @@
# Gaps: where LightCraft falls short of Lightroom # Gaps: where LightCraft falls short of Lightroom
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains) > **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-23 · **Change:** minor (gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains)
Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and
how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is
@@ -27,7 +27,7 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 551 of 1,446 Adobe models verified (38%), ~85% of photographers on a verified body; Nikon 5 bodies verified, Canon 34, Leica 14 (the Q3 by a recorded check of 38 private DNGs). Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 regression (#702); F828 SRF now discovered and imported as preview-only, sensor decoding still missing, DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) | | 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 551 of 1,446 Adobe models verified (38%), ~85% of photographers on a verified body; Nikon 5 bodies verified, Canon 34, Leica 14 (the Q3 by a recorded check of 38 private DNGs). Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 regression (#702); F828 SRF now discovered and imported as preview-only, sensor decoding still missing, DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) |
| 11 | **Enhance: Raw Details, Super Resolution; AI denoise without shippable weights or X-Trans** | ENH rows 0%; denoise Bayer only, RawNIND weights GPL and opt-in | Fujifilm users get no AI denoise; nobody gets it out of the box | 30–60 + model decision | [raw-parity](raw-parity.md#demosaic-levels-crops-and-lens-metadata) | | 11 | **Enhance: Raw Details, Super Resolution; AI denoise without shippable weights or X-Trans** | ENH rows 0%; denoise Bayer only, RawNIND weights GPL and opt-in | Fujifilm users get no AI denoise; nobody gets it out of the box | 30–60 + model decision | [raw-parity](raw-parity.md#demosaic-levels-crops-and-lens-metadata) |
| 12 | **Content-aware / generative remove, detect distractions, reflections removal** | REM rows 75%; patch synthesis missing | cleanup work goes back to Photoshop | 25–45 (+ generative model) | [target-app-parity](target-app-parity.md#ai) | | 12 | **Content-aware / generative remove, detect distractions, reflections removal** | REM rows 75%; patch synthesis missing | cleanup work goes back to Photoshop | 25–45 (+ generative model) | [target-app-parity](target-app-parity.md#ai) |
| 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data; main-file pages read on demand up to 16 GiB, with dense-table and WAL bounds retained (#798); offline record counts and destination are now shown in a lasting import result | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) | | 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data; main-file pages read on demand up to 16 GiB, with dense-table and WAL bounds retained (#798); offline record counts and destination are now shown in a lasting import result; before an import the app asks whether the catalog merges into the open library or goes into a new library (opt-out in Settings, #2672) | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
| 14 | **HDR display** (EDR / Windows HDR); the headroom limit and SDR rendition from `crs` XMP (the HDR switch, `HDREditMode`, is read). HLG AVIF export done; the display's SDR/HDR decision is tested, blocked on eframe's 8-bit surface | HDR rows 70%; LR-VIEW-HDR-DISPLAY ⬜ | HDR editing works blind on HDR monitors | 12–25 | [hardware-parity](hardware-parity.md) | | 14 | **HDR display** (EDR / Windows HDR); the headroom limit and SDR rendition from `crs` XMP (the HDR switch, `HDREditMode`, is read). HLG AVIF export done; the display's SDR/HDR decision is tested, blocked on eframe's 8-bit surface | HDR rows 70%; LR-VIEW-HDR-DISPLAY ⬜ | HDR editing works blind on HDR monitors | 12–25 | [hardware-parity](hardware-parity.md) |
| 15 | **Print module**: identity plate / watermark / photo info overlays, Cut Guides for the packages, Custom Package Auto Layout, printer-driver Page Setup and direct printing, JPEG file output (the Print dialog writes a colour-managed PDF) | LRC-PRINT rows | Classic users who print | 15–25 | [target-app-parity](target-app-parity.md#features) | | 15 | **Print module**: identity plate / watermark / photo info overlays, Cut Guides for the packages, Custom Package Auto Layout, printer-driver Page Setup and direct printing, JPEG file output (the Print dialog writes a colour-managed PDF) | LRC-PRINT rows | Classic users who print | 15–25 | [target-app-parity](target-app-parity.md#features) |
| 16 | **Map view, Book, Slideshow module, publish services** | LRC-MAP / BOOK / SS rows ⬜ | Classic users; publish services matter for Flickr / SmugMug workflows | 60–100 | [target-app-parity](target-app-parity.md#features) | | 16 | **Map view, Book, Slideshow module, publish services** | LRC-MAP / BOOK / SS rows ⬜ | Classic users; publish services matter for Flickr / SmugMug workflows | 60–100 | [target-app-parity](target-app-parity.md#features) |
@@ -79,6 +79,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
| Date | Change | Summary | | Date | Change | Summary |
|---|---|---| |---|---|---|
| 2026-10-23 | minor | Gap 13: before a Lightroom catalog import the app asks whether it merges into the open library or goes into a new one (opt-out in Settings, #2672); hours unchanged |
| 2026-10-21 | minor | Gap 28 shrinks: F8 shows / hides the right panel (`view.rightPanel`); rating `[` `]`, flag cycling and F1 help were already bound, so the entry lists only filter-bar keys, Tab / ⇧Tab and F5; 2–4 h (was 4–8) | | 2026-10-21 | minor | Gap 28 shrinks: F8 shows / hides the right panel (`view.rightPanel`); rating `[` `]`, flag cycling and F1 help were already bound, so the entry lists only filter-bar keys, Tab / ⇧Tab and F5; 2–4 h (was 4–8) |
| 2026-10-20 | minor | #2362: import destination albums use the shared hierarchy/search picker instead of a flat name list; existing import workflows retained; ranked gaps/readiness counts unchanged | | 2026-10-20 | minor | #2362: import destination albums use the shared hierarchy/search picker instead of a flat name list; existing import workflows retained; ranked gaps/readiness counts unchanged |
| 2026-10-19 | minor | Gap 21: Indonesian (`id`) shipped with every catalog message but What's New; four new languages left (Hindi, Arabic, Korean, Vietnamese), 135–239 h | | 2026-10-19 | minor | Gap 21: Indonesian (`id`) shipped with every catalog message but What's New; four new languages left (Hindi, Arabic, Korean, Vietnamese), 135–239 h |