Resolved against rb-base-36: Scanned keeps the base's rewrite flag next to magic and tag; Exif keeps the Panasonic lens fallback; locale catalogs merged (one duplicate key dropped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Task: LRC-LIB-CATALOG-IMPORT
Count offline records, Local promotions and virtual copies correctly. Keep destination, warnings and errors in a persistent import result, with Show in Library clearing filters. Preserve library-switch ownership, reimport identities, existing edits and one-step undo.
Avoid repeated missing-original probes and unnecessary staged-photo, catalog-operation and develop-patch copies. Preserve upstream phased progress and typed cancellation. Address Astra review findings with regression coverage.
Validated the combined main tree with all eight cargo xtask ci gates (3,462 passing test executions; zero failures), fresh 30k import/persistence/metadata checks, duplicate-free same-session and reopened reimports, and visually inspected headless screenshots. Historical performance comparisons apply to the f730805d production changes before main integration; native Windows import/GUI acceptance remains unqualified.
Generated with Codex.
Photos dragged from the grid onto a Folders row move there on disk and in the library
(`photo.moveToFolder`: files and XMP sidecars, virtual copies follow, never overwrites, one undo
step), as Move Selected Photos Here in the row's menu does.
- The row lights up, and the label following the pointer says where they go: "Move 2 photos to
“Porto”" (onto an album the same gesture only adds them, so the two read apart).
- A closed folder held under the pointer for 0.7 s opens, so a folder inside it can be reached.
- Refused, with the reason on release and a not-allowed pointer: a row that covers several disks,
and Local photos (not in the library). Photos already there say "Already in “Porto”".
- A drop asks first, as Lightroom Classic does: "Move 2 photos to “Porto”?" (their files and
XMP sidecars move on disk; Edit → Undo moves them back), with Don't ask again, kept in Settings
as "Confirm before moving photos to another folder on disk" (on by default). The menu's Move
Selected Photos Here, chosen by name, goes at once. A question left open while another library
opens (the menu bar and agents reach past a dialog) moves nothing: photo ids are per library.
- The toast says how many moved and that Edit → Undo moves them back; the menu item says the same.
- Photos that leave the view leave the selection, so a Delete or edit next can't reach them unseen.
For NAS and network shares: the copy across volumes now streams (it read the whole file into
memory, a problem for a large raw or video) into a free hidden name that takes the photo's name only
once complete (a share dropping mid-copy left a truncated file under the photo's name, which a
retry reported as the photo already there). New engine tests cover a share going away:
Undo with the share offline or read-only is refused with the reason and kept until it can run,
nothing is deleted and the library stays linked; a connection lost partway through a move puts
back what moved; a file the share can't give back rolls the whole Undo back; and a real
move across devices where the machine has a second file system (`/dev/shm`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In an album the Delete key ran photo.delete, which moved the photo to
Recently Deleted, so it also left its folder views. It now removes the
selection from the album only (album.removePhotos), as in Lightroom; in a
smart album it changes nothing. Photo > Remove from Album shows in normal
albums with the Delete key; Photo > Delete Photo still deletes from the
library. album.removePhotos defaults to the album on screen and returns
{removed, album}.
A sidecar write that failed because the folder was offline or read-only was only logged. Keep those photos waiting, write them again once their files are reachable, and tell the user.
A library with no photos opened on the Detail view, whose empty state said "Choose a photo in
the grid or filmstrip" while the grid was empty and hidden: the only screen that mentioned
import was off screen, and Import itself was nowhere but the File menu and its shortcut.
- An empty library (no photos, or only deleted or browsed ones) opens on the Photo Grid with the
sidebar (Square Grid stays as it is), in the desktop app and in `lightcraft-cli snapshot`. The
desktop app switches before it notes what its settings file holds, so the switch alone isn't
saved.
- The grid and the Detail view show "Your library is empty" with Import Photos… (primary) and
Import Lightroom Catalog… (not on the web) as buttons, and a line saying photos and folders
can be dropped into the window, with the key Import Photos… is bound to now (`⌘⇧I` on macOS,
`Ctrl+Shift+I` elsewhere, or a remapped one). Not while a folder is browsed under Local or
Recently Deleted is shown: their photos aren't in the library but are on screen.
- The right-hand panel says "No photos yet / Import photos to get started" there instead of
"Select a photo to edit".
- The top bar has an Import button beside Export in every library. The centred search field's
reserve grows by both sides' share (620, and a 190 minimum), so the filter icon stays clear of
the icons on the right at every window width, also with macOS's title bar in the window. The
top bar's icons are named for screen readers, and a refused click says why in a toast.
- `empty_state` is `empty_message` with buttons that do the next step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds an option under Settings > Import > Cameras and Cards that opens Import from Device when a camera or memory card shows up. It's off by default, and a card that was already in at startup doesn't ask.
The thumbnail cache wrote .jpg files into the library's thumbs folder, which is usually inside
Pictures: photo galleries listed every thumbnail as a photo (issues #610, #1066), and every read
brought the file's date forward, so each launch made them all look new.
- Cache files are named <32 hex>.lcthumb (JPEG data as before). A .jpg of before is taken over
under the new name when it is asked for; the rest when the cache is tidied, which the engine does
once per cache folder, on a thread of its own with a row in the activity stack that shows its
progress and can stop it. Nothing is rendered again.
- Reading writes nothing: what was read is remembered for the session (a full cache lets it go
last), and only a file that went unused for a week has its date brought forward.
- A folder that holds nothing but the cache gets a CACHEDIR.TAG, when the cache makes it and when
it is tidied; one that also holds somebody's files is not tagged (issue #98).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An offer taken asked the box to start a new edit on the next frame; a
box not drawn then (its panel switched away at once) took the cursor
whenever it was next drawn. The request holds for one frame.
- Return on the frame the box scrolled out of sight still took the
highlighted offer, which was no longer drawn: it gives nothing.
- In a window with room for the offers on neither side of the box they
covered it: they go to the side with more room and show as many lines
as fit there, the highlighted one among them.
- Ukrainian says "suggestions" as the panel does; four clearer wordings
(uk, ru, de, ja).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- "+N more" was for the whole session: it is for the selection it was
asked of, and another selection's long list is short again.
- A keyword two photos spell differently was offered twice under one
widget id, and a click on it gave nothing: it is offered once.
- The offers followed the box out of the panel when it scrolled away and
covered it near the bottom of the window: they are not drawn while the
box is out of sight, and go above it where there is no room below.
- The highlight was a line number, so a change of the library moved it to
another keyword, which Return then gave: it stays with its keyword.
- Esc after an offer was taken brought back keywords just added; Shift or
Alt with the arrow keys moved the highlight instead of the text cursor;
a refused add lost the typed text without a word.
- A name the library lacks was looked up in the library twice per change
of the text: once.
- A wrong value under keywordChips or foldedSections discarded the whole
settings file: it is read as the default.
- Groups follow the tree ("Places > Italy" right after "Places"); a heading cut
short says its whole name on hover; the number of chips measured for a
shortened list follows the box's width; the selection is asked for once
a frame.
- The nine new plain messages are translated in every language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A view is asked again after every edit, so a photo that an edit makes stop
matching it leaves the grid at once and every photo after it moves up: a
keyword given in a smart album of photos without keywords takes the photo
away from under the cursor, and in a random order, with no landmarks, the
shift reads as a new shuffle (the seed does not change).
View > Hold View Until Refreshed (`library.holdView`, off by default, kept
with the library's preferences): a photo the view shows stays in it when an
edit makes it stop matching a smart album's rules, Picks or the filter bar.
View > Refresh View (`library.refresh`, also in the sort menu while the hold
is on), Reshuffle, another source, filter or sort, or new rules for the
smart album shown, shows what matches now.
- Photos that start matching still appear at once.
- A photo that is deleted, or taken out of the album or folder shown, still
leaves at once: a held photo has to be where the view looks
(`Catalog::query_keeping`, `within`).
- A source with no filter over it holds nothing and is queried as before.
- A refresh keeps the seed.
- The grid's count no longer reads "3 of 2" when a held view shows more than
now match its source.
Each edit still asks the whole library again; that cost is not addressed
here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photo ▸ Photo Merge ▸ HDR for Each Bracket Set (`merge.hdrBatch`) merges many brackets in one go with the last HDR
options, as Lightroom merges a selection of stacks: one HDR per stack among the photos, then per bracket among the
unstacked ones (`Catalog::bracket_sets`: photos taken less than about a second after the previous one's shutter
closed, at exposures at least ½ EV apart; bursts at one exposure and earlier merge results are left out). The app runs
the merges one after the other in the activity stack (cancelling one stops the rest; a failed set doesn't); the
command runs them inline for the CLI and MCP (`lightcraft-cli merge hdr-batch FILES…`) and reports merged and failed
sets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Activations stay on the device between layers (net/program.rs lowers the graph; every
operator is a WGSL kernel, one submission and read-back per run); faces of a photo go
in batches of up to 16; Settings > Faces shows each model's device and a CPU-only
switch (faces.gpu); the scan's pace limits GPU use; the GPU check is an activity row.
YuNet 58 -> 21 ms, SFace 40 -> 9.6 ms, AuraFace 295 -> 71 ms (heavily loaded machine).
Sources: own design.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A raw imported while its format couldn't be decoded kept preview_only until a
manual Reload, so new decoders in a release never reached existing photos.
When a library opens in another app version, its preview-only photos whose
stamp differs are probed again in the background and get exactly what Reload
from Disk gives them (shared reload_ops): a file that decodes now loses the
flag, one that still doesn't gets the current reason and a fresh stamp. The
result is one journaled system batch, not an undo step; develop settings and
history are kept. Unreadable files are left for next time, photos changed
while probing are skipped, and a result for a library closed meanwhile is
dropped. A toast says how many raws now develop from their raw data.
- engine: cmd/recheck.rs (stale / probe / apply) and
library.recheckPreviewOnly {all?} -> {checked, updated, cleared}; refused
without file access, where every probe would clear every flag
- desktop: recheck::tick once per library generation, through tasks::spawn
("Checking raw files" in the activity stack)
- the preview-only notice (Edit, Info) gains a Reload from Disk button
- strings translated in all nine catalogs; docs/mcp.md and the parity row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>