On a straightened photo, dragging a free crop's corner out diagonally grew
only one side: the corner grew across first, met the tilted edge, and from
there no straight step down stayed inside, so the other side never moved.
The corner now goes to the place nearest the pointer that keeps the frame
inside and at least the minimum size: the same convex nearest-place solve a
move uses (#2299), generalised to points that move with one or both of the
offset's components. On each axis the corner moves only between where it is
and where the pointer asks, so a sideways push past a tilted edge stops there
and keeps the height. Edge handles and unstraightened photos keep the old path.
CodeRabbit on #2689 (head 496291276):
- `cull` takes the view it hands on from before ending an open drag, so the
drag's own commit can't change it first. (In practice the view already
follows the drag's preview, so the photo is out of it mid-drag either way;
this is the order every caller should use.)
- LR-BEHAV-LOCALE cites the commands of all 18 languages.
- Two 2026-10-21 revision rows moved above the 2026-10-20 ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its code is already in the batch; docs keep the batch's version, headers list
#2468's change entries too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #2689 (head 02743d187):
- crates/engine/src/cmd/library.rs: `cull` (#2327: a rating, flag or label
that takes the active photo out of the view) and `photo.delete` /
`photo.deletePermanently` moved the active photo without ending an open
interaction, unlike `step` and `library.select`: the drag stayed open with
no undo step. Each ends it now before its own change, so the drag is its
own undo step and comes first: undo brings the rating or the photo back,
with its edit, then the edit (Delete Permanently's undo restores the
edit too). Test an_open_edit_ends_before_the_selection_moves_on (rating,
delete, delete permanently) fails without the fix.
- docs/parity-checklist.md (LR-BEHAV-LOCALIZE) and docs/localization.md: list
every shipped language (Polish, and the others the row lacked; Italian in
the localization doc).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/ui-egui/src/lib.rs: the batch's module list (tests_capture_time) with
#1922's. LR-VIEW-ZOOM: the batch's row plus #1922's middle-button sentence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docs only, and the batch's side already holds everything #2701's merge
resolved (its rows that differ are main's rows other batch PRs edited).
Tracker summary recomputed (`cargo xtask parity --write`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/parity-checklist.md: header lists both sides' changes; #1499's two new
dated prose entries (2026-10-23, 2026-10-24) become revision rows like its
two earlier ones, so the history stays one table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- apps/lightcraft/src/native_menu.rs: upstream's item swapping (macOS keys
given up for real, Help menu not swapped, Settings / Quit tracked) with
KEY-GRID's keymap-aware rows. `Item.label` stays the text the row shows
(native_item_presentation), so `Item::native_label` reuses it instead of
formatting it again; Settings / Quit store theirs through
display_item_label, as upstream's native_label did. No early return after
a rebuild (upstream).
- crates/ui-egui/src/i18n.rs: upstream's tests (#2729: one test per check
across languages, gaps reported, never a failure) plus the batch's two new
ones (dropped-photos notice, Polish counts); production code as merged.
- grid.rs, keyword_list.rs, masking.rs: the translation tests #2729 removed
stay removed; the batch's new keyword and segmenter tests stay.
- cmd/library.rs, ui-egui/src/lib.rs: both test modules, each with its
#[cfg(test)].
- xtask/src/main.rs: #2302's test_selected and upstream's translation_gaps.
- docs: tracker / gap rows take both sides' edits, headers list both sides'
changes, history rows of both sides newest-first. What's New: one section
per date, newest first (the batch had 10-22 above 10-23, and second 10-21
and 10-19 sections); no line lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Detail, dragging with the middle button (the mouse wheel pressed) pans the
zoomed photo with a hand cursor, in every tool. Remove / Heal / Clone, the
mask brushes and Crop no longer have to be left to look around a zoomed photo,
and they don't paint or move during that drag: it is handled with the pinch and
scroll gestures, before the tool overlays.
Right-click keeps the photo's context menu and Space keeps the zoom toggle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dialog_field took the focus back every frame while the field wasn't being edited, so Tab never
reached the check box, Cancel or OK of New Album, New Smart Album, Save Metadata Preset and the
other one-field dialogs. It now takes the focus once, when the dialog opens (the way the keyword
dialogs already did).
crates/ui-egui/src/lib.rs: the batch's handle_dropped_files refuses presets
while no library is open, as #2701 does. The drop card says so, and its
"photos wait" line is desktop-only (the web build's drops import into its
session as before). photos_dropped_with_no_library_open_wait_for_one also
drops a preset; LR-IMP-DRAGDROP row updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Presets belong to a library (presets.json, <library>/Profiles): dropped
while the launch list or the failed-library window is up, they went into
the session the library opened then replaced. They are refused with a
toast now; the photos of the same drop still wait for the library. Docs:
What's New, control protocol, the #1290 tracker row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #2689 (head b840f3f29):
- docs/control-protocol.md, docs/lightroom-catalog-import.md (#2685): the
destination window opens only when settings.lightroomImport is "ask"; the
three places that said it always asks now say so.
- docs/parity-checklist.md: two dated prose entries cut the revision table,
so the rows after them (#2685's among them) fell outside it. They are rows
now, and the batch's rows read newest-first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- xtask/src/main.rs: upstream's runners (cargo test or nextest, no
examples, doctests apart, one wasm `cargo check --keep-going`) with
#2302's `ci --quick` on top: test_selected builds the same targets
(--lib --bins --tests), the wasm check takes the changed crates in one
call, the full test step is upstream's.
- apps/lightcraft/src/control_server.rs: upstream's `before` closure, with
the batch's no-delay sockets kept.
- docs/parity-checklist.md: LR-BEHAV-ACCESS from main (tests/it path),
LR-BEHAV-LOCALE / LR-BEHAV-LOCALIZE from the batch (Polish).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #2689 (head 0d64d9ef3):
- Edit In (#1417) returned an error when the editor failed to open although
photo.editExternal had already added the copies to the library, so a script
retrying on ok:false made another set each time. It now succeeds with
`failed` set (null on success, as open_originals) and a toast saying why.
Test edit_in_reports_an_editor_that_wont_open_without_failing fails without
the fix.
- docs/gaps.md, docs/parity-checklist.md: one entry was listed twice on the
Change line.
- Revision-history rows the batch added now read newest-first (gaps,
parity-checklist, localization-parity, target-app-parity); upstream's own
rows are left where they were.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: dev profile keeps line tables for workspace crates and no debug info for dependencies
* Control server tests: persistent_requests_and_slow_replies_outlive_the_idle_read_timeout no longer races the idle timeout
Each request (and the closing half-shutdown) reaches the socket before the
worker reads, so on a slow machine only the reply waits outlast the 100 ms
idle timeout, as the test intends. The idle test sends its input the same
way, and the no-frames test allows 5 s instead of 2 s to give up.
* CI: one integration-test binary per crate
Each crate's tests/*.rs become modules of tests/it/main.rs (51 test files
into 13 binaries, plus 6 that stay their own). Inner #![cfg] attributes
move to the mod lines. Files that change state for the whole process keep
a binary of their own: codecs jxl_tail_heap and raw sr2_heap (dhat global
allocator), gpu shutdown, shutdown_first and memory, ui-egui shutdown.
Docs, comments and the GPU probe name the new paths and targets
(--test it <module>::); the CI report names an it binary's skips after
the crate.
* CI: tests under nextest, doctests apart, one wasm check
xtask ci runs the tests with cargo nextest run --lib --bins --tests when
cargo-nextest is installed, else cargo test with the same targets and a
one-line notice; doctests run after them with cargo test --workspace --doc.
The heif and hardware GPU steps use the same runner. Examples stay out of
the test build (clippy --all-targets still checks them). Concurrent tests
under nextest follow the RUST_TEST_THREADS cap (NEXTEST_TEST_THREADS).
The wasm check is one cargo check --keep-going over the same crates, and
clippy runs with --keep-going.
* CI: tests run under cargo test by default, nextest with LIGHTCRAFT_NEXTEST=1 (per-process scene cache)
The launch window no longer probes each recent library with the lock check
of File > Open Library (which created catalog.lock and wrote
catalog.lock.owner into every free library at every launch, including
other drives, network shares and sleeping disks). A row is now checked by
looking only: the path exists, is a folder, can be listed, holds a
LightCraft catalog, and that catalog opens for reading. A library another
program has open is found when Open is pressed: the open fails with the
lock owner's message, the window stays and greys the row out.
New test: listing (and drawing the window) leaves every library folder
byte-identical (contents and modification times); it fails with the old
probe. Docs (What's New, parity row, control protocol) say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
Settings > General > At launch: Open the last library (default, as before)
or Ask me. With Ask me the desktop app opens nothing and the library-problem
window lists the recent libraries instead (name, folder, when last opened;
missing, locked or no-longer-library ones greyed out with the reason; the
last one selected): Open, Open Another..., New Library..., Remove from List,
Quit, with arrows / Return / Esc. Alt/Option held in the first 2 s after the
window appears shows the same list once over the library that opened (winit
reports no modifiers before the first frame; macOS only on a change).
File > Open Recent Library > Remove from List and
app.recentLibraries {remove: path} forget one entry; nothing on disk is
touched. The list reports lastOpened, kept under a new settings key
(recentLibrariesOpened) so older files and versions keep their list.
--library and LIGHTCRAFT_LIBRARY keep priority (launch_plan), --memory never
asks, lightcraft-cli never reads ui.json. Settings are saved while the list
shows; command-line files go into whichever library opens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
LRC-LIB-CATALOG-IMPORT. Before File ▸ Import Lightroom Catalog… starts, a
window asks where the catalog goes: merge into the open library (named,
with its photo count; preselected when it is empty) or create a new
library named after the catalog file, next to the open library (both
editable, Choose… picks the folder). The new library is made and opened
through app.newLibrary (its rules: an empty or new folder, refused while
another import runs), then the catalog is imported into it. Cancel does
nothing. Open Library… on a Lightroom catalog goes through the same choice.
Opt-out: Settings ▸ General ▸ When importing a Lightroom catalog (Ask
where to import it / Always merge into the open library), saved as
ui.json settings.lightroomImport (lenient, default ask); the window's
"Don't ask again" picks Always merge. A session in memory merges without
asking, as before.
file.importLightroom takes {into?: open|new, name?, location?} to choose
without the window. UI layer only: library.importLightroom, the engine
and lightroom_import.rs are unchanged.
Tests: decision table (setting x library on disk/in memory x photos x
into), params, default name; headless window / merge / new library /
cancel / empty library / setting / Don't ask again persisted / folder
not empty / into / in memory / stale library; tests_open_lightroom
follows the new window. Format messages in all 17 catalogs, plain
strings in French. Docs: lightroom-catalog-import, control-protocol,
parity-checklist, whats-new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
Measured on 68 photos straightened by hand in Lightroom Classic and 300 left as shot, Auto
straighten turned 79 of the 300 by more than 1° (65 by more than 2°): the edges of planks, a
branch or a bird's wing were taken for the horizon. It now answers only for at most 10° and on at
least 3 agreeing lines (Upright Level is unchanged): 19 of the 300, with the same 14 of the 68
levelled within 0.5° and 1 instead of 3 more than 1° off. When it finds nothing, the app says so
instead of leaving the photo without a word. LR-CROP-AUTO goes to 🟡: it still leaves most tilted
photos alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/parity-checklist.md, docs/gaps.md, docs/target-app-parity.md: rows two PRs both
changed had been kept twice by the doc resolution. Each row is now upstream main's row
plus every merged PR's own edit (3-way against the PR's merge base), so main's newer
text is kept (LR-LIB-FLAG #1013, Lanczos-3 in LR-MERGE-HDR / -PANO, LR-EXP-DIM #1620,
gaps 8 and 10) and the PRs' additions too; LR-BEHAV-LOCALE / -LOCALIZE take main's
Georgian and #1846's Polish. `cargo xtask parity --write` refreshed the summary.
- #1313 added "Show Frame Rate", a switch; #2195 names switches for what they show
(menubar::tests::toggles_are_named_for_the_thing_not_show): now "Frame Rate".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>