From da49bb5f7fb4ff9a47398aefa900c8a28cfef1d5 Mon Sep 17 00:00:00 2001 From: harrisonoconnorhover <130776285+harrisonoconnorhover@users.noreply.github.com> Date: Fri, 16 Oct 2026 08:45:35 -0400 Subject: [PATCH 1/2] CI: reconcile lint and known unsupported corpus expectations Simplify an equivalent slideshow bounds guard and remove an unnecessary closure drop from an export test for strict Rust 1.95 Clippy. Include the E-PL7 sample among known unsupported variants, matching the documented preview-only status; retain its format and embedded-preview checks. Generated with Codex. --- crates/engine/src/slide_overlay.rs | 2 +- crates/engine/src/tests_export.rs | 1 - crates/raw/tests/corpus.rs | 9 +++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/slide_overlay.rs b/crates/engine/src/slide_overlay.rs index 409739335..a27f75d49 100644 --- a/crates/engine/src/slide_overlay.rs +++ b/crates/engine/src/slide_overlay.rs @@ -903,7 +903,7 @@ fn shadow(img: &mut Rgba8, tl: &crate::text::TextLayout, x: f64, y: f64, sigma: let my0 = (y - pad).floor().max(-pad); let mx1 = (x + tl.width as f64 + pad).ceil().min(iw + pad); let my1 = (y + tl.height as f64 + pad).ceil().min(ih + pad); - if !(mx1 > mx0 && my1 > my0) || !mx0.is_finite() || !my0.is_finite() { + if !(mx1 > mx0 && my1 > my0 && mx0.is_finite() && my0.is_finite()) { return; } let (w, h) = ((mx1 - mx0) as usize, (my1 - my0) as usize); diff --git a/crates/engine/src/tests_export.rs b/crates/engine/src/tests_export.rs index 2f1a05116..e66090b39 100644 --- a/crates/engine/src/tests_export.rs +++ b/crates/engine/src/tests_export.rs @@ -1038,7 +1038,6 @@ fn same_folder_as_original_refuses_photos_without_a_folder() { assert_eq!(f["photo"], id.0, "{f}"); assert!(f["error"].as_str().unwrap().contains("no folder on disk"), "{f}"); } - drop(write); assert!(written.is_empty(), "{written:?}"); // a photo on disk whose folder is gone (moved, an unplugged drive) diff --git a/crates/raw/tests/corpus.rs b/crates/raw/tests/corpus.rs index 31e62f7dc..cb7f6791a 100644 --- a/crates/raw/tests/corpus.rs +++ b/crates/raw/tests/corpus.rs @@ -16,10 +16,11 @@ fn corpus_root() -> PathBuf { /// Variants known not to decode yet (see the crate docs): matched against the lower-case file name. const KNOWN_UNSUPPORTED: &[&str] = &[ - "cr3-", // Unverified CRX coding variants; exact supported cases live in cr3_corpus.rs. - "orf-olympus-em", // Olympus compressed ORF - "-he.nef", // Nikon High Efficiency (HE / HE★) NEF, issue #193 - "sraw", // Canon sRAW / mRAW + "cr3-", // Unverified CRX coding variants; exact supported cases live in cr3_corpus.rs. + "orf-olympus-em", // Olympus compressed ORF + "orf-olympus-epl7.orf", // E-PL7 is preview-only too (docs/raw/coverage.tsv). + "-he.nef", // Nikon High Efficiency (HE / HE★) NEF, issue #193 + "sraw", // Canon sRAW / mRAW ]; #[test] From 47972900e7c37b52805e57f0945a5668dc7d6ae5 Mon Sep 17 00:00:00 2001 From: harrisonoconnorhover <130776285+harrisonoconnorhover@users.noreply.github.com> Date: Fri, 16 Oct 2026 08:45:35 -0400 Subject: [PATCH 2/2] LRC-LIB-CATALOG-IMPORT: classify Lightroom video records Honor VIDEO when importing catalog records and repair older image-classified records on reimport with existing atomic, reversible operations. Preserve IDs, source facts, edits/history and album/stack membership. Cover native catalog import, type filtering, repair and Undo/Redo with synthetic SQLite regressions. Document the filter-only scope of issue #1940. Generated with Codex. --- crates/engine/src/lightroom_catalog.rs | 90 +++++++++++++++++++++++++- docs/gaps.md | 5 +- docs/lightroom-catalog-import.md | 5 ++ docs/parity-checklist.md | 5 +- 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/lightroom_catalog.rs b/crates/engine/src/lightroom_catalog.rs index 777206bcb..7e24a74ff 100644 --- a/crates/engine/src/lightroom_catalog.rs +++ b/crates/engine/src/lightroom_catalog.rs @@ -763,6 +763,10 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, data: CatalogImport, contex } p }; + // Offline videos bypass file probing; Lightroom's format still identifies their kind. + if text(&src.image, "fileFormat").eq_ignore_ascii_case("VIDEO") { + p.kind = lightcraft_catalog::MediaKind::Video; + } ids.insert(src.source_id, p.id); index.photos.insert(identity(&data, src), p.id.0); let keep_develop = existing_id.is_some() && p.edited.is_some() && !update_existing; @@ -827,7 +831,14 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, data: CatalogImport, contex if copy_masters.contains(&src.source_id) { planned.insert(photo_id, p.clone()); } - if s.catalog.photo(p.id).is_some() { + if let Some(old) = s.catalog.photo(p.id) { + if old.kind != p.kind { + // Repair older imports with existing reversible operations. Removing just the + // record leaves album/stack membership intact; the enclosing batch is atomic. + let mut repaired = (**old).clone(); + repaired.kind = p.kind; + ops.extend([Op::RemovePhoto { id: p.id }, Op::AddPhoto { photo: Box::new(repaired) }]); + } if was_local && promoted.insert(p.id) { imported += 1; ops.push(Op::SetLocal { id: p.id, local: false }); @@ -1422,6 +1433,83 @@ mod tests { ] } + #[test] + fn native_video_import_is_visible_in_video_filter() { + let dir = std::env::temp_dir().join(format!("lightcraft-lrcat-video-filter-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("videos.lrcat"); + let mut tables = photo_tables((dir.to_str().unwrap(), "")); + tables[2].2[2] = SqlValue::Text("clip.MOV".into()); + tables[3].2[3] = SqlValue::Text("VIDEO".into()); + std::fs::write(&path, native_catalog(&tables)).unwrap(); + let mut s = crate::Session::new(); + let imported = s.execute("library.importLightroom", &json!({"path": path})).unwrap(); + let id = PhotoId(imported["mapping"]["1"].as_u64().unwrap()); + assert_eq!(s.catalog.photo(id).unwrap().kind, lightcraft_catalog::MediaKind::Video); + s.execute("library.filter", &json!({"kind": "video"})).unwrap(); + assert_eq!(s.visible(), &[id]); + s.execute("library.filter", &json!({"kind": "image"})).unwrap(); + assert!(s.visible().is_empty()); + s.execute("edit.undo", &json!({})).unwrap(); + assert!(s.catalog.is_empty()); + s.execute("edit.redo", &json!({})).unwrap(); + s.execute("library.filter", &json!({"kind": "video"})).unwrap(); + assert_eq!(s.visible(), &[id]); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn native_video_reimport_repairs_kind_without_losing_edits_or_memberships() { + let dir = std::env::temp_dir().join(format!("lightcraft-lrcat-video-reimport-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("videos.lrcat"); + let mut tables = photo_tables((dir.to_str().unwrap(), "")); + tables[2].2[2] = SqlValue::Text("clip.MOV".into()); + tables[3].2[3] = SqlValue::Text("VIDEO".into()); + std::fs::write(&path, native_catalog(&tables)).unwrap(); + let mut s = crate::Session::new(); + let id = s.catalog.alloc_photo_id(); + let mut photo = + Photo::new(id, Source::File { path: dir.join("Photos/clip.MOV").to_string_lossy().into() }, "clip.MOV", "VIDEO", 1920, 1080, "before"); + photo.rating = 5; + photo.flag = Flag::Pick; + photo.file_size = 1234; + photo.content_hash = Some("existing-content".into()); + s.commit("Previous import", Op::AddPhoto { photo: Box::new(photo) }).unwrap(); + let mut settings = lightcraft_develop::DevelopSettings::default(); + settings.light.exposure = 0.75; + s.set_develop(id, settings, "Personal edit").unwrap(); + let peer_id = s.catalog.alloc_photo_id(); + let peer = Photo::new(peer_id, Source::File { path: "/missing/peer.jpg".into() }, "peer.jpg", "JPEG", 20, 10, "before"); + s.commit("Peer", Op::AddPhoto { photo: Box::new(peer) }).unwrap(); + let mut album = Album::new(s.catalog.alloc_album_id(), "Personal album"); + album.photos = vec![id, peer_id]; + s.commit("Album", Op::AddAlbum { album: album.clone() }).unwrap(); + let stack = lightcraft_catalog::Stack { id: s.catalog.alloc_stack_id(), photos: vec![id, peer_id], collapsed: false }; + s.commit("Stack", Op::AddStack { stack: stack.clone() }).unwrap(); + let before = s.catalog.to_snapshot(); + let mut expected = (**s.catalog.photo(id).unwrap()).clone(); + expected.kind = lightcraft_catalog::MediaKind::Video; + s.execute("library.filter", &json!({"kind": "video"})).unwrap(); + assert!(s.visible().is_empty()); + let result = s.execute("library.importLightroom", &json!({"path": path})).unwrap(); + assert_eq!(result["mapping"]["1"], id.0); + assert_eq!(result["imported"], 0); + assert_eq!(result["preservedExistingEdits"], 1); + assert_eq!(s.catalog.photo(id).unwrap().as_ref(), &expected); + assert_eq!(s.visible(), &[id]); + let after = s.catalog.to_snapshot(); + assert_eq!(s.catalog.album(album.id), Some(&album)); + assert_eq!(s.catalog.stack(stack.id), Some(&stack)); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(s.catalog.to_snapshot(), before); + assert!(s.visible().is_empty()); + s.execute("edit.redo", &json!({})).unwrap(); + assert_eq!(s.catalog.to_snapshot(), after); + assert_eq!(s.visible(), &[id]); + std::fs::remove_dir_all(dir).unwrap(); + } + #[test] fn reads_native_sqlite_tables_without_lightroom_or_external_processes() { let bytes = native_catalog(&photo_tables(("/catalog/", ""))); diff --git a/docs/gaps.md b/docs/gaps.md index f11a74f7a..c3d89b157 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps: where LightCraft falls short of Lightroom -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** minor (gap 10: 515 of 1,446 models verified, ~84% of photographers on a verified body) +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** patch (gap 13: imported video classification repaired) 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 @@ -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**: 545 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 / F828 regression (#702), 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) | | 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; 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; offline record counts and destination are now shown in a lasting import result; catalog VIDEO records match the Videos filter, with older classification repaired on reimport | 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) | | 15 | **Print module**: identity plate / watermark / page numbers / crop marks / photo info overlays, 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) | @@ -77,6 +77,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w | Date | Change | Summary | |---|---|---| +| 2026-10-16 | patch | Gap 13: catalog VIDEO records match the Videos filter, including after reimport of older records (#1940, filter issue only). Video playback, frame previews and folder-sync discovery remain outside this repair; no gap closed or counts changed. | | 2026-10-14 | minor | Gap 26: at 100k photos stack and album lookups are indexed, so an "In a Stack" or "Album" smart album counts in ~14 ms (was 0.6–2.7 s); open time unchanged | | 2026-10-14 | minor | Gap 10: Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (coverage.py 515 → 545 verified); sensor verification does not establish colour parity (#1277) | | 2026-10-14 | minor | Gap 21: de, es, fr, ja and uk catalogs filled (97–99%); 55–90 h → 40–70 h | diff --git a/docs/lightroom-catalog-import.md b/docs/lightroom-catalog-import.md index 5ad4383fd..ed5426a81 100644 --- a/docs/lightroom-catalog-import.md +++ b/docs/lightroom-catalog-import.md @@ -34,6 +34,11 @@ edits are preserved by default; `updateExisting=true` explicitly replaces them. identity prevents reimport from duplicating virtual copies and collections. One undo step reverses catalog changes; any saved recovery archives remain available. +Records marked `VIDEO` in the source catalog appear in the **Videos** type filter, including +offline originals. Reimport also repairs the classification of videos imported as images by +older builds, preserving their IDs, edits and album/stack membership. This does not add video +playback, frame previews or video discovery through folder synchronization. + Before changing records, the importer attempts to save source image records, decoded XMP, verbatim develop settings, history, snapshots and collection content to compressed `Interop/lightroom-import-N.lca` recovery archives in the LightCraft library. Archiving is diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 95dcd2aef..7d6642ad3 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 36: slideshow module, print-ready PDF fixes, local adjustment presets, Layout Image loupe overlay, Delete in an album removes from it, new languages Czech, Polish, Italian-ready catalogs; many rows' notes updated by their PRs) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** patch (Lightroom catalog video classification; statuses unchanged) · **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 @@ -698,7 +698,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06- | LRC-LIB-SURVEY | Survey view | P2 | ✅ | `cmd:view.survey` | N | | LRC-LIB-REFVIEW | Reference view | P2 | ✅ | `cmd:view.reference`, `cmd:photo.setReference`, `crates/ui-egui/src/panels/compare.rs` (`show_reference`) | View ▸ Reference View (⇧R): the reference photo beside the active one (edited with the Edit panel); photo menu ▸ Set as Reference Photo; each pane says “Rendering…” (widget `compare:rendering:`) or “This photo can't be opened” (`compare:renderFailed:`) as the loupe does | | LRC-LIB-CATALOG-PANEL | Catalog sets | P1 | ✅ | `cmd:library.source` (`missing`) | all, recently added, picks, recently deleted, missing photos (when any) | -| LRC-LIB-CATALOG-IMPORT | Native Lightroom Classic catalog import | P1 | 🟡 | `cmd:library.inspectLightroom`, `cmd:library.importLightroom`, `crates/engine/src/lightroom_catalog.rs`, `docs/lightroom-catalog-import.md` | read-only `.lrcat` plus committed WAL pages (also WAL-only tables with main schema format 0), in-tree read-only Rust SQLite B-tree reader; background inspection/import with progress/cancellation, per-open library identity checks; persistent result with destination library, offline-original counts and Show in Library, dismissed when switching libraries; offline Local records are promoted without probing missing files; originals in place, ratings/flags/labels, XMP, hierarchical keywords, collection sets/regular collections, virtual copies, mapped develop settings; stable reimport identities, undo, missing-file placeholders, a root folder not found at its recorded path looked up relative to the catalog (Lightroom's `relativePathFromCatalog`, so a project folder copied from another computer stays linked; reported as a warning), existing edits preserved. Best-effort deduplicated recovery archives retain settings/history/snapshots/collection rules (32 MiB each, eight archives/128 MiB aggregate); archive limits/write failures and optional-table limits warn without blocking photo import; unrelated unsupported tables and opaque SQL BLOBs skipped. Custom white balance on relative-WB raws (`RAW` in Lightroom's terms) stays As Shot and is reported, since the catalog keeps no as-shot white to shift from (issue #510). Adobe rendering, AI data and native history replay remain incomplete; no writes to Lightroom catalogs. Open Library given a Lightroom catalog folder, an `.lrdata` / `.lrcat-data` folder or the `.lrcat` makes no library there and offers this import with the catalog found (`cmd:app.openLibrary`, `crates/engine/src/lightroom_folder.rs`; issue #891) | +| LRC-LIB-CATALOG-IMPORT | Native Lightroom Classic catalog import | P1 | 🟡 | `cmd:library.inspectLightroom`, `cmd:library.importLightroom`, `crates/engine/src/lightroom_catalog.rs`, `docs/lightroom-catalog-import.md` | read-only `.lrcat` plus committed WAL pages (also WAL-only tables with main schema format 0), in-tree read-only Rust SQLite B-tree reader; background inspection/import with progress/cancellation, per-open library identity checks; persistent result with destination library, offline-original counts and Show in Library, dismissed when switching libraries; offline Local records are promoted without probing missing files; originals in place, ratings/flags/labels, XMP, hierarchical keywords, collection sets/regular collections, virtual copies, mapped develop settings; stable reimport identities, undo, missing-file placeholders; VIDEO records match the Videos filter, with reimport repairing older image classification while keeping edits and memberships; a root folder not found at its recorded path looked up relative to the catalog (Lightroom's `relativePathFromCatalog`, so a project folder copied from another computer stays linked; reported as a warning), existing edits preserved. Best-effort deduplicated recovery archives retain settings/history/snapshots/collection rules (32 MiB each, eight archives/128 MiB aggregate); archive limits/write failures and optional-table limits warn without blocking photo import; unrelated unsupported tables and opaque SQL BLOBs skipped. Custom white balance on relative-WB raws (`RAW` in Lightroom's terms) stays As Shot and is reported, since the catalog keeps no as-shot white to shift from (issue #510). Adobe rendering, AI data and native history replay remain incomplete; no writes to Lightroom catalogs. Open Library given a Lightroom catalog folder, an `.lrdata` / `.lrcat-data` folder or the `.lrcat` makes no library there and offers this import with the catalog found (`cmd:app.openLibrary`, `crates/engine/src/lightroom_folder.rs`; issue #891) | | LRC-LIB-CATALOG-LOCK | Catalog open in one program at a time | P1 | ✅ | `cmd:app.openLibrary`, `crates/catalog/src/lock.rs`, `crates/engine/src/library.rs` (`open_library`) | exclusive OS lock on `catalog.lock` for the session (app and `lightcraft-cli --library`); a second opener is refused with who has it (`catalog.lock.owner`: program, process, computer); released by the OS on a crash, so never stale; file systems without locks open unprotected with a warning (the app's notices, `library.info` → `settingsWarnings`, and the CLI's stderr; issue #171); no read-only mode; the browser build (OPFS) has no guard across tabs yet. A library that can't be opened at launch (locked, unreadable, newer format, drive missing) shows a window — Try Again, Choose Another Library…, Continue Without Saving (then a banner; nothing written), Quit — instead of a silent demo session (`crates/ui-egui/src/panels/library_problem.rs`); the lock is released explicitly when the library closes, not by letting its file handle close, so a program another thread is starting at that moment (which holds a copy of every file handle until it is launched) cannot keep the library "already open" for a moment (issue #680) | | LRC-LIB-CATALOG-SWITCH | New / Open Recent catalog, catalog name shown | P1 | ✅ | `cmd:app.newLibrary`, `cmd:app.openLibrary`, `cmd:app.recentLibraries`, `cmd:app.clearRecentLibraries`, `crates/ui-egui/src/panels/settings.rs`, `crates/ui-egui/src/panels/left.rs` | File ▸ New Library… (name, then location), File ▸ Open Recent Library (last 8; missing ones greyed out, dropped when opened; Clear), the open library's name under My Photos and in the window title (issue #902); a library that can't be opened leaves the current one open; ⌘O (Ctrl+O on Windows and Linux) runs Open Library… and asks for a folder once (`crates/ui-egui/src/tests_keymap.rs`: `cmd_o_opens_the_library_picker_once`); a dialog or a text field with the focus keeps the key | | LRC-LIB-FOLDERS | Disk folder tree | P1 | ✅ | `cmd:library.browse`, `cmd:folder.rename`, `cmd:folder.move`, `crates/ui-egui/src/panels/left.rs` (`local_section`, `folder_tree`) | standard places + the browsed folder + Browse Folder…, each an expandable tree of subfolders, breadcrumb navigation; context menu: Rename Folder…, Move Folder To…, Show in Finder (on disk, photos relinked; one undo step each: undo / redo move the folder back and forth, refused when the destination is taken) | @@ -797,6 +797,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| +| 2026-10-16 | patch | LRC-LIB-CATALOG-IMPORT: VIDEO records appear in the Videos filter; reimport repairs older classification with edits, memberships and undo preserved (#1940, filter issue only). Status and summary counts unchanged. | | 2026-10-12 | minor | LR-EDIT-OPTICS-PROFILE: Sony ARW shading, CA and distortion tables decoded with settings and provenance (raw IFD, `SR2SubIFD` fallback); applied corrections unchanged (ILCE-7RM4A and ILCE-7RM6 distortion); status unchanged (🟡) | | 2026-10-14 | minor | LR-IMP-CAMERA-COVERAGE: Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (coverage.py 515 → 545 verified); sensor verification does not establish colour parity (#1277); status unchanged (🟡) | | 2026-10-13 | minor | LR-MERGE-PANO: a panorama's DNG takes its `AsShotNeutral` from the median of its frames' vendor white-balance multipliers (CR2, NEF, ARW…) instead of the first frame's tags, and frames now carry the multipliers (issue #1144); status unchanged (✅) |