Commit Graph
47 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 cc6c0e457d LR-MASK-PANEL: reorder masks, duplicate and invert, list context menu, overlay colour cycle
- mask.move {to | delta} reorders the list (no-op moves record nothing); mask.duplicate
  {invert} = Duplicate and Invert, the copy placed right after the original.
- Masks list: show/hide eye per row (on hover, always while hidden; new eye-off icon drawn
  in code), double-click to rename in place, right-click menu (duplicate, duplicate and
  invert, invert, show/hide, move up/down, rename, delete).
- View → Cycle Mask Overlay Color (view.maskOverlayColor without params).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:49:55 -04:00
Brandon ThomasandClaude Opus 5.5 8548c157ba MENU-EDIT-PASTESELECTED + KEY-SEARCH: paste selected settings, Find, fuller photo context menu
- Edit → Paste Selected Settings… (⇧⌘V): group checklist prefilled with the copied groups;
  develop.paste {groups} pastes only the chosen copied groups.
- Edit → Find… (⌘F, view.focusSearch) focuses the search field.
- Photo context menu: Remove from Album (in an album), Create Version, Paste Selected
  Settings…, Show in Finder, Export…, Export with Preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:00:24 -04:00
Brandon ThomasandClaude Opus 5.5 faa5478966 LR-EXP-DIALOG + MENU-FILE-EXPORTPRESETS: export presets, destination policy, TIFF compression
- Built-in export presets (JPEG Small 2048 px, JPEG Large full size, Original + Settings, DNG)
  and user presets saved with the library (export.presets / savePreset / deletePreset);
  app.export {preset, …overrides} everywhere (desktop, MCP, lightcraft-cli run).
- File → Export with Preset submenu (built-ins, user presets, Custom…); dialog: preset
  picker, Save preset, Choose… folder picker, subfolder, if-file-exists (add number /
  overwrite / skip), start number, TIFF compression (none / LZW / ZIP); {date} naming token.
- One export_batch for the desktop app and headless MCP (paths, conflicts, sidecars);
  ExportOptions::to_json is the exact inverse of from_json.
- ⌘E = Export with Previous (alias); parity: Show in Finder row was stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:46:54 -04:00
Brandon Thomas 22e4f0fd8c Merge main into worktree-agent-ab72a7623afb95196
# Conflicts:
#	crates/engine/src/import.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/src/headless.rs
#	crates/ui-egui/src/panels/dialogs.rs
#	crates/ui-egui/src/panels/grid.rs
#	docs/parity.md
2026-10-01 13:33:29 -04:00
Brandon Thomas db89203fb2 Merge branch 'worktree-agent-a27a1456c70c1074d' 2026-10-01 13:27:58 -04:00
Brandon ThomasandClaude Opus 5.5 93a1fd3bce LR-IMP-ADD-DIALOG + LR-IMP-LOCAL: import review dialog with candidates, destination, album, preset, keywords
library.importPreview scans/probes (probes reused by the import) and marks duplicates;
library.import gains preset, keywords and albumName and is one undo step; File > Add Photos
opens a review grid (thumbnails, checkboxes, duplicates unchecked), add in place or copy into
the dated Originals folders, batched import with progress; engine + headless tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:27:13 -04:00
Brandon ThomasandClaude Opus 5.5 4075b42d85 Merge main into the masking branch (output space / deep finish, presets, parity)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:23:58 -04:00
Brandon Thomas 799ecc08bc Merge branch 'worktree-agent-ad641003294616c82'
# Conflicts:
#	crates/engine/src/library.rs
#	crates/ui-egui/src/lib.rs
#	docs/parity.md
2026-10-01 13:21:16 -04:00
Brandon ThomasandClaude Opus 5.5 d91aaa4ca1 LR-REM-SPOT-EDIT + LR-REM-BRUSH-PARAMS: select, move, resize, refresh and delete spots; brush keys
Engine: spot.select / spot.update (move target or source, size, feather, opacity, mode) /
spot.refreshSource (next-best source away from the current one) / spot.delete (selected spot);
spot.add resolves the automatic source (so it has a pin and stays put at every size) and selects
the new spot. Remove tool: pins for targets and the selected spot's source, click to select, drag
to move; Size/Feather/Opacity sliders edit new and selected spots; ⌫ deletes the selected spot
(never the photo), / refreshes its source, [ ] size and Shift+[ ] feather (Masking brush too).
Command tests and headless pointer/keyboard tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:20:06 -04:00
Brandon Thomas 5bd1d238c8 Merge main into worktree-agent-ab72a7623afb95196
# Conflicts:
#	docs/parity.md
2026-10-01 13:18:43 -04:00
Brandon ThomasandClaude Opus 5.5 f471b77800 LR-LIB-LABEL: colour-label menus, Info swatches, grid label dots and editable label names
Op::SetLabelName + catalog label names (undoable, journaled), label.names / label.setNames,
Edit Color Label Names dialog, named label submenu (Photo menu, grid context menu), Info-panel
swatches; engine + headless tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:17:57 -04:00
Brandon ThomasandClaude Opus 5.5 1f3bc9c075 LR-LIB-CAPTURETIME: edit capture time (set, shift, time-zone shift) for a selection
Op::SetCaptured (validated, journaled), photo.setCaptureTime (the active photo gets the time,
others keep their offsets; or each / shift / hours), Edit Capture Time dialog with preview
(Photo menu, Info panel); engine + headless tests incl. op-log replay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:11:49 -04:00
Brandon ThomasandClaude Opus 5.5 ef9c6ca38c Merge main into settings branch (memory budget + configurable thumbnail disk cache)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:09:06 -04:00
Brandon ThomasandClaude Opus 5.5 53c4b295b9 LR-LIB-RENAME: batch rename with templates, preview, safe on-disk renames and undo
Op::SetFile (catalog only; engine moves files + XMP sidecars first, never overwriting,
rolling back on failure); undo/redo move files back and forth; virtual copies follow;
photo.rename / photo.renamePreview; Rename Photos dialog (Photo menu, grid context menu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:06:33 -04:00
Brandon ThomasandClaude Opus 5.5 464d4926b9 LR-PREF-GENERAL + LR-IMP-RAWDEFAULT + LR-VIEW-FULLSCREEN/NAVIGATOR/INFOOVERLAY: Settings dialog, import defaults, view modes
Settings (Cmd+, / app menu on macOS, Edit elsewhere) with General, Import,
Performance and Interface tabs. App settings (startup view, delete
confirmation, GPU, preview size, filmstrip/grid badges, last library) live in
UiState.settings (ui.json); library settings (raw / non-raw / per-camera
import presets, XMP, thumbnail cache size) go through library.preferences into
prefs.json. Import applies the matching default preset; the photo remembers it
as its import look (unedited; Reset returns to it). Open Library… switches
libraries at runtime.

View modes: F full-screen preview (photo on black, arrows step, I cycles the
info overlay, Esc exits), Cmd+Shift+F window full screen, Cmd+I cycles the
loupe info overlay (off / file + date / exposure), Navigator mini map while
zoomed (click/drag pans).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:03:33 -04:00
Brandon Thomas 3c644f7faa Merge branch 'main' into worktree-agent-aecd35c3421bad4ae
# Conflicts:
#	crates/ui-egui/src/render.rs
2026-10-01 13:01:41 -04:00
Brandon ThomasandClaude Opus 5.5 7fd21517ae LR-LIB-KEYWORD: library-wide keyword rename/delete/merge, left-panel keyword tree, suggestions
Catalog keyword ops (one SetMeta batch: undoable, replayed from the op log), hierarchical
a|b|c keywords (tree with counts, parent filters include children), keyword.* commands,
rename/merge dialogs, Keywords-panel suggestions; catalog, engine and headless tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:00:15 -04:00
Brandon ThomasandClaude Opus 5.5 89b8aa3f77 LR-VIEW-PHOTOGRID: group the photo/square grid by date with day/month/year headers
Catalog date runs (stacks never split), Sort.group (auto/day/month/year/none) in the sort
menus, library.groups query, sticky headers, header click selects the group; layout unit
tests and a headless test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:52:07 -04:00
Brandon Thomas e1b2055897 Merge branch 'main' into worktree-agent-aecd35c3421bad4ae
# Conflicts:
#	crates/ui-egui/src/panels/edit.rs
#	docs/parity.md
2026-10-01 11:47:29 -04:00
Brandon ThomasandClaude Opus 5.5 d6431d0b8a Merge main into M5.6 (memory) branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:36:32 -04:00
Brandon ThomasandClaude Opus 5.5 7266f4153f M5.6c: one memory budget for caches, decode gate and GPU pool; peak RSS 1.9-2.3 GB -> 0.83-0.99 GB
- memory::budget (default min(25% RAM, 1.5 GiB); LIGHTCRAFT_MEMORY_MB, app.memoryBudget):
  half for engine caches (sources + rendered previews, LRU across them via a global
  use tick; the photo on screen stays), a quarter for decodes in flight (WorkGate:
  thumbnails, prefetch and import probes wait; loupe/export loads never wait), an
  eighth for the GPU free pool.
- GPU: best-fit buffer reuse (<= 25% slack), buffers released outside a render are
  pooled at once (no stranded retired lists), pool trimmed after 3 s idle.
- Decodes drop file bytes and raw samples early, colour/crop/orient in place.
- macOS: apps install malloc_zone_pressure_relief as the release hook (after each
  decode, import and idle trim): freed large blocks no longer stay resident.

14 raws + loupe + 12 steps (M4 Pro): peak RSS 1.9-2.3 GB -> 0.83-0.99 GB, footprint
2.5-2.6 GB -> 0.91-0.96 GB, heap peak 930 -> 656 MB; stepping median ~13 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:35:08 -04:00
Brandon ThomasandClaude Opus 5.5 5720cf333d LR-PROF-DROPDOWN: profile favourites and recent (persisted with the library), grouped profile menu, amount slider
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:23:26 -04:00
Brandon Thomas 59140d8265 Merge branch 'worktree-agent-aada35d252224c5cd'
# Conflicts:
#	crates/engine/src/media.rs
#	crates/ui-egui/src/state.rs
2026-10-01 11:11:49 -04:00
Brandon ThomasandClaude Opus 5.5 64dfa8adbd Merge edit-panel parity (Point Color, Calibration, curve refine saturation + targeted adjustment, red/pet eye, visualize spots)
Fix-ups: diagnostic overlays never populate the view-preview cache; Photo Merge's RenderRequest gets
the new overlay field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:02:11 -04:00
Brandon ThomasandClaude Opus 5.5 ce3dd5e156 Merge main (Photo Merge); M1.14b: stack merge results with their sources
Photo Merge's Create Stack option now puts the merged DNG on top of a
collapsed stack of its sources (Catalog::stack_with_ops), so the grid
shows the result; test in tests_merge (and no stack without the option).
Photo Merge dialogs live in a Photo > Photo Merge submenu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:00:51 -04:00
Brandon ThomasandClaude Opus 5.5 5f401d2eef Merge main (straighten, vignette style) into the polish branch
Also: GPU retired-buffer list leaks its buffers at thread exit instead of
dropping them (wgpu's thread-locals may already be gone; the drop aborted
the test process intermittently), and headless UI tests wait up to 120 s
for renders (they timed out on a loaded machine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:52:17 -04:00
Brandon ThomasandClaude Opus 5.5 06d30a0e59 LR-EYE-PET: pet eye — glow detection, dark pupil, catchlight
Pet eyes are found by brightness (the tapetum glows in any colour) and the disc is replaced by
a dark neutral pupil (Darken scales it); redeye.catchlight {index, on, offset} adds a soft
white catchlight at an offset from the pupil centre in pupil radii. Red Eye panel: Red Eye /
Pet Eye mode for new corrections and an Add Catchlight checkbox. CPU/GPU twins updated
together (equivalence covers both modes and the catchlight).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:43:57 -04:00
Brandon Thomas 00efffd372 Merge branch 'worktree-agent-a5bfb1e4b62622bcb'
# Conflicts:
#	crates/engine/src/media.rs
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/shortcuts.rs
2026-10-01 10:37:52 -04:00
Brandon ThomasandClaude Opus 5.5 ebcc41825b LR-EYE-RED: red eye correction with automatic pupil detection
pipeline::redeye: inside the user's ellipse the pupil is found on the source image (a fixed
grid in normalized coordinates, so previews and exports agree): weighted centroid and area of
red-dominant pixels (or, for pet eyes, of the brightest ones). Pupil Size scales the detected
disc; rendering in scene-linear light after white balance neutralizes the red where it is red
(iris and catchlights stay) and darkens it. CPU (lin stage) and GPU kernel redeye_k with an
equivalence test (both the GPU and the CPU scene-linear paths). Indexed controls
redEye.<i>.pupilSize/darken, redeye.add/delete commands, the Red Eye panel (selected eye's
sliders, delete) and the loupe tool (drag an ellipse, click to select).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:37:02 -04:00
Brandon ThomasandClaude Opus 5.5 bc2cda97cf M13.2: Photo Merge commands, dialog with live preview, CLI
- engine: `merge.hdr`, `merge.panorama`, `merge.hdrPanorama` (bracket size from
  EXIF); MergeJob (Send) with progress/cancel, preview renders through the develop
  pipeline (deghost overlay, auto-crop frame); finish writes <name>-HDR/-Pano.dng
  next to the first source, imports, selects, applies Auto Settings / auto crop
- ui: Photo Merge dialog (HDR / Panorama / HDR Panorama) with live preview on a
  worker thread, background final merge with progress toast; context menu and
  Ctrl+H / Ctrl+M (Ctrl now means the physical Control key on macOS)
- cli: `merge hdr|panorama|hdr-panorama`, `synth-merge` for synthetic inputs
- merge::synth: procedural brackets (DNG) and panorama views; docs/merge.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:30:03 -04:00
Brandon ThomasandClaude Opus 5.5 ce89ab5375 M4.1: Straighten tool (drag along the horizon or a vertical) + Auto straighten (crop.autoStraighten)
Auto uses Upright's consensus level angle as the crop angle; sign verified on a tilted test image
and pinned by a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:28:29 -04:00
Brandon ThomasandClaude Opus 5.5 78e50b5fc5 M5.6a: memory report (library.memory, ui.inspect memory) and opt-in DHAT heap profile
Per-cache bytes: decoded thumbnail/preview/full sources, rendered previews,
GPU device buffers (allocated/pooled/retired), loupe stage caches (CPU and
GPU) and textures. lightcraft-cli --features dhat-heap adds live/peak heap and
writes dhat-heap.json.

Baseline (14 raws, loupe, 12 steps): peak RSS 1.9-2.3 GB, footprint 2.5-2.6 GB;
heap peak 634 MB after import (probes decode every raw), 930 MB during grid
thumbnails (parallel decodes); GPU free pool grows to 2 GB (exact-size reuse).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:28:13 -04:00
Brandon ThomasandClaude Opus 5.5 b36920844b Merge main into the polish branch
Resolved: album.create (main's one-liner + smart: None), menus (panels in
the Window menu, main's dialog.newFolder ⇧⌘N, app.showInFinder and
app.exportPrevious placed in the menu layout), shortcuts (native-menu skip
+ main's aliases and UI-over-engine rule; Shift+X/Shift+U aliases go
through the culling path; my Shift+P/X/U flag-and-advance dropped in
favour of the aliases). X and Delete stay egui-handled (contextual keys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:26:18 -04:00
Brandon ThomasandClaude Opus 5.5 8b7fb306dc LR-EDIT-LIGHT-CURVE-TAT: targeted adjustment tool for the tone curve and the colour mixer
develop.targeted {target: curve|hue|sat|lum, x, y, delta, channel?}: samples the photo at the
point on a proxy render with the target stage and everything after it neutralized, then
nudges the parametric curve region (by the sampled tone and the splits), moves/adds a point
on the master/RGB point curve, or adjusts the colour-mixer (or B&W mix) bands weighted by
their share of the sampled OkLCh hue. UI: target buttons in the curve editor and the colour
mixer; a vertical drag on the loupe sends one step per unit inside one interaction (one
undo step). New original vector icon (Target) in icons.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:25:29 -04:00
Brandon ThomasandClaude Opus 5.5 490fe943cd LR-EDIT-COLOR-POINTCOLOR: Point Color — sampled colours with shift, variance, range and visualize
Up to 8 OkLCh samples (pointColor.pick samples the colour after the mixer on a proxy render);
per sample hue/saturation/luminance shift, variance (compress/expand the spread around the
sample) and an overall + per-axis range with soft edges, applied after the colour mixer in
ColorOps and its WGSL twin (equivalence case). Indexed control specs
(pointColor.<i>.hueShift …) so develop.set/controls/MCP address each sample. Visualize range
is a render overlay (RenderRequest::overlay) drawn after either renderer: the sample's own
edit is left out and colours outside the range turn grey. Point Color flyout in the Color
section with swatches, eyedropper, sliders, visualize and delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:18:28 -04:00
Brandon ThomasandClaude Opus 5.5 2c37c26d1e M16.1: ⌫ deletes photos again (was bound to forward-delete), and never in the Masking/Remove/Red Eye panels
"Delete" parsed as egui's forward-Delete key, so the Mac ⌫ key (Backspace) didn't delete. In the
Masking panel ⌫ now deletes the active mask; while retouching it does nothing. ⇧⌘N no longer
fires album.create (which needs a name).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:10:46 -04:00
Brandon ThomasandClaude Opus 5.5 0571d8e438 M9.7: virtual copies — new catalog entries sharing the original file
Photo.copy_of / copy_name. photo.virtualCopy (Cmd+') clones the photo with
independent settings and history, names it "Copy N" (numbered per master),
adds it to the original's albums and stacks it (expanded) right after the
original; one undo step for several copies (Session::merge_undo). Copies
never write XMP sidecars. "Copy N" badge in the grid, Info panel line,
copy:yes search token. Also: Info panel stars laid out in a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:58:36 -04:00
Brandon ThomasandClaude Opus 5.5 5434c5d172 M1.14: stacks — group, expand/collapse, top of stack, auto-stack by time
Stack entity in the catalog (AddStack/RemoveStack/SetStack ops with
inverses, validation, snapshot + journal replay property test). Helpers
build ops for group (merging stacks), ungroup, remove, set top, collapse,
and stack_with_ops() for future merge results. Views arrange stack members
together and collapse to the top. Commands stack.* (Cmd+G, Cmd+Shift+G, S,
Shift+S, expand/collapse all, stack.auto with preview); grid/filmstrip
count badges (click to toggle), context menu, Auto-Stack dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:52:11 -04:00
Brandon ThomasandClaude Opus 5.5 50741eceb4 M1.13: smart albums — saved filters that update live
Album.smart holds Filter rules (rating, flag, label, kind, edited, keyword,
camera, lens, capture date range, text, album); SetAlbumRules op with
inverse, validation (no nested smart albums, no deleted), journal replay
property test. album.createSmart (from the current view or explicit rules),
album.setRules; New Smart Album dialog, smart icon, live counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:47:38 -04:00
Brandon ThomasandClaude Opus 5.5 30aac43fab M5.2e: renders and exports use the GPU; render_bench GPU column
`media::develop` renders on the GPU when available (views, render_now, exports;
thumbnails stay on the CPU) and falls back to the CPU otherwise. `app.gpu
{enabled?}` toggles it at runtime, `LIGHTCRAFT_GPU=0` disables it for the process,
`ui.inspect` reports the adapter under perf.gpu.

Performance: dropped device buffers are recycled once their thread has submitted
(fresh 100-300 MB allocations per pass cost as much as the passes), Gaussian
passes ping-pong between two buffers and read all channels of a pixel at once,
and long box radii slide over longer chunks. LIGHTCRAFT_PROFILE prints real GPU
stage timings. render_bench prints CPU and GPU columns and the CPU/GPU difference.

24 MP ARW, loaded machine, min of 7: cold loupe 415 -> 32 ms, exposure drag
22-42 -> 3.9 ms, NR drag (1920) 301+ -> 18 ms, export render 1037+ -> 291 ms;
max 1 LSB difference. docs/gpu-pipeline.md describes the implementation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:19:44 -04:00
Brandon ThomasandClaude Opus 5.5 b00dda4cec M4.4: Upright — Auto/Level/Vertical/Full from detected lines + Guided upright (up to 4 guides)
- pipeline::upright: own gradient-based line-segment detector (level-line region growing seeded from the
  strongest gradients, weighted principal-axis fit, elongation test); vanishing points by RANSAC over
  segment pairs + least-squares refinement (smallest eigenvector of Σ w·l·lᵀ)
- corrections as a virtual camera rotation K·R·K⁻¹ (vertical VP → y axis; Full also horizontal VP → x axis
  with focal length from VP orthogonality when plausible); Level = rotation; Auto = Full/Vertical when
  moderate, else Level; all re-centred
- Guided: guides stored in normalized lens-corrected coordinates; 2+ vertical/horizontal guides define the
  vanishing points, a single guide levels
- the analysis runs on the lens-corrected image at 1024 px and the result is stored in
  geometry.upright_transform (identical preview/export); settings naming a mode without a transform
  (XMP, CLI) are resolved at render time
- commands geometry.upright {mode} (Update = run again) and geometry.guides {guides, add}
- UI: Upright mode buttons, Update, Draw/Clear Guides in the crop panel; on-canvas guide drawing that
  follows the warp
- tests: segment detector on known lines; Vertical/Auto/Full fix converging verticals; Level fixes a tilted
  horizon; Full fixes both families; guided from two guides; analysis resolution independence; lazy vs
  stored transform; engine commands

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:16:18 -04:00
Brandon ThomasandClaude Opus 5.5 3ef4eb0687 M9.2: preset files — .lcpreset import/export (groups preserved), XMP presets via crs: mapping
- engine presets.rs: .lcpreset JSON format {format: "lightcraft.preset", version, presets[]},
  parse_preset_file (.lcpreset file / bare preset / array, or XMP), folder expansion,
  Session::add_presets (identical presets skipped, id clashes get fresh user.* ids, built-ins
  never replaced).
- crs::preset_from_xmp: crs: fields → partial settings, crs:Name/crs:Group (fallback: file name /
  "Imported Presets").
- Commands preset.import {paths} and preset.export {path, ids?, group?}; UI commands
  file.importPresets / file.exportPresets (File menu, with native open/save dialogs on desktop),
  Presets panel "⋯" menu (Import Presets…, Export User Presets…) and group context menu
  (Export Group…).
- Tests: export → import round trip keeps groups/settings, duplicates skipped, clashing ids,
  malformed files reported, hand-written XMP preset imports and applies.
- docs/xmp-interop.md: preset file section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:05:26 -04:00
Brandon ThomasandClaude Opus 5.5 8af27c11b1 M10.8: XMP sidecars — save/read metadata + develop settings, auto-write, crs: interchange
- crates/engine/src/sidecar.rs: <stem>.xmp (default) or <file>.xmp next to originals with rating,
  label, title, caption, copyright, creator, keywords, capture time/GPS, pick flag + full develop
  settings in lc:settings; atomic writes; read on import (sidecar wins for metadata, edits restored)
  and via photo.readMetadataFromFile (one undo step); raw/DNG embedded XMP used without a sidecar.
- Commands: photo.saveMetadataToFile (Cmd+S), photo.readMetadataFromFile, library.xmpPreferences
  {autoWrite, naming}, library.toggleAutoWriteXmp; prefs persisted in the library's prefs.json.
  Auto-write rewrites sidecars after each committed change (not mid-drag; undo/redo too).
- crates/engine/src/crs.rs: crs: develop fields (PV2012 light, WB abs/incremental, presence, HSL,
  B&W mix, parametric + point curves, colour grading, sharpening, NR, vignette, grain, optics,
  geometry, crop) → partial settings; documented in docs/xmp-interop.md.
- meta: write_xmp_lc for extra lc: properties; crs namespace canonicalised on read (never written).
- Tests: packet round trip, crs mapping with hand-written snippets, save → delete library →
  re-import restores edits, auto-write + prefs persistence, foreign sidecar, DNG-embedded XMP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:36:19 -04:00
Brandon ThomasandClaude Opus 5.5 759d3c7f8a M1.3: import (recursive folders, content-hash duplicates, add vs copy)
- engine::import: expand files/folders recursively (sorted; hidden entries
  and the library folder skipped), skip known paths, probe + hash in
  parallel (std::thread::scope), skip duplicates by content hash (against
  the library and within the batch), add in place or copy into
  <library>/Originals/YYYY/YYYY-MM-DD/ with unique names; one undoable op.
- library.import {paths, mode: add|copy, album?} returns
  {imported, duplicates[{path, existing, reason}], failed, scanned}.
- probe_bytes records the content hash (SipHash-1-3 128) on every photo.
- system clock for import/edit times on native (Session::with_system_clock).
- app: command-line folders are expanded by the engine (re-importing the
  same folder is a no-op); example gen_photos writes procedural test JPEGs
  (never committed).
- Measured (200 procedural 2000 px JPEGs, M-series): import 2.25 s; reopen:
  library load 1.7 ms, control ready 0.12 s, first thumbnail 0.17 s,
  visible grid drawn 0.48 s (thumbnails from the disk cache).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:04:07 -04:00
Brandon ThomasandClaude Opus 5.5 bcecab7d84 M1.5: thumbnail/preview cache (disk + memory LRU, prioritized job pool)
- new crate lightcraft-preview (L3): 128-bit content hashing (SipHash-1-3),
  cost-bounded Lru, DiskCache (JPEG q90 files keyed by content hash +
  settings hash + size + RENDER_CACHE_VERSION; temp+rename writes; LRU prune
  by mtime to 80% of budget; corrupt files dropped), PreviewCache (memory
  LRU over disk), JobPool (per-slot dedupe, priorities, reprioritize/drop,
  inline mode for wasm).
- engine: Session::thumb_job buckets thumbnails to 128/256/384/512 and serves
  them from the cache without decoding the source; decoded thumbnail
  sources now in a 384 MB LRU (was unbounded); library attaches
  <library>/thumbs; library.info cache stats; library.clearPreviews.
- ui-egui: Renderer runs on JobPool; thumbnails scrolled out of the
  prefetch area are dropped from the queue; on-screen thumbnails get
  re-prioritized; ui.inspect reports in-flight renders + thumb textures.
- Demo library (24 photos): grid fully drawn 4.4 s cold -> 0.21 s warm
  (24/24 disk hits, no source decoded).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:51:47 -04:00
Brandon ThomasandClaude Opus 5.5 e7b3724b5f M1.4: crash-safe catalog persistence (op log + snapshot)
- catalog: Store trait (FsStore: temp+fsync+rename+dir fsync; MemStore),
  Journal: CRC-checked JSON-lines op log with sequence numbers + atomic
  snapshots; torn final record dropped and truncated, stale records after a
  snapshot skipped, mid-log damage preserved as catalog.log.damaged-N.
- PushHistory op so each edit logs one history step (not the full history).
- engine: Session::open_library/persist/close_library; every top-level
  command is fsynced before returning; presets.json + view.json;
  library.info / library.compact commands.
- app: default library ~/Pictures/LightCraft Library (LIGHTCRAFT_LIBRARY or
  --library DIR override), --memory for throwaway sessions, snapshot on quit.
- tests: proptest replay(log)==state with random snapshots/reopens, every
  truncation of the last record, stale log, mid-log damage, FsStore, engine
  restart without close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:40:40 -04:00
Brandon ThomasandClaude Opus 5.5 69e8aa6e18 M0.6: engine (Session, ~75 commands: library/photo/album/develop/crop/masks/spots/presets/versions/history/queries; undo via inverse ops; interactions; render jobs; demo library)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:33:29 -04:00