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
CodeRabbit nitpick on #2701: a failing assertion skipped giving `Locked` its
permissions back, and a folder left at mode 000 can't be removed. A Drop
guard restores the mode and removes the test's folder on every exit
(found one left behind by an earlier failing run).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbBu4MRiaKxEbSrMTZiLj8
The gate on cf9334907 failed on
tests_slideshow::playback_settings_drive_the_slideshow_and_bad_ones_are_refused
and ::settings_shows_the_slide_layout_and_a_live_preview (#1287); they passed
on the two gates before and in 15 of 15 local runs of the module with 4
threads, and nothing since touches the slideshow. Each checked its result
one frame after the click / after the preview changed, which a loaded
runner doesn't always give: they wait for it now (the checkbox state, the
green backdrop pixel). Test code only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbBu4MRiaKxEbSrMTZiLj8
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>
A finished scan's review that waits for a dialog to close counts as an
import in progress: dropped photos and another scan are refused
meanwhile. Confirming a review while an import (or a Local folder
browse scan, which ends in one) runs is refused too, since replacing
it would stop its worker.
A scan can finish while Settings is open, and Settings can then open
another library. The review records the library generation it was made
in: a review waiting for a dialog to close is dropped once its library
is gone, and confirming one in another library imports nothing.
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>
Read whether a popup is open before the dialog window is drawn: drawing it
closes a dropdown the click lands outside of, so a quick click (pressed and
released in one frame) used to see no popup and close the dialog too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- apps/lightcraft/src/native_menu.rs: upstream's item swapping (macOS keys
given up for real, Help menu not swapped, Settings / Quit tracked) with
KEY-GRID's keymap-aware rows. `Item.label` stays the text the row shows
(native_item_presentation), so `Item::native_label` reuses it instead of
formatting it again; Settings / Quit store theirs through
display_item_label, as upstream's native_label did. No early return after
a rebuild (upstream).
- crates/ui-egui/src/i18n.rs: upstream's tests (#2729: one test per check
across languages, gaps reported, never a failure) plus the batch's two new
ones (dropped-photos notice, Polish counts); production code as merged.
- grid.rs, keyword_list.rs, masking.rs: the translation tests #2729 removed
stay removed; the batch's new keyword and segmenter tests stay.
- cmd/library.rs, ui-egui/src/lib.rs: both test modules, each with its
#[cfg(test)].
- xtask/src/main.rs: #2302's test_selected and upstream's translation_gaps.
- docs: tracker / gap rows take both sides' edits, headers list both sides'
changes, history rows of both sides newest-first. What's New: one section
per date, newest first (the batch had 10-22 above 10-23, and second 10-21
and 10-19 sections); no line lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A message a catalog lacks, or translates as an empty string, shows in
English and fails nothing:
- build.rs no longer requires every formats catalog to carry the same
messages, and tr_format! gets a last arm that formats the English
source, so a message no catalog has builds.
- Coverage tests (every language translates X) and tests of one
language's exact wording are gone; the language-switching, layout,
date, placeholder and user-value tests read the catalog's own value
instead of a hard-coded translation.
- i18n::tests::translation_gaps_report lists, per language, the plain
and format messages it lacks and its untranslated What's New lines.
It always passes; cargo xtask ci
runs it from the test step's binary after the tests.
- catalogs_load checks that every catalog file is a JSON object of
strings with each key once; it.json named "Dimensions" twice.
The drop code runs every frame, mostly with nothing dropped; while the
launch list was up, hold_dropped then toasted "The photos are imported once
a library is open" on every frame, hiding any other toast (the presets
one among them). An empty drop is a no-op now. Found by CodeRabbit on
#2701; the test steps frames with the list up and fails without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dialog_field took the focus back every frame while the field wasn't being edited, so Tab never
reached the check box, Cancel or OK of New Album, New Smart Album, Save Metadata Preset and the
other one-field dialogs. It now takes the focus once, when the dialog opens (the way the keyword
dialogs already did).
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 0d64d9ef3):
- Edit In (#1417) returned an error when the editor failed to open although
photo.editExternal had already added the copies to the library, so a script
retrying on ok:false made another set each time. It now succeeds with
`failed` set (null on success, as open_originals) and a toast saying why.
Test edit_in_reports_an_editor_that_wont_open_without_failing fails without
the fix.
- docs/gaps.md, docs/parity-checklist.md: one entry was listed twice on the
Change line.
- Revision-history rows the batch added now read newest-first (gaps,
parity-checklist, localization-parity, target-app-parity); upstream's own
rows are left where they were.
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>
Review follow-up on #2685. CodeRabbit: the library-switch check ran for Merge only, so a window left over from before a switch could still make a library from defaults that described the old one; it now covers both choices (new test). The Mac session: on macOS the switch's own notice can replace the error toast before the test reads it, so the stale-window tests check confirm's error directly. docs/gaps.md gap 13 updated with the parity row (CodeRabbit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
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