The text fields and GPS saved with no `ids`, so `photo.setMeta` wrote to
whatever was selected when the edit ended. A selection change during an
edit then gave the typed text to the new selection. Each edit now keeps
the selected photos from its start and saves to them.
craft-ui and craft-docpath are pinned to craft-libs
17744430be327d0257496a6a31c8d38fe20a6756, where a divider drag keeps the
split's sizing mode. LightCraft no longer turns dragged ratios back into
side-panel widths.
Lightroom Classic's keyboard shortcut page lists Option+0 / Option+Shift+0
(Alt+0 / Alt+Shift+0 on Windows) for cycling forward and backward through
the keyword sets; LightCraft had no key or command for it. Next Keyword
Set (keyword.nextSet, Alt+0) and Previous Keyword Set
(keyword.previousSet, Alt+Shift+0) make the set after or before the
current one current, in the Keyword Set menu's order: Recent Keywords,
then the saved sets. The keys wrap around at either end, and Alt+1 to
Alt+9 then apply the new set's keywords.
The two command labels have rows in every catalog. The KEYC-METADATA and
LRC-LIB-KEYWORDS rows, docs/ui-parity.md and docs/library-shortcuts.md
list the keys.
craft-ui stores a moved divider as a ratio. After each frame the split
beside a side panel takes the width the panel was drawn at, so the panel
keeps its width when the window resizes and left_width / right_width follow
the drag.
craft-ui and craft-docpath are pinned to craft-libs 16fb3d46e1eaa0d455465e4e66a3ac7b17b55bd3.
The saved workspace is craft_ui::docking::Workspace<Panel>; settings written
before it had a version load as version 1. ui.dock params parse as the shared
DockCommand and apply through Workspace::apply_all. LightCraft keeps its
panels, permissions, default layout, photo-view checks, the fallback beside
the photo view for a panel with no recorded place, and its settings file.
The photo view takes side splits (accepts_splits on PROTECTED), so panels
still open and move beside it.
Tests: a version 1 workspace file loads and reopens its hidden panel; a drag
of a group by its bar moves both of its panels.
When a saved keymap gave a key to one command and a later build declared
the same key as another command's default, one press ran both commands,
and menus and Help > Keyboard Shortcuts listed the key on both.
binding() now drops a declared shortcut while the keymap gives its key to
another known command, other than its contextual partner. handle(),
conflicts(), shortcut_of(), the menus and the shortcut editor read it.
In the editor such a row counts as changed, so its restore button takes
the key back.
handle() also let every saved entry stop the fixed keys on its key
(Enter in the grid, secondary keys such as Shift+X, the rating and
label digits, Home, End and Shift+arrows), including entries for
commands the build does not know. It now reads only the entries of
known commands, as binding() does, so a stale entry in ui.json no
longer silences Shift+X, a rating digit or a label digit.
The active photo can be one that is not selected (library.select with an
`active` outside `ids`). With Selected Photos on, the Info fields then
showed that photo's values and marked fields Mixed against it. The fields
now show the active photo's values only when it is selected, and else the
first selected photo's, and compare the selected photos with those.
Copy Metadata and Paste Metadata had no shortcut. They now use
Cmd+Option+Shift+C and Cmd+Option+Shift+V on macOS and
Ctrl+Alt+Shift+C and Ctrl+Alt+Shift+V on Windows and Linux, the keys
Lightroom Classic's keyboard shortcut page lists for copying and
pasting metadata in the Library module. Both keys also work when
Windows and Linux deliver them as clipboard events. The KEYC-METADATA
row now lists the keys LightCraft binds (Cmd+S, the two new keys and
Alt+1 to Alt+9 for keyword sets) and every other key of that table
that is still missing. Gap 28 in docs/gaps.md and the Library keys in
docs/ui-parity.md list the two new keys.
The template file tests now create their folder through a Scratch guard
that removes it when the test ends, including when an assertion fails,
as tests_folders.rs and tests_slideshow_export.rs do.
Photo IDs are local to a library, so the loupe's last photo is forgotten when another library opens, and a photo there with the same ID shows the info overlay again.
On Windows, tests_launch_library::listing_the_libraries_leaves_every_folder_byte_identical
failed every time with os error 33. Its snapshot read every file in the folder, including the
lock file of the library the test holds with LibraryLock::acquire. On Windows that lock is
LockFileEx, and reading the locked bytes through another handle fails with
ERROR_LOCK_VIOLATION.
The snapshot now records each file's length as well as its contents and modification time.
When the read of a catalog.lock fails with os error 33 on Windows, its contents are left out
and the file is compared by its length and modification time. Every other file is still
compared byte for byte, and on other platforms the held lock file is too. The app does not
read a held lock file anywhere, so only the test changes.
Resolves the parity summary (regenerated), drops a duplicated #[cfg(test)], files the What's New entry
under today's date and bumps the checklist's Last updated line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main replaced "Show Clipping" with "Clipping": the PR's Beschneidung term moves to the new key.
Also takes CodeRabbit's two wording suggestions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With several photos selected, the Info panel offers Show Metadata For:
Target Photo (the default) shows the active photo's values as before,
and Selected Photos shows the values the selected photos share. A text
field or GPS where they differ is empty with a Mixed hint, and
Copyright Status shows Mixed. What is typed there is saved to every selected
photo, and a Mixed field typed in and left empty clears it on all of
them. A field left as it was saves nothing, even when the choice
changes during the edit. The choice is saved with the app settings, and
`view.infoSelectedPhotos {on?}` sets it for an agent.
A pass over all 2,565 interface strings and 299 formats messages of the
German catalogs (What's New lines not included). 108 messages change:
- Lightroom terms: Tönung (Tint), Beschneidung (Clipping)
- one word per thing: Bedienfeld (panel; Bereich stays for sidebar
sections), Begleitdatei (sidecar), Dia (slide), Zuschnitt (crop),
Farbmarkierung (label) where "Markierung" read as the pick/reject flag,
Verschlusszeit, Smart-Album, Perspektivkorrektur (Upright),
Aktivitätsstapel
- "Ihre Dateien/Fotos" at the start of a sentence read as the formal
"your" in a du catalog; rephrased
- double colons and "1 Fotos" in count messages, awkward phrasings
- spaced en dash instead of em dash, no space before a final ellipsis,
German quotes around {profile}
The terms are listed in docs/localization-de.md; localization-parity
records the review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View Options gets the Loupe View tab's General section with "Show message
when loading or rendering photos". It is on by default, so the loupe keeps
saying "Rendering…" as before. With it off, the loupe does not show the
"Rendering…" pill while an edit renders, or the "Rendering…" text over an
empty loupe; the "can't be opened" messages still show. The choice is saved
with the app settings, and `view.viewOptions {loupeMessages}` sets it for
an agent.
View Options had no Show Briefly When Photo Changes. A tick box under
Loupe Info 1 and one under Loupe Info 2 now choose the Info that shows
for 3 seconds each time the loupe changes to another photo while the
info overlay is off. Ticking one unticks the other. An overlay that is
on stays on and shows its own Info. view.viewOptions takes
briefly (off, basic or exposure), and the choice is saved with the app
settings; a settings file without it reads as off.
Found while testing crates/catalog/src/local.rs:62 (folder_of), used by forget_local_plan at :192 and :204
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
print.template.save, delete and import changed the template list before
saving the preferences, and left the change in place when the save failed.
A retried import then saw its own unsaved copy and stored another one as
"Name (2)". The list now goes back to what it was when the save fails, as
library.holdView and the slide templates do.
The slideshow always advanced on its own after the slide duration.
The new Manual slideshow setting (Settings > Slideshow > Playback, or
manual in slideshow.playback and view.slideshow) keeps each slide, the
intro and the ending up until the right or left arrow or the control
bar changes it. Space, Esc, random order and repeat work as before.
Exports still use the slide duration. The setting is saved with the
library and in slideshow templates; a template without it reads as off.
The script runs under #!/bin/sh; on dash the array assignment was a
syntax error. Pass the auth option and its value as two plain words.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security: bound codec allocation to what a hostile file declares
A GIF's first image descriptor could name a frame up to four times the
canvas budget and be charged that allocation before any size check saw
it; PNG's compressed text chunks were expanded through an unbounded
inflate; JPEG handed hostile frame headers to the allocating decoders
(zune for the common cases, jpeg-decoder for CMYK/scaled/lossless) and
let an APP1 flood drive quadratic metadata reassembly. Each now refuses
on the same max_pixels budget the other decoders honour, with a
regression test per gate.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* Fix clippy collapsible_if in DCT-scale size check
* Check jpeg-decoder header on scaled path; cap APP1 markers in decode_codes8
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Codebuff <noreply@codebuff.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
- Reverse slider loop order in UI to match Lightroom layout (Highlights, Lights, Darks, Shadows).
- Sanitize splits and amounts against NaN, inf, and inverted split bounds in ParametricCurve::new, at, and region_at.
- Guard param_drag against zero/negative widget dimensions.
- Update live region readout to display the current frame's dragged value immediately.
- Add regression test for non-finite and inverted split values.
Co-Authored-By: deepseek-flash <noreply@deepseek.com>
FredApps on #2689: a re-check started while the launch list is up (a
library not found looked for again, the window back to the front) never
marked its row as checking, so `apply_answers` skipped the new answer: a
library plugged back in stayed "not found" in the list. A row is re-armed
when its path is asked again about an answer it has (not after a failed
open, whose reason stays on the row). Test
a_library_found_again_comes_back_to_the_launch_list fails without it.
Fixes#2774
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbBu4MRiaKxEbSrMTZiLj8
- Expose ParametricCurve from lightcraft-pipeline and use it in both
curve_luts and the UI curve editor, eliminating duplicate implementations
and ensuring render and graph never drift.
- Invert the parametric model while holding the other three regions fixed,
converting vertical pointer motion relative to the grabbed region's initial
amount so a horizontal drag with zero vertical travel leaves all sliders
untouched even with heavy overlapping neighbouring bumps (fixes AlexChambers
report: Darks=60, Lights=0 grabbed at x=0.51).
- Replace all raw table indexing with .get() per never-crash rules.
- Add unit and headless integration tests verifying that horizontal drag
with other regions non-zero changes nothing.
Co-Authored-By: deepseek-flash <noreply@deepseek.com>