- fr-formats: "{n} photo{0} déplacée{0} vers « {1} »" (the adjective took a literal "(s)").
- docs/localization-parity.md: 7 languages in "Other languages LightCraft ships" (was 4);
Swedish and Polish ship, so they leave the "lacks" list and the target totals.
- xtask ci_lock (#2302): the lock-setup errors name the way out
(`LIGHTCRAFT_CI_LOCK=off` or `=<dir>`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
The engine already applies photo.setMeta to all selected photos via
targets(), but the Info panel and All Metadata dialog always show a
single photo's values with no mixed-value indication.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Find Missing Photos took a missing photo's file name with Path::file_name,
which doesn't split on `\` on macOS or Linux, so a library made on Windows
had photos it could only find by content: those with no hash on record
(Lightroom-catalog imports, files edited since) were never found. Both the
missing photos and the files found on disk are now named by one rule: the
part after the last `/` or `\`, in lower case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LibraryLock::acquire wrote catalog.lock.owner with std::fs::write,
which follows a symlink at that name. A library folder someone else
prepared (a shared drive, an unpacked archive, a synced folder) could
carry catalog.lock.owner -> ~/.bashrc or any other file the user can
write, and opening the library replaced that file's content with the
owner JSON. The catalog's staging files had the same problem and were
fixed in #1502; the owner note was missed.
The note is now written through store::create_staging (made
pub(crate)): whatever is at the name is removed (a link itself, never
its target) and the file is created with create_new. Drop already
removed the note with remove_file, which never follows a link.
Test (Unix): a link at catalog.lock.owner pointing at a file outside
the library leaves that file's bytes unchanged through acquire and
drop, and the note lands in the library as a regular file naming this
process.
Reported-by: BS.ehlas.cz
Both sides' revision-history rows and change lines are kept (newest first);
print_view.rs auto-merged, so main's shared context_menu call sits beside the
PR's current_paper()/paper_name() punctuation and alias fixes.
# Conflicts:
# ROADMAP.md
# docs/gaps.md
# docs/parity-checklist.md