Found while testing crates/catalog/src/dates.rs:52 (shift_iso, tail = iso.trim().get(19..)); used by crates/engine/src/cmd/manage.rs:222
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/cull.rs:17-20 (secs, used by the burst grouping at cull.rs:45)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
craft-libs#169 is merged, so the fork pin is no longer needed. Pinned to
storytold/craft-libs 82a24b006eefa036b43c09a67fb9a3fbd17ffe63 (main after the merge) rather than the older 12281d07,
which predates resolve_absolute and the Windows colon refusal.
Unrelated to the #2135 security fix in the commit before this one: both lines
predate this branch and are rustc/clippy 1.96 lint churn. `cargo clippy
--workspace --all-targets` only warns about them, but `cargo xtask ci` runs it
with `-D warnings` (xtask/src/main.rs), so the gate fails before any test runs.
- `clippy::nonminimal_bool` in `slide_overlay::shadow` (reported twice, two
overlapping suggestions on the one expression): `!(A && B) || !C || !D` is
`!(A && B && C && D)` by De Morgan, which is what the line now says. The
operands are pure float comparisons and `is_finite` guards with no side
effects, so this is an exact rewrite — a NaN or infinite `mx0`/`my0` still
makes `shadow` return without drawing, as before. (Clippy's second suggestion
drops the `my0.is_finite()` term and is *not* equivalent; it is not used.)
- `clippy::drop_non_drop` on `drop(write)` in
`same_folder_as_original_refuses_photos_without_a_folder`. The line is not
dead: `write` is a closure holding a mutable borrow of `written`, and the
`drop` ended that borrow so the next assertion could read it. The closure now
lives in a block of its own instead, which ends the borrow at the brace
rather than relying on `drop` of a non-`Drop` type. Every assertion of the
test is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
Photo ▸ Edit In ▸ Open Original in Other Editor handed a photo's stored path
straight to `open` / `xdg-open` / `explorer.exe`, with `Path::is_file()` as the
only check, and Lightroom catalog import kept original paths exactly as the
catalog had them. A crafted `.lrcat` could therefore put `payload.exe`,
`start.lnk`, `run.bat`, `page.hta`, `Evil.app` or `go.command` into the library
and have it run on one user click.
- `lightcraft_engine::open_path`: one gate for every path that leaves
LightCraft for the OS. The extension must be one LightCraft reads
(`import::EXTENSIONS`, which already covers the Edit In copy formats
`tif`/`tiff`/`psd`), it must be a regular file (so a `.app` bundle and any
other folder are out), what a link resolves to must be a read type too, and
what is handed over is the absolute path. A refusal is the sentence shown to
the user.
- Open Original and Edit In go through it; a refused photo is listed in
`skipped` with the reason, and the menu item is greyed out for a record that
can never be opened (extension only, so no disk or share is touched while a
menu is drawn).
- `lightroom_catalog::refuse_non_media` drops such images where the catalog is
read and again where its photos become records, with a warning each, so a
refused entry never becomes an openable library record. A virtual copy of a
refused original goes with it.
Tests: a catalog naming a program (and a virtual copy of it) imports neither
while the catalog's real photos do; a library record naming a program is never
handed to the stub launcher, which records no invocation, and a program
selected beside a photo leaves only the photo, by absolute path. Both fail
before this change.
Co-Authored-By: Claude <noreply@anthropic.com>
Retain the pre-frame size check and allocation budget while avoiding untracked input-sized buffers. Add the reported heap regression and container/progressive compatibility checks.
Co-authored-by: Codex <noreply@openai.com>
Use 照片庫 for "library" everywhere (it was split between 照片庫 and
圖庫), and 縮圖目錄 for the contact sheet progress label to match the
File menu item. Only translations change; English keys and format
placeholders are untouched.
Found while testing crates/slideshow/src/settings.rs:296-300 (parse_color); same pattern in crates/engine/src/slideshow.rs:117-122 (fadeColor)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/rename.rs:919 (start parsed at crates/engine/src/cmd/manage.rs:12)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Clippy 1.97 rejects `drop(write)` in the same-folder export test: the
closure has no Drop, so its borrow of `written` already ends at its last
use and the call did nothing. Removed, with a note on why the following
read of `written` compiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2NDVXA8jQQWeJb4YVTb6b
Canon EOS R6 Mark II writes its Exif model as 'Canon EOS R6 Mark II', which failed the spectral camera table lookup because the table registers the model as 'EOS R6m2'. Add the alias mapping and regression unit tests in lightcraft_raw and lightcraft_catalog.
These files carry no JPEG: their preview is an uncompressed 8-bit RGB
IFD (1217 x 913 in the H4D-40 FFF, 218 x 218 in the CFV 3FR). The
largest such image is handed on by embedded_preview as a small baseline
TIFF, which lightcraft_codecs::decode reads, so 3FR / FFF show a preview
in the grid and loupe before they are developed. No Orientation tag is
written: the raw file's applies, as for JPEG previews without one. The
FFF's 16-bit copy (with a TransferFunction of unknown meaning) is not
used.
decode_raw_preview applied a container-declared colour space by forcing
the JPEG decoder on any owned preview; it now checks for JPEG bytes, so
a wrapped TIFF (or a JPEG XL preview) goes to its own decoder.
Checked on the CC0 H4D-40 FFF and CFV 3FR through
embedded_preview_srgb: right colours, the 3FR upright through its
Rotate90. Tests cover the wrap, the choice among RGB images, a short
strip and truncated files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2NDVXA8jQQWeJb4YVTb6b
An FFF (a 3FR converted on the desktop) has no colour matrix of its own:
the camera's stays in the original 3FR header the FFF carries as a
complete TIFF in private IFD0 tag 0xC51B. The new vendor::hasselblad
reader decodes 3FR / FFF through dng::decode_as and, when the file has
no matrix, takes the matrices from that header, so an FFF no longer
develops with the "camera RGB = sRGB" fallback (H4D-40 as-shot white
moves from xy 0.279, 0.356 to 0.325, 0.336).
RGGB checked against the camera preview on the CC0 H4D-40 FFF and CFV
3FR (raw.pixls.us 931 and 709). Synthetic tests cover the embedded
matrix, an own matrix winning, a missing or damaged header, and
truncated files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2NDVXA8jQQWeJb4YVTb6b
Align feature descriptions, counts, exports and language options with the
current code. Distinguish model-dependent features and implemented Print
and slideshow workflows from their remaining gaps.
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
The Print dialog's Page section gets Lightroom's Page Options for Single Image
and Contact Sheet: the page number at the bottom right, the print settings line
(sharpening, profile, resolution) at the bottom left, and crop marks at the
corners of each photo. They are `print.set {page: {pageNumbers, pageInfo,
cropMarks}}`, saved with templates, drawn in the preview and composed into the
PDF; marks and text turn white on a dark page.
The HDR / Panorama / HDR Panorama dialogs now open with the options of the
last merge of that kind instead of the defaults, and those options are kept
in the library's prefs.json (Session::last_merge) so they survive a restart.
"With Last Settings" and HDR for Each Bracket Set read the same store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Batches 36's doc conflicts kept main's side of rows both sides had touched; for some, main had not changed the row,
so the PR's update was lost. Restored: LR-VIEW-FILMSTRIP (#1482), KEYC-COMPARE (#1491 + #1555), LR-IMP-LOCAL
(#1547 + #1820), LR-LIB-STACK (#1556), gaps 28 (#1555), 2 (#1029) and 23 (#1038), ROADMAP UI/UX (#1038). The
raw-parity by-maker and by-use tables are re-measured with coverage.py (545 verified, ~94% / ~85%).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>