CodeRabbit on #2689 (head 496291276):
- `cull` takes the view it hands on from before ending an open drag, so the
drag's own commit can't change it first. (In practice the view already
follows the drag's preview, so the photo is out of it mid-drag either way;
this is the order every caller should use.)
- LR-BEHAV-LOCALE cites the commands of all 18 languages.
- Two 2026-10-21 revision rows moved above the 2026-10-20 ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #2689 (head 02743d187):
- crates/engine/src/cmd/library.rs: `cull` (#2327: a rating, flag or label
that takes the active photo out of the view) and `photo.delete` /
`photo.deletePermanently` moved the active photo without ending an open
interaction, unlike `step` and `library.select`: the drag stayed open with
no undo step. Each ends it now before its own change, so the drag is its
own undo step and comes first: undo brings the rating or the photo back,
with its edit, then the edit (Delete Permanently's undo restores the
edit too). Test an_open_edit_ends_before_the_selection_moves_on (rating,
delete, delete permanently) fails without the fix.
- docs/parity-checklist.md (LR-BEHAV-LOCALIZE) and docs/localization.md: list
every shipped language (Polish, and the others the row lacked; Italian in
the localization doc).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
library.source cleared the filter's library folder when a library folder became the source, but not the filter's album when an album did. LibrarySource::to_filter makes the album shown replace the filter's, so the grid was right; but the filter bar has no album chip, so the old album stayed in the filter unseen and narrowed the next source (All Photos, Picks). As for a library folder, the one shown now wins.
Found while testing crates/engine/src/cmd/library.rs:736
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 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>
The command sweep ran metadata.downloadPlaceNames with empty params, which
starts a real download from download.geonames.org into the settings folder.
The sweep now skips it (the command has its own loopback-server tests) and
asserts that no place names download was started.
Co-Authored-By: Claude <noreply@anthropic.com>
A colour-range mask compares every pixel against every sample, so
render and export cost is pixels x samples. The interactive sampler
stops at 5, but mask.add / mask.update (colorRange) passed their
`samples` array straight through, so a few hundred samples on a large
render turned into billions of operations and hung rendering and
export. The project already caps similar lists (MAX_STROKE_DABS,
MAX_DETAIL).
The samples list from automation is now capped at 64.
Test: a request with 1000 samples is cut to 64; a two-sample request is
unchanged.
Reported-by: BS.ehlas.cz
Find Missing Photos took a missing photo's file name with Path::file_name,
which doesn't split on `\` on macOS or Linux, so a library made on Windows
had photos it could only find by content: those with no hash on record
(Lightroom-catalog imports, files edited since) were never found. Both the
missing photos and the files found on disk are now named by one rule: the
part after the last `/` or `\`, in lower case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two catalog additions and one internal change, with nothing calling the new ones yet:
- Catalog::folder_records_located: folder_records_follow for a folder located where it
already is on disk (records of library folders at the destination stay).
- Catalog::local_records_of: the Local records of some files, split into those that can
give way and those that hold something of the person's.
- Find Missing Photos hashes a file in 64 KiB pieces instead of reading it whole (the same
hash).
Switches read "Show Focus Point ✓" next to "Filmstrip ✓"; now every switch
is named for the thing and the check mark says whether it is on (Focus
Point, Clipping, Photo Counts, Mask Overlay, All Mask Overlays, Mask Pins,
Layout Image). Show Original stays: it is a view, not a switch.
Names that meant two things: View ▸ Detail and the Edit section Detail
(Detail View now, like Reference View), Window ▸ Edit and the Edit menu
(Edit Panel), Compare and Compare Before and After (Before/After Side by
Side, next to Split and Top/Bottom), and Clear/Discard Preview Cache (one
command, now Discard Preview Cache everywhere). New labels in all 13
catalogs. Tests: no "Show …" switch; no two menu commands share a label or
a menu's name.
Co-Authored-By: Claude <noreply@anthropic.com>
A full count (AlbumTally::of, albums.list) keeps each loop-free smart album's photos for the pass and
answers "album is #N" from them, so an album tested by 51 others is worked out once instead of 52 times
per photo. Albums in or reaching a loop, or in a chain too deep for the depth limit, are evaluated by
their rules as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/engine/src/media.rs, crates/engine/src/smart.rs: DecodedSource keeps both new fields, from_proxy (#1809) and reduced (#2263); a smart preview loads with from_proxy true, reduced false.
crates/engine/src/cmd/previews.rs: original_only refuses both proxy-loaded and reduced sources.
crates/ui-egui/src/panels/grid.rs: badge keeps main's double-click guard and calls #2300's toggle_stack
crates/ui-egui/src/tests_grid.rs: both sides' badge tests
docs/parity-checklist.md: header and revision rows from both sides; rows take both changes
crates/engine/src/cmd/previews.rs: the opening refresh takes every photo on disk (#1809) with the preview_fitted_look flag of #1545 (vendor raws and DNGs); SmartJob and refresh docs name both.
crates/engine/src/camera_preview.rs: LOOK_VERSION keeps both history lines and the higher value.
crates/engine/src/media.rs: RENDER_CACHE_VERSION keeps the higher value.
shift_iso writes back only a time it fully reads (a date alone, or HH:MM
with optional seconds and fraction, an optional zone, nothing after);
iso_seconds stays lenient for sorting and rules. photo.setCaptureTime
leaves such photos as they were and counts them as `unreadable`, and a
set that would move other photos by an unreadable time is refused; alone,
the photo gets the typed time. The dialog's preview says the photo's time
can't be read, OK says what was left, any error keeps the dialog open
(button and ui.dialog.confirm), and Set mode doesn't start from an
unreadable time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use chrono for UTC/calendar conversion instead of hand-rolled copies
Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month table with chrono as a workspace dependency:
- apps/lightcraft/src/logging.rs: timestamp (log line time)
- crates/catalog/src/dates.rs: civil + weekday
- crates/catalog/src/stacks.rs: iso_seconds (date -> seconds)
- crates/engine/src/cmd/face_recognize.rs: capture_secs (native only)
- crates/meta/src/datetime.rs: DateTime::unix_seconds and the valid() day check
Output formats and semantics are unchanged (pre-epoch clamp, day rollover that
normalize_iso relies on, leap-second tolerance, EXIF/XMP parsing). chrono uses
only std, so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.
Follow-up to storytold/cadcraft#1785
* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed
chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock as the epoch, keeping the old pre-epoch clamp.
timestamp and catalog's civil write years past 9999 with {:04} like the old
{y:04} did, because %Y would add a '+'. Tests cover the out-of-range and
5-digit-year cases.
* Don't build an unrepresentable SystemTime in the far-future test
UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.
* time: preserve old out-of-range date conversion (no epoch fallback)
`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01, changing output the old hand-rolled
Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was
318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).
- apps/lightcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/catalog/src/dates.rs: `civil` cycle-based (the callers stay inside
0000-9999, but a far clock no longer resets to the epoch).
Pinned old outputs in tests:
logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
civil 10_000_000_000_000 -> 318857-05-20T17:46:40,
-10_000_000_000_000 -> -314918-08-13T06:13:20, i64 extremes
* Don't build a year-318857 SystemTime unchecked in the logging test
Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.
* Name the 400-year range extension as the one exception to the time rule
The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.
* Say the library computes month and day; time-of-day is plain arithmetic
Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.