* LR-PREF-SHORTCUTS: the shortcuts panel's Fixed keys ignore keys saved for unknown commands
* KEYC-METADATA: an Add Keywords request the Keywords panel can't take in time expires
* LR-META-INFO: Target Photo saves an Info edit to the photo it shows
Preserve current main, adapt frame/input and IME handling, and keep library
check results stable across layout passes. Include the matching Nix font lock
and reviewed attribution changes.
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.
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.
CodeRabbit on #2689 (head 496291276):
- docs/control-protocol.md, `app.recentLibraries`'s doc, `is_missing`'s
doc: a folder that can't be listed is `exists: null` (couldn't tell), not
`false`, since 142e496f5; the launch window still shows why for every
reason.
- a_failed_lookup_doesnt_mark_a_recent_library_missing skips (System) when a
folder with no permissions can still be read, as when running as root.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #2689:
- `unavailable` used `exists()` / `is_dir()`, which are false for any
error: a parent folder that can't be read or a share that timed out came
out as "Not found" and greyed the entry out. Only an absent path is "not
found" now; other lookup errors are "This library couldn't be checked:
<reason>" (LookupFailed), and a folder that refuses to be listed is no
longer counted as missing either (it is there: permissions, macOS
privacy), so neither is greyed out and opening says why.
- Every recent library was checked again every 30 s while the app ran,
waking external drives and network shares. A library not found is looked
for again every 30 s (a drive plugged back in; an absent path wakes
nothing); the others only when the window comes back to the front, for
the 8 entries shown; paths never checked are checked once.
Tests a_failed_lookup_doesnt_mark_a_recent_library_missing and
recent_libraries_are_checked_again_only_when_the_window_comes_back fail with
the old lookup / the old timer / no timer for libraries not found.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
crates/ui-egui/src/lib.rs: the batch's handle_dropped_files holds the photos
(library_problem::hold_dropped) before the Lightroom / running-import
checks. The drop card (drop_overlay.rs, LR-IMP-DRAGDROP) says the photos
wait instead of announcing an import or a review; test
photos_dropped_with_no_library_open_wait_for_one drops through
`ui.dropFiles`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While no library is open (the launch list, or the window of a library that
failed to open), a drop went into the in-memory session that the library
chosen then replaced, so the photos were gone from view. They now join the
command line's files (`pendingImport`) and are imported into the library
that opens (or the temporary session), with a toast saying so. Found by
CodeRabbit on #2689. Test
photos_dropped_on_the_launch_list_go_into_the_library_chosen fails with
the hold disabled.
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>
Review round: whether a recent library is there is kept apart from whether it can be
opened. `Unavailable::is_missing` (not found, a file, no library, a folder that can't be
listed) is the only answer that greys out File ▸ Open Recent Library and makes
`app.recentLibraries` say `exists: false`; an unreadable catalog or a failed check gives
`null`. A failed open from the launch window (say another program has the library)
drops the answer so it is checked again, instead of storing the open's error as one; the
launch list keeps the error on its row. Test: Open on a locked library from the launch
window, then the menu entry stays enabled and `exists` is true (fails if a failed open
marks it missing); an unreadable catalog gives `exists: null`. Docs worded to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round (CodeRabbit + an adversarial review, handled together):
- The launch list starts with every row "Checking…" and a worker per path checks it (a
share whose server is gone, or a sleeping disk, can take seconds or never answer; one
that hangs doesn't hold the others). After 5 s a row says the drive isn't answering;
it can still be opened. Once every answer is in, the selection moves to the first
library that can be opened, unless the person moved it.
- File ▸ Open Recent Library and `app.recentLibraries` no longer look at folders either
(the menu is rebuilt every frame): they use the same answers, refreshed after 30 s, when
a library is opened, removed from the list, or fails to open. `app.recentLibraries`
gives `exists: null` and `checking` until a check answered.
- Reasons are found on the worker and put into words on the UI thread, in the interface
language. A check whose worker dies ends as "This library couldn't be checked".
- Each check holds an activity row ("Checking recent libraries"; quick ones don't show).
- Docs: control protocol (`checking`, `available`, `exists`), What's New; fr strings.
Tests: slow check never holds the window; late answers keep the person's selection; the
menu takes its answers from a worker (fails with the old is_dir()); reasons in French; one
hung drive doesn't hold the others; a dying check ends; reopening clears a missing answer.
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
While files are dragged over the window a veil and a card say what dropping
does (review N items, import N presets, open the face model; close the open
dialog first when one is open). Dropped photos and folders open the import
review like File > Import; Settings > Import can import them at once. A drop
while a dialog is open is refused with a toast. The web build now reports
unreadable and failed drops. Adds the control request ui.dropFiles.
Co-Authored-By: Claude <noreply@anthropic.com>
When the scan finds nothing to import, relink, remove or read, the dialog
showed four greyed choices (two still ticked) and a Synchronize button
that started an empty job with an activity row. It now shows the folder,
"The library is up to date with this folder." and any Skipped lines, with
one Close and no Cancel (`only_shows`). `sync::confirm` answers
`{"upToDate": true}` and starts nothing, so `ui.dialog.confirm` and agents
get the same result.
Co-Authored-By: Claude <noreply@anthropic.com>
Keep the stored destination consistent with the picker's None fallback after
catalog changes. Extend the real-file import regression to delete the chosen
album before confirmation, then verify import membership and undo.
Document the helper contracts and destination transitions for review coverage.
Co-Authored-By: Codex <noreply@openai.com>
Reuse the shared album picker in Import Photos, retaining None/New album,
editable names, album-context preselection and existing import semantics.
Keep long paths within the viewport and cover hierarchy, localization,
keyboard choices, real imports, undo and layout regressions.
Co-Authored-By: Codex <noreply@openai.com>
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>
Photos imported before focus points were read got one only through Reload from Disk, which
hashes whole files on the UI thread. Now:
- lightcraft_meta::focus::read_header reads only what the decoder looks at: IFD0 (Make,
Model), the Exif IFD, the maker note's IFD and the focus tags' values (a JPEG: its segments
up to the Exif one), walked round by round and decoded with the whole-file code from a
buffer of the file's length holding just those ranges. Bounded rounds, bytes (2 MB) and
size (1 GB). One 32 KB read per α1 / A7S III ARW; on 109 real files (83 with a point)
it gives the whole-file read's point for every one.
- A background job (crates/engine/src/focus_scan.rs) reads photos with no point from a
supported body (by the stored camera; nothing else is opened), ARW or JPEG, one at a
time in path order, with a "Reading focus points" activity row (Cancel::Flag). The UI's
per-frame focus.pump gives it the face scan's pace (paused while dragging or typing) and
starts it once per library opening. Commits every 200 photos or 3 s as system ops (no
undo step, no sidecar write), only meta.focus, only on photos that still have none at the
same path. "Looked, none" goes to focus-scanned.bin beside the library, headed by the
decoder's tag so a decoder knowing more bodies starts afresh; unreadable files are retried
at the next opening.
- photo.readFocusPoints {ids?, wait?} (Photo > Read Focus Points) runs it on demand.
Measured cold on a 5400 rpm USB drive: 200 α1 / A7S III ARWs in 1.9 s (~100 photos/s,
32 KB each), so 58,796 raws take about 10 minutes instead of re-reading 2.25 TB.
Read the autofocus point from Sony maker notes (FocusLocation 0x2027, FocusFrameSize
0x2037, FocusMode 0x201b, AFAreaModeSetting 0x201c, AFTracking 0x2021 and the area mode
used from the enciphered 0x9402 block), for the ILCE-1 and ILCE-7SM3, whose layout was
checked against ExifTool's output on 333 real ARWs. Manual focus, centre-only Multi
reports and unchecked bodies give no point. The box is stored on the upright photo as
Meta.focus (omitted when unknown; no catalog format change), filled at import and by
Reload from Disk.
View > Show Focus Point (Alt+F) draws it in Detail, Compare and Survey through rotation
and crop; View > Zoom to Focus Point (on by default) makes Toggle Zoom centre on it; the
Info panel shows "AF-C · Wide · Animal Eye Tracking". photo.inspect returns meta.focus.