Reset Edits rebuilt a photo without a default-preset look from DevelopSettings::default(),
so a raw imported with capture sharpening 40 and colour noise reduction 25
(DevelopSettings::for_raw) came back with both at 0, and with its embedded lens
corrections off. It now starts from the photo's own built-in defaults
(Photo::camera_defaults, the same as import) and keeps the white balance of its
current as-shot reference, as before. Rendered files are unchanged (their defaults
are DevelopSettings::default()). Reported in #763's notes.
Only proxies of raws that take the per-file camera look (Photo::relative_wb) go stale: JPEG/PNG/DNG proxies are never rebuilt for the look. The opening scan runs once per LOOK_VERSION (a look-version marker in the smart previews folder), not at every library open; Build Smart Previews still refreshes stale proxies. Each proxy is checked and written under one lock, so the background refresh and library.smartPreviews never write the same file at once. LOOK_VERSION 2: main's DRO / ILCE-7CR camera-look changes (#528, #583, #568, #616).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a photo's AI Denoise picture is being made or waits its turn, the
stack shows "AI Denoise" with the running photo's tiles as a percentage
and its file name (no cross: what is made follows the Denoise amounts and
Settings). The row goes when the queue is idle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While photos are left to search for faces the stack shows "Finding
faces" with how many are done (no cross: the scan is switched off in
Settings). The engine keeps the scan's peak, so Settings and the stack
agree. Also translates the "Downloading model" label.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Downloading the SAM 3, face and AI Denoise models shows a row with the
model's name and the megabytes so far; its cross stops the download like
the Settings button. The denoise download tests get their own scratch
folders (they shared the face tests' names and could delete each
other's files).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build Previews, Build Smart Previews and Discard Smart Previews show a row
with the photo count and stop from its cross; the start toast goes, the
finish toast stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The registry is reachable as commands, so the UI, the control channel,
the CLI and MCP list and cancel background tasks the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `ExportOptions.hdr` (`hdr` param): photos edited in HDR export as ISO
21496-1 gain map JPEGs (the SDR rendition as the base, the gain map measured
against it as written) or 32-bit float TIFF with highlights above 1.
Other formats and SDR edits export as before.
- Output sharpening and watermarks apply to both renditions.
- `develop.hdr {enabled?, maxEv?}` turns HDR editing on/off and sets the
headroom limit.
Checked on macOS: ImageIO reports both the ISO and the Apple gain map, and
CoreImage's HDR decode of a 3 EV edit reaches 2.95 EV.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scan went stale on any catalog change anywhere (a background import,
a rating elsewhere), and folder.synchronize then scanned again inline:
on the UI thread, and acting on results the person never saw. A scan
now stays current while the folder's own photos are the same records
(Session::take_folder_changes compares them by identity). The dialog
asks with `scanned: true`, which refuses a stale scan instead of
redoing it; the dialog then stays open and scans again in the
background. Agents that just call folder.synchronize still get a fresh
scan.
The app dropped session.folder_changes every frame no dialog was open,
so an agent's folder.scanChanges never reached its folder.synchronize
in the running app; it now only lets go of a scan its dialog made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file only browsed in Local was counted as already in the library and
silently dropped; it is now new, and synchronizing brings it into the
library, as an import does.
A photo whose file was renamed or moved within the folder showed as
missing while its file counted as a skipped duplicate, so "Remove
missing" sent the photo and its edits to Recently Deleted and imported
nothing. The scan now matches a missing photo's content hash among the
folder's files and lists it as moved; synchronizing relinks it (on by
default, "Relink photos whose file was renamed or moved"), in the same
undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A folder that isn't there (moved whole, or on a disk that isn't
connected) used to scan as "every photo missing", one tick away from
moving a whole disk to Recently Deleted. The scan (on its worker, so an
asleep share never stalls the UI) now reports it as offline and both
commands refuse it; the dialog says so. A whole disk or a folder holding
disks needs `disk: true`, as Remove Disk from Library does; the disk rows
pass it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
folder.scanChanges keeps its result in the session; folder.synchronize
uses it (once) while it is of the same folder and the catalog has not
changed since, so a scan made on a worker thread is not repeated on the
UI thread and files that arrive after the dialog was shown are left for
the next synchronize. Otherwise it scans again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scan compares a library folder (and the folders inside it) with the
disk without changing anything: new files (the import scan's duplicate
rules), missing photos (the Missing Photos rule) and metadata updates
(XMP sidecars saved after the photo came in or was last edited here
that say something the library doesn't). It runs without the session
(SyncInput + scan_with) so the app can scan on a worker thread.
folder.synchronize imports the new files in place and, when asked,
moves missing photos to Recently Deleted and reads the updated
sidecars, all as one "Synchronize Folder" undo step. Only folders the
library holds photos in are accepted, never the startup disk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes. folder_records_follow now drops records left at the
destination even when the moved folder carries none, so an unlabelled
folder renamed onto an old path no longer inherits that path's label.
folder.label {label: none} works once the folder's photos are gone (a
label can no longer get stuck); setting a label still needs photos.
Adds tests for both, undo/redo of a labelled move, and labels replayed
from the journal without a snapshot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
folder.label {path, label} gives a library folder a colour label (or
takes it off) as one undo step; only folders the library holds photos in
are accepted. library.folders reports each row's label. folder.rename and
folder.move commit the folder records with the photos' relinks, so the
labels of the folder and the folders inside it go along and undo/redo
carry them back and forth.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export now leaves out only what it must, and its counts are exact:
- A name starting with a byte-order mark is left out. On the first
line, the reader would take the mark for the file's own.
- A synonym in braces is written, since it reads back. An empty
synonym, or one that repeats another in a different case, is
skipped quietly, as the reader would drop it anyway.
- keyword.export counts and checks for Capture One the keywords it
actually wrote, now returned with the text. It used to guess from
the lines, so a name starting with a brace was missed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A list saved as UTF-16, as Windows' Notepad can, now says to save it as
UTF-8 instead of only "not UTF-8 text". Text passed to keyword.import is
capped at 16 MB, like a file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The list format can't hold some names. A name in brackets or braces
would read back as a left-out keyword or a synonym. A line break or a
leading space would split it or break the indentation. A keyword nested
deeper than 64 levels won't read back either. These keywords, and those
inside them, are now left out of the file. keyword.export names them as
`unwritable`, and the toast says which.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `keyword.export {path?}` writes the keyword list file (Lightroom
Classic's format, which Capture One, Photo Supreme and Bridge read),
or gives its text, and names the keywords Capture One's importer
refuses (none of ; , < > may be in a list it reads).
- `keyword.import {path | text}` reads one (Photo Supreme's Formatted
Vocabulary File too) as one undo step → {added, updated}. A line it
can't read is named and nothing is taken; a file larger than 16 MB or
not UTF-8 is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A smart preview keeps the colour and tone curves the camera-look fit gave it, so a
fit change (#499, #528, #583) left existing previews with the old look. They now
record LOOK_VERSION in the header; an older one is rebuilt from its original when the
library is opened or Build Smart Previews runs, and kept as it is while the original
is offline.
Saving compared set names by any letter's case while using, deleting
and the current set compared ASCII case only: "été" saved over "ÉTÉ"
but couldn't use or delete it. Every set command (and Recent Keywords)
now compares names one way, as keywords are compared.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving Recent Keywords as a set under the name of an existing one
(Edit Set… on Recent Keywords) replaced that set's keywords without a
word, while renaming onto a taken name was refused. `keyword.saveSet`
takes `new: true` for a set that must be new, and refuses a name
another set has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`keyword.saveSet {name, replace}` without `keywords` took the current
set's keywords, so renaming a set that wasn't current gave it another
set's nine. The keywords default to the renamed set's own now (the
current nine only for a new set), and an empty `replace` is no rename.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For Edit Keyword Set (Lightroom Classic's), `keyword.saveSet`:
- keeps each slot in its place: an empty one ("") is an empty slot, so
the keywords after it keep their ⌥ keys (empty ones were dropped and
the rest moved up); trailing empty slots go, nine at most;
- holds each keyword once, whatever its case: a second one leaves its
slot empty;
- with `replace`, renames that set in its place among the sets and
keeps it current; a name another set has is refused.
⌥ on an empty slot does nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`keyword.toggleFromSet` checked whether every target photo had the
keyword by ASCII case, while the set's buttons and the rest of the
keyword code match any letter's case: with "ÄRZTE" and "ärzte" on the
photos the button showed on, but ⌥N added the keyword (a no-op) instead
of taking it off, so it could never be toggled off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rename, merge and delete answered `changed` with every change they
made, so with the keyword list it no longer meant photos (renaming a
keyword no photo has said 2). `changed` is photos again, and `listed`
counts the keyword list's changes (what Purge reports).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`photo.setMeta` stored keywords as given: " beach " showed in the tree
as "beach" but couldn't be found there (Edit, Move, Delete said there
was no such keyword), and duplicates were caught by ASCII case only.
Keywords are now stored cleaned (`a | b ` is `a|b`, empty ones are
dropped), each once whatever the case of any letter, and removing one
matches it the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`keyword.move` worked out where the keyword landed from the parent's
spelling in the library, and fell back to the top level when the
parent didn't exist yet, while the catalog made that parent: the
keyword filter stayed on the old path and the grid showed nothing. It
now lands the filter where the catalog puts the keyword.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Put New Keywords Inside This Keyword" was a session preference that
followed renames by hand: undo and redo never moved it back, so after
undoing a rename it named a keyword that no longer existed and the
next new keyword silently made that parent again. It is now a mark on
the keyword in the catalog's keyword list (`new_keywords_inside`, one
keyword at most): it moves with its keyword (into a merge too), goes
with it, comes back with undo and redo, and is kept with the library.
Setting it is one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merging `a|b` and `a|c` into `a` moved the listings one at a time: the
`a|b|c` listing landed on `a|c` before that one left for `a`, merged
into it, and its attributes were lost. Every moving listing now leaves
first, then each lands. Each keyword also moves by the closest keyword
merged (`a|b` before `a`), so the order they are given in doesn't
change the result; the keyword filter follows the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>