- "photo(s){}" printed "photos(s)s"-style text: the plural suffix now follows the noun ("photo{}"),
as the Spanish catalog does; smart previews repeat it ({0}).
- Menu triangles (▸) in translations replaced by → (the shipped fonts have no ▸ glyph; test
translated_catalogs_do_not_use_missing_menu_triangles).
- "{:.0} %" -> "{:.0}%" so the Merging progress test matches every language.
- Translate the two strings main added since the branch (Camera local tone mapping).
- README, docs/localization.md and the LR-BEHAV-LOCALE parity row list French.
- Test: French plural formatting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A raw without colour matrices of its own now gets DNG ColorMatrix1/2 and
ForwardMatrix1/2 for its model when the model is one of the 52 cameras
in rawtoaces-data (Academy Software Foundation, Apache-2.0): 22 Canon,
11 Nikon, 11 Sony, 3 Fujifilm and 5 others, plus the other names its
alias list gives (Rebel T3i and Kiss X5 for the EOS 600D; bodies it
lists as sharing a sensor). tools/spectral_camera_table.py fits, under
Standard Illuminant A and D65, a white-preserving 3x3 to the measured
spectral sensitivities over rawtoaces-data's 190 training reflectances
and writes crates/raw/src/spectral_table.rs. No number comes from Adobe
or from a raw decoder. lightcraft_raw::spectral looks a camera up by
Exif make and model; the DNG colour model does the rest.
camera_preview::starting_colour takes the first available of: the
file's own matrices, a camera profile for the model (fitted to the
JPEG for tone), the spectral matrices, the file-local JPEG fit, the
neutral fallback. A profiled model whose profile fit fails takes its
spectral matrices. White balance stays relative to the as-shot look for
every raw without matrices of its own, so Photo::relative_wb and the
controls are unchanged; calibrate still pools covered models.
Tests: one per precedence step (synthetic), the table's lookup, aliases
and DNG shape, and a corpus test of the order on ILCE-7M4 (profile),
EOS 5D Mark III and D5100 (spectral), EOS 7D (own fit) and a DNG. The
ILCE-7RM2 and GFX100S corpus tests now expect the spectral matrices.
Attribution in assets/ATTRIBUTION.md and NOTICE. RENDER_CACHE_VERSION 23.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A catalog written before the lens data was read at import (RW2/RWL from
v0.4.0) has none stored, and the lens correction stays off. Reload now
stores the fresh probe's lens data (new journaled, undoable
Op::SetEmbeddedLens; catalog format 4) and, for photos still at import
settings, switches the correction on as import does. Edited photos and
photos with a default-preset look keep their settings.
The parity rows cite panels/right.rs, where the field lives, and say
that it applies on Return or leaving it, that Esc keeps the old angle
and that it takes a decimal comma; the Angle slider row points to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Custom white balance from a Lightroom catalog or XMP sidecar was taken as absolute Kelvin for every
raw. Raws LightCraft develops relative to their as-shot look (ARW, NEF, RW2, RAF, CR3, CR2, PEF; 6500 K
means as shot) then read a small tweak as a large blue shift: ΔE 22 against Lightroom's export on the
photos of issue #510.
The crs mapper now takes a `Target` (preset, rendered, raw with a measured illuminant, relative-WB raw)
instead of a bool. For relative-WB raws, and rendered files with only `Temperature`, a Kelvin value is
converted to the same mired shift from the packet's `AsShotTemperature` / `AsShotTint`. Without that
reference (Lightroom catalogs keep none for Custom photos) the photo stays As Shot and the import
report lists `Temperature, Tint` as not carried over. Import, sidecar read and catalog import decide
the target the same way through `lightcraft_catalog::relative_wb_format`.
Tests: shift from the issue's numbers (3650 → 3578 K lands at ~6275 K), the As Shot fallback with its
report entry, target selection per file, and the catalog mapper's Custom-WB case.
Each photo's develop settings record the rendering process they are
interpreted by (`DevelopSettings::process`). There is one process, V1, the
pipeline as it is; nothing renders differently (180 CPU 8/16-bit and GPU
renders of procedural scenes, and the saved settings JSON and hash64, are
byte-identical to main).
Settings saved without the field load as V1 (a field-level serde default),
new settings get the latest (`Default`), and V1 is left out when saving, so
files and preview-cache keys stay as they were. A damaged value (anything
but a whole number up to 2^32 - 1) reads as V1 too, so it never costs the
rest of the settings (an unparsable `lc:settings` sidecar is ignored). Reset moves a photo to the
latest process, section and slider resets don't; virtual copies inherit
it; copy / paste / sync / Auto Sync / presets don't carry it; a number from
a newer LightCraft is kept and renders with the newest known process. The
process is not an edit (Edited badges, import-look checks). The base tone
map is chosen per process in one place (`finish::base_tone`, shared by CPU
and GPU). Photo > Update to Current Process (`develop.updateProcess`) moves
selected photos on an older process to the latest as one undo step. Its
menu enablement follows the selection; a call that names photos (`ids` /
`id`) is validated on those instead, through a new opt-in
`CommandSpec::explicit_targets` (execute checks enablement before the
handler sees `ids`). `develop.get` reports the process. The rule and how to add V2:
docs/process-versions.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From final review: the problem-message example ("rule 2.1: rating is
0–5, not 9") predated the rating check's rewording ("no rating 0–5 is
9"); two doc comments still said Album rules take a plain album; and
What's New described the Album rule's old flat list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From final review: I had refused a time zone or fractions of a second
in rule dates on the premise that capture times are stored local and to
the second. They aren't. Imports write DateTime::to_iso, which carries
EXIF SubSecTime and OffsetTime ("2026-08-14T10:00:00.250+02:00"), so a
rule like "is 2026-08-14T10:00:00.250" did match those photos and was
wrongly refused; saved albums holding such dates would have shown ⚠
too.
rule_date now accepts, after whole seconds, a fraction (1–9 digits)
and a zone (Z or ±hh:mm). Anywhere else ("2026-08-14Z") they are no
date. Issue::DateWithZone and its translations are gone; such values
are NeedsDate. The test matches a photo captured with a fraction and a
zone, and the docs no longer say zones are refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: a loop saved anyway doesn't "match nothing". Inside it, the
album being evaluated counts as empty, so an "isn't" rule on it matches
every photo; the sidebar marks it. Also: Album rules take plain or smart
albums (not "plain" only), the picker lists both, an album that is
smart is no longer a problem, and a broken sentence in the parity row
is mended.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review:
- Esc with the calendar open also closed the rule editor and lost its
edits. The shortcut layer runs before the UI, and Esc's command (Back)
closes any dialog. Shortcuts now leave Esc to an open popup (any
menu or calendar). The picker marks Esc as handled for the frame
(widgets::take_escape / escape_taken; the mark lasts the whole frame,
since egui may run more than one pass), and the dialog's own Esc
check defers to it. New headless test: Esc closes only the calendar.
- Paging wrapped at the ends (‹ on January 0000 showed December 0000).
step() is now a pure function that stops at years 0000 / 9999; year
pages are aligned blocks of twelve, and the last one holds 9996–9999.
View::open clamps a caller's year and month, so a huge `today` can't
overflow.
- PickedDate::parse reads a time after the day only if it is one
(T10, T10:00, T10:00:00), as the rule check does; "2026-08-14Tjunk"
is no longer shown as a valid day.
- Today is marked in the month and year views too (holds_today).
- Documented: ids must be unique among the pickers on screen; the
picker picks dates, not times (picking a day replaces a typed time).
The i18n test also covers Year / Month / Day.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some date values passed the check but never matched. "2026-10-01 10:00"
(a space, not T) and "…T10:00:00+02:00" (a zone) were accepted, while
the matcher compared them as written against capture times stored as
"2026-10-01T10:00:00".
rules::rule_date is now the one reading for the check and the matcher:
2026, 2026-08, 2026-08-14, then T10 / T10:00 / T10:00:00, with a space
working as the T, and a real date and time (25 o'clock and minute 61 are
refused too). A time zone or fractions of a second can never match a
local, whole-second capture time. They get their own issue,
DateWithZone, translated in all seven locales, instead of matching
nothing silently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
i18n::problem_text shows a problem as the field's label and the issue
in the current language ("Titel: braucht einen Suchbegriff (oder „ist
leer“)"); problem_line puts the rule's position first ("#2 Title: …")
for the sidebar's tooltip. The editor's row notes and the sidebar use
them.
All 20 issue texts are translated in the seven locales, each quoting
its own operator names ("ist leer", «пусто», 「が空」), with あり / なし
for Japanese yes/no. A test requires every issue to be translated in
every language and checks English and German output.
Also corrects the zh-hant sidebar tooltip to name the menu as it is
(編輯智慧型相簿…).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A smart album whose rules no longer check shows ⚠ before its name
(`albumProblem:<id>`). Its tooltip adds "Some rules need fixing (Edit
Smart Album…):" and each problem under the rule summary, in one
tooltip. i18n::problem_text is the one place problems become display
text. The new line is translated in all seven locales.
Also: the album menu's Edit Smart Album… built the dialog itself and
skipped RuleSet::upgrade. It now goes through dialog.smartAlbum like
every other way in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rule editor renamed an album (album.rename) and then set its rules
(album.setRules) as two commits. If the second failed (refused rules
before the dialog checked them, or a save error), the album kept the new
name without the rules, and undo needed two steps.
album.setRules takes an optional `name` (blank keeps the album's) and
commits the rename and the rules as one Op::Batch: both or neither, one
undo step. The dialog edits through it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: the number spinner was fixed, but the "in the last" spinner
still clamped a stored N on first draw ("0 days" became 1, hiding the
problem and saving a different rule; 20,000 became 10,000). Values
that weren't an {n, unit} object or a two-item "between" were replaced
by the defaults as soon as they were drawn.
- In the last: shown as stored ({n, unit}, any case, or a plain number
of days) and written only when the N or the unit is changed, with
clamp_existing_to_range(false).
- Between: anything but a pair is shown as the default pair but kept
until one bound is edited, so the check marks it. A slice pattern
replaces the [0] / [1] indexing.
- Album picker: reads the id as the catalog does; names albums with
their folder ("Trips / Rome") and sorts them by name.
- Opening an album's rules applies RuleSet::upgrade.
- New tests: drawing leaves five unreadable values as stored and marks
the three that can't match, and an edit with bad rules doesn't rename
the album (neither OK nor confirm). The parity note no longer
overclaims.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
localization.md explains "<context>|<English>" catalog keys and the one
context today (smart album value); localization-ja.md notes あり / なし.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: the treatment choice "Color" reused the shared "Color" key,
which zh-hans and ru translate as the colour noun (颜色, Цвет) for the
Color panel, giving "Treatment is colour". The colour-label choice
"None" was masculine in Spanish (Ninguno) next to the feminine
"etiqueta". They are now "In Color" and "No Label", translated in all
seven locales (彩色, Цветное, Sin etiqueta…).
Also moves display_label_gaps_are_reported's doc comment back onto it;
the new choice test had been inserted between them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
parity.md: LRC-LIB-SMARTCOLL lists the 47 fields by menu group and what
Classic has that is still missing. LR-LIB-SMARTALBUM no longer claims
"match-all only, no rule editor". What's New describes the grouped menu
and the new fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lossless JPEG scans whose SOS gives predictor selection value 8 (which T.81 leaves undefined) are read as Hasselblad codes them (ljpeg::decode_hasselblad): 32-bit little-endian words read from their most significant bit, no byte stuffing; each row in pairs of samples, both Huffman categories before both differences; category 16 is followed by 16 ignored bits and means -32768; each sample is the previous sample of its column parity in the row plus the difference, modulo 2^16, both starting at 32768 on every row. Only the layout measured is accepted (one component, 16 bits, no point transform or restarts, even width); other layouts and selection values above 8 stay Unsupported. The rule was measured on 14 CC0 files with the ljpeg-explorer tool. RENDER_CACHE_VERSION 23.