mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-13 01:14:21 -06:00
d4f11018adafc3caba8c48da04113e7d44599ef0
138
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
61005f3dc1 |
Culling: the loupe uses built previews; photos prepared ahead and in the background (#3077)
* PERF-CULL-0: ui.inspect reports how the loupe showed each photo it was stepped to
perf.loupeSteps records, per step, the ms from the step to the first frame
with anything of the photo, with a full-size picture (camera JPEG, cached
or rendered) and with LightCraft's own render, measured on the frames the
app draws, so culling benchmarks exclude the control channel's round trip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 95f0343848fc0a238f80f07ceb292eaf6779aeaa)
* PERF-CULL-1: browsing a folder shows its first photos within a second
The Local view read and hashed every file of the folder before showing any
of them (2,933 EOS R5 C CR3s: 30.6 s to the first photo). It now hands the
files the library lacks straight to the import, ordered the way the view
sorts them (file time for the date sorts), which adds them batch by batch:
first photo 0.9 s, twelve thumbnails 1.4 s, the whole folder in the same
~34 s as before, in the background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b766558e7ef4f802919a144da53e444f70e0186b)
* PERF-CULL-2: the loupe makes the next photos' camera previews ahead of the step
Culling steps through photos, and each step waited for the next raw's
embedded JPEG to be read and decoded (p50 187 ms, p95 332 ms on EOS R5 C
CR3s) and then for a full raw develop. Now:
- the loupe's stand-ins for the next five photos in the direction of
travel and the one behind are made ahead (Slot::QuickAhead, priority
just below the loupe's own render) and taken over at the step, so a
step shows the camera JPEG at once (p50 0 ms);
- a slot keeps its photo while the photo stays wanted, so photos that
shift places as the user steps are not made twice;
- decoded camera JPEGs stay in the memory LRU (never on disk), so going
back shows them again without a decode;
- while an arrow key is held (a photo left within 0.25 s), the raw
develop waits until the photo has stayed 0.12 s, and a render queued
for a photo already left is dropped, so the workers make the previews
instead of renders nobody sees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0430b95636d2b6adce8887af7b9464687c95549e)
* PERF-CULL-3: CR3 previews take the smallest embedded JPEG that covers the view; plain sRGB JPEGs decode straight to 8-bit
Upstream's embedded_preview_covering (
|
||
|
|
077f4a507f |
GPU-FULL: more of the render on the GPU, loupe drafts from GPU textures (#1065)
* GPU-FULL-1: LUT profiles, Refine Edges and soft proofs render on the GPU These three sent the whole loupe / compare / export render back to the CPU pipeline (media::develop and lightcraft_gpu::render declined them). - LUT profiles: the .cube table rides in the finish kernel's aux buffer (LUT_* parameters); lut_apply mirrors Lut3d::apply (trilinear, domain), applied after grain and blended by the profile amount, as on the CPU. - Refine Edges: the cross guided filter Auto Mask already ran on the device, now with a combine mode (max for strokes, mix by sqrt(refine) for Refine), applied to the combined alpha before the mask's invert and amount. - Soft proofing: proof-space gamut mapping and the destination / display gamut warnings in the kernel (PROOF_* parameters, GAMUT_WARN and warning colours generated from the CPU constants), for output spaces and displays. Equivalence tests (RTX 5070, DX12): LUT profiles at three amounts and a non-default domain, Refine 15/60/100 on two masks, proofs in 16 combinations of space, output, display and warnings - all max 1 LSB, mean <= 0.0005 LSB. The tests also check that strong refine and the destination warning change the image (so they aren't comparing two unchanged renders). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: spot removal, defringe and the histogram on the GPU Spots and defringe made every render that had them (and every white balance or spot edit) read the resampled image back, run the scene-linear stage on the CPU and upload it again. Now only the ProRAW local tone map (off by default) does. - Defringe: OkLab pass, separable lightness min/max filter, per-pixel apply. Parameters come from the new optics::defringe_params, which the CPU stage now uses too. - Spots: new spot module (spot_prep / spot_apply over each spot's box, the Heal tone correction as device Gaussians), spot by spot in place so each reads what the earlier ones left, as on the CPU. Remove's automatic source: spot_score rates the 48 candidates on the device and only the scores come back. Layout, candidates, spoke directions, radii and ranking are shared helpers in pipeline::spots (spots::apply uses them; behaviour unchanged, pipeline tests green). - Histogram: counted with atomics on the device on Histogram::of_srgb8's sampling grid (exact), instead of a CPU pass over the readback. Tests: spots (clone, heal, auto remove incl. near the edge where no candidate fits, brushed strokes, feather 0, cut by the edge, overlapping) and defringe (purple/green, hue ranges, preview and full size) max 1 LSB; the device histogram equals the CPU count of the returned image for small, sampled (>1 MP) and windowed renders; the scoring kernel picks the CPU's candidate. (The edge case caught a real bug: "doesn't fit" was written as a decimal literal that rounds below f32::MAX.) Unit tests that create the device now take turns with the crash-sentinel test, which swaps a global. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: fix the spot kernels' build; GPU tests fail when kernels don't build 07d4680b's spot module declared `const F32_MAX = bitcast<f32>(...)`, which naga can't evaluate as a constant: the kernels failed to build, the device was unavailable, and the equivalence tests skipped as if there were no adapter - so that commit's GPU tests didn't actually run. f32::MAX is now a function, and the equivalence suite (and the device unit test) fail instead of skipping when an adapter exists but the kernels don't build. Re-verified on the RTX 5070 (DX12), all on the device: 21 equivalence tests incl. the 8 spot cases (max 1 LSB), defringe, the exact device histogram, and the auto-source unit test. Adds an ignored thumbnail throughput bench (tests/thumbs_bench.rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-3: thumbnails render on the GPU once the device is ready Every cached job (grid and filmstrip thumbnails, preset and profile previews, faces, quick stand-ins) was forced onto the CPU. They now use the GPU when lightcraft_gpu::ready() - so they never block on device creation at startup (1.8 s incl. kernels here) and render on the CPU until then. Views and exports keep waiting for the device as before. tests/thumbs_bench.rs (ignored): six workers rendering 256 px thumbnails of 512 px sources with preset-like settings (NR, sharpening, clarity, grading, a gradient mask): RTX 5070 3.6 ms each vs 4.3-5.2 ms on 32 CPU threads, with the cores left free for raw decoding. Engine and UI suites: unchanged (only the 12 failures that predate this branch on Windows: tests_sync paths, tests_model_setup). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-4: the loupe's drafts are drawn straight from the GPU Rendering and drawing now share one device: the desktop app creates the window's wgpu device with the adapter's limits and hands it to the GPU renderer (lightcraft_gpu::adopt_window_device, on a background thread at startup, under the crash sentinel; a device that doesn't suit falls back to one of the renderer's own, as before). A drag's drafts of the views are RenderJob::on_screen: the render ends in an Rgba8Unorm texture (lightcraft_gpu::render_display -> Output::Display, new `present` kernels: unwritten-pixel count, row padding for the copy) and egui-wgpu draws it as a native texture (ui-egui ScreenTextures, implemented by the app) - no readback, no conversion to egui pixels, no re-upload. The readback's checks run on the device (atomic unwritten count, device histogram). Renders that need pixels keep the readback: the settled full render (view cache, overlays, screenshots), thumbnails, exports, overlays, windows that spots grew, a monitor profile. - engine: RenderJob::on_screen, RenderResult::frame (ScreenFrame). - ui-egui: Tex::native / Tex::id() (21 draw sites), Renderer set_screen_textures, natives per slot (freed when pixels return), ui.inspect perf.gpuWindowDevice / screenTextures / mainOnGpu. - app: WindowTextures, window device limits, LIGHTCRAFT_GPU_WINDOW=0 to opt out. Tests: crates/gpu/tests/display.rs adopts a device in its own process and checks the texture holds exactly the readback's pixels (padded and unpadded widths, draft and full), the histogram matches, and overlays / grown windows come back as pixels. In the app (RTX 5070, a 45 MP CR3 from the user's library copy): all 90 drafts of a drag drawn from GPU textures, 1.69 ms vs 2.05 ms median per draft render with the readback; the frame screenshot shows the image correctly; pixels return after the drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: the denoise runner tests take turns with the crash-sentinel tests Building a denoise runner creates a device and compiles kernels under the init marker. Running in parallel with backend::tests, it could arm the marker the test had just set, so "is removed afterwards" failed now and then. The runner tests now hold DEVICE_TEST_LOCK while they build, like the render tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-4: fix the build after merging upstream main Upstream's QuickJob panic guard builds a RenderResult (add `frame: None`), its idle-trim test builds a Tex (add `native: None`), and its on_exit test builds App (add the WindowTextures slot). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: upstream's blur tests take turns with the crash-sentinel tests gaussian_matches_cpu and bench_kernels create the device, so they hold DEVICE_TEST_LOCK like every other device test in lightcraft-gpu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the same-folder export test compares paths separator-blind (Windows) The written path is the original's folder (`\` on Windows) joined with `Exports/IMG_1.jpg`; the expected one had `\Exports`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the folder-drop tests find folder widgets separator-blind (Windows) The folder tree spells its paths with `/`; the tests built widget ids from Windows paths with `\`, so none was found and every test failed on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the Windows path fixes take the same form as in the other PR branches So the branches merge into main without conflicts between their copies of the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: headless screenshots read back GPU-drawn views; LIGHTCRAFT_GPU_LIMITS caps the adopted device Review of PR #1065: - A headless `ui.screenshot` taken mid-drag showed the photo as a white rectangle: the GPU-resident draft left no CPU copy. With keep_pixels (control channel) the renderer now keeps each view's frame, and `cpu_textures` reads it back through `ScreenTextures::read` (`lightcraft_gpu::read_texture`). Drawing still never reads back, so drags keep the speed-up. - Adopting the window's device skipped LIGHTCRAFT_GPU_LIMITS; `Gpu::adopt` now caps its limits the same way `Gpu::new` does. Tests: a_headless_screenshot_reads_back_a_view_drawn_from_the_gpu (ui-egui), the_limits_override_caps_an_adopted_device (gpu); the display test uses read_texture and skips through lightcraft_testkit::skip!. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: review fixes (CodeRabbit 2026-10-21) and slideshow/print draw GPU-drawn views - the window-device adoption is announced before its thread starts: until it finishes, the settings' warm-up and renders don't create a device of their own (they raced it) - the device error / device-lost handlers go on only after the kernels build, so a declined window device keeps the window's own handler - releasing a slot frees its GPU-resident texture and kept frame - a soft proof into a colour space drafts full size on the GPU; only one to a device profile counts as proofed for `handles` - docs: Fill spots and device-profile soft proofs render on the CPU - slideshow, slide layout and print draw `Tex::id()` (the GPU texture when there is one) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the display test skips when LIGHTCRAFT_GPU=0 switches the GPU off It panicked ("GPU rendering is off") instead of skipping like the other GPU tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: a view readback never panics on a lost device read_texture (headless screenshots of GPU-drawn views) polled the device directly. On a lost device wgpu 30 panics in Device::poll; it now goes through the same guard as the renderer's own polls (poll_device): a lost device returns an error, stops the GPU and is not polled again. Review of PR #1065. Also quiets a browser-build dead-code warning on OnScreen::Frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: an HDR edit's histogram shows the HDR range on the on-screen GPU path too develop_on_screen returned the GPU's SDR histogram for both its Display and Image arms; it now applies lightcraft_pipeline::hdr_histogram as develop_reporting does (None for drafts, windows, proofs and non-HDR edits, which keep the GPU's histogram). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the Navigator draws the photo's texture as it is drawn in the loupe The Navigator falls back to the main view's texture before the photo has a thumbnail. It drew `t.tex`, which is stale (or the 1x1 placeholder) when the main view holds a GPU-resident draft; it now draws `Tex::id()`, like every other draw site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the egui 0.37 lifecycle test's texture names no native id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: rustfmt the lifecycle test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the second window fits the frame's size; freed GPU frames drop their views The second window sized its image from the texture handle, which is only a 1x1 placeholder for a GPU-resident render; it now uses the frame's size. When a GPU-resident render can't be shown and every native texture is freed, the views pointing at them (Before, Region, Compare tiles) are dropped too, so they render again as pixels instead of drawing a freed id. Test: freeing_gpu_frames_drops_the_views_drawn_from_them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(gpu): LIGHTCRAFT_GPU_ADAPTER picks the card for rendering too The variable chose the card only for AI Denoise and the face detector (two copies of the same enumerate-and-match code); photo rendering always took the first high-performance adapter, on a two-card machine the one driving the screens. One helper, backend::pick_adapter, now serves the render device (Gpu::new), the denoise network and the face detector: the first hardware card whose name contains the value (any case), else the high-performance one. A name that matches no card is logged and falls back instead of failing. backend::may_adopt_window_device says whether rendering may share the window's device (only for the window's card). Name matching is unit-tested without a GPU; the equivalence tests print the card they ran on. Documented in docs/gpu-pipeline.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: a view switching to a GPU texture releases its pixel image Review of #1065 (CodeRabbit nitpick): when a slot that had pixels switched to a GPU-resident texture, its egui texture kept the full-size image, never drawn while the native texture is set. Tex::show_native now swaps it for a 1x1 placeholder on the switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gpu): LIGHTCRAFT_GPU_ADAPTER picks among several matches in a defined order and logs once Review of #3428: - several cards match: the one whose whole name it is, then dedicated before built-in before virtual cards, then wgpu's order (backend::choose_named_adapter); the log names every match and the one used; - no match: the default choice, with a warning that lists the cards; - a name matching only a software renderer: never used, and the warning says so; - each line is logged once (backend::log_once), though the render device, AI Denoise and the face models each ask; - tests on fake adapter lists: a_fake_adapter_list_picks_in_a_defined_order, a_choice_is_logged_once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the release note gives the GPU's documented bound against the CPU (3 levels, mean under half a level) Review of PR #3544. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the window's device isn't adopted when LIGHTCRAFT_GPU_ADAPTER names another card With #3428 merged, Gpu::new already makes the render device on the named card; the window's device was still adopted first, so the app rendered on the screen's card whatever the variable said. backend::may_adopt_window_device (tested on names in backend.rs) now decides: another card named renders on a device of its own, and the window stays on the screen's card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e05c4f7e7c |
Merge track r3-t08
# Conflicts: # crates/meta/README.md # crates/ui-egui/locales/cs.json # crates/ui-egui/locales/de.json # crates/ui-egui/locales/es.json # crates/ui-egui/locales/fi.json # crates/ui-egui/locales/fr.json # crates/ui-egui/locales/id-formats.json # crates/ui-egui/locales/id.json # crates/ui-egui/locales/it.json # crates/ui-egui/locales/ja.json # crates/ui-egui/locales/ka-formats.json # crates/ui-egui/locales/ka.json # crates/ui-egui/locales/pl.json # crates/ui-egui/locales/pt-br.json # crates/ui-egui/locales/ru.json # crates/ui-egui/locales/sv-formats.json # crates/ui-egui/locales/sv.json # crates/ui-egui/locales/uk.json # crates/ui-egui/locales/vi.json # crates/ui-egui/locales/zh-hans.json # crates/ui-egui/locales/zh-hant.json # crates/ui-egui/src/panels/grid.rs # crates/ui-egui/tests/it/main.rs # docs/gaps.md # docs/library-shortcuts.md # docs/parity-checklist.md # docs/raw-parity.md # docs/user/guides/export-photos.md # docs/whats-new.md |
||
|
|
8415fd6a90 |
Merge track r3-t06
# Conflicts: # docs/parity-checklist.md # docs/whats-new.md |
||
|
|
67b636329a |
Merge track r3-t03
# Conflicts: # crates/ui-egui/src/tests/drop_import.rs # crates/ui-egui/tests/it/main.rs # docs/gaps.md # docs/parity-checklist.md # docs/whats-new.md |
||
|
|
e8566e839a |
Merge PR #2952: LR-EDIT-COLOR-TEMP: a one-field white balance partial keeps the other field's look (#2910)
Conflicts: docs unioned |
||
|
|
e595d9de5e |
Merge PR #3408: Control: pointer and scroll gestures refuse numbers past any screen instead of queueing NaN positions
docs/whats-new.md: both entries kept, newest first. |
||
|
|
7283694154 | Control: pointer and scroll gestures refuse numbers past any screen instead of queueing NaN positions | ||
|
|
6b1fc20053 |
LR-IMP-ADD-DIALOG: render import previews when the camera JPEG is too small
Require a covering embedded image only for renderable import candidates. Keep the available camera JPEG for preview-only raws and failed or unavailable render fallbacks; ordinary quick views and grid thumbnails retain their immediate stand-in behavior. Also release import preview state when canceled through the control channel. Co-Authored-By: Codex <noreply@openai.com> |
||
|
|
8ab4641011 |
LR-IMP-ADD-DIALOG: finish import thumbnail sizing review (#2056)
Reconcile with current modules, preserve all locale catalogs, reuse small-source preview jobs, release dismissed review state, and keep exact-entry Escape inside the slider. Co-Authored-By: Codex <noreply@openai.com> |
||
|
|
840e33a311 |
Merge branch 'main' of https://github.com/storytold/lightcraft into space-hold-pan
# Conflicts: # crates/ui-egui/src/lib.rs |
||
|
|
0a76419c4e |
fix(mcp): #3069 resend a request that crossed the app's idle-close notice
A request sent just as the app expires an idle control connection crosses the app's id-less idle-close notice: the client's pre-send check saw an open connection, wrote the request, and read the notice as its reply, so `--connect` and the MCP server failed a command the app never read. The app reads nothing after its idle timeout, so such a request never ran. The app now marks the notice `"closed": "idle"`, and `Remote::call` sends a request whose reply is that notice once more on a new connection (it also matches the notice's exact text, for apps from before the marker). At most one resend per call, whatever the reason; a reply with an id or any other error is returned as before. `idle_close_reconnects_without_replaying_a_command` now forces both orders (notice before the next request, and a request crossing it) instead of racing the notice; with main's client it fails every run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bb59018de6 |
Merge remote-tracking branch 'origin/main' into space-hold-pan
# Conflicts: # crates/ui-egui/src/control.rs # docs/control-protocol.md |
||
|
|
fa7e5b9cb4 |
LR-EDIT-COLOR-TEMP: a one-field white balance partial lands where a mode answers its own white (#2910)
Reviewing #2952 turned up two more shapes of the same bug, both on the path this branch is about. `effective_wb` ignores the temp / tint stored on an `As Shot` white balance — it answers the file's as-shot white — and on a raw edited in Kelvin it also ignores a named preset mode's stored numbers, through `WbMode::preset`. So a one-field partial that names its scale, `wb {temp: 5800, scale: "kelvin"}`, merged into such a photo, was stored and then read by nothing: the panel shows 5800 K while the render stays at as shot, and the stored tint no longer matches what renders. An unmarked partial already materialized a Custom value through the relative-scale conversion; the named-scale ones never did. The photo's preparation now writes out the white the photo really renders as Custom values before the partial lands, for `As Shot` and for preset modes on a Kelvin source. `Auto` keeps its mode — its gray is resolved and stored when the mode is set, not at render time — and a partial that names a `mode` itself stays authoritative. Sources without the Kelvin scale (a raw whose camera has no matrices, a rendered file) had the same swallow, and the number reaches their render now. The issue asks for one photo-aware merge in every caller that writes settings onto a photo, and names preset apply. `preset.apply` ran `Preset::apply` → `apply_partial` → `DevelopSettings::merged` and never saw the photo's scale, so a preset authored by hand naming one white balance number still had the original bug; measured on a photo at 5000 K / +12, it landed the photo on the relative scale and moved the tint it never mentioned. The preparation and the normalize-back now wrap `Preset::apply` per photo, with the amount blending and the mask merging untouched, which also puts an amount below 100 % on a single scale. Presets the app writes (`preset.create` → `extract_groups`) and the crs / XMP importers name mode, temp, tint and scale together and so land in the branch that behaves exactly as before. Tests: the `As Shot` and preset-mode cases in the #2910 merge test, and the new `preset.apply` test, fail before this commit (the first stores the number in a mode that renders the photo's old white; the second demotes the photo to `Legacy`); the same test then pins that `preset.apply` and `develop.merge` agree on a one-field white balance, which is the point of having one merge. Docs: the rule for an agent is in `docs/control-protocol.md` and `docs/mcp.md`, recorded on the `LR-EDIT-COLOR-TEMP` row of `docs/parity-checklist.md`, and in `docs/whats-new.md`. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
049034e247 |
LR-EDIT-COLOR-TEMP: a one-field white balance partial keeps the other field's look (#2910)
`develop.merge`, `lightcraft-cli render --settings` and MCP `set_develop` merged a white balance
partial that names only `temp` or only `tint` straight into the photo's settings, so the field the
partial leaves out — stored on the photo's scale — is re-read on the partial's scale. On a raw
edited in Kelvin that moves the green/magenta balance of photos the partial never mentioned:
`wb {temp: 5800}` on a photo at 5000 K / +12 tint lands on the relative scale, a shift of ~2.5 tint
units.
Resolve a one-field white balance partial against the photo: bring its stored white balance onto
the partial's scale with the photo's own as-shot white, merge, then put the result back on the
scale that photo is edited on. The inverse of `legacy_to_kelvin` is the composition of the two
Bradford adaptations run backwards: `lightcraft_pipeline::local::kelvin_to_legacy`. It maps the
source's own white to the relative scale's 6500 / 0 exactly, and answers None for a white the
relative scale cannot hold. Partials naming both fields or neither, presets and the crs / Luminar
importers are untouched: they always write `temp`, `tint` and `scale` together.
Tests: the round trip over the slider's reach, in both directions, and hostile inputs
(`crates/pipeline/src/local.rs`); the merge cases both ways plus the unchanged ones, and a restated
value that must not move the white at all (`crates/engine/src/tests_color.rs`). Both fail before
the change.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
672baaea46 | Merge lightcraft#2913 (egui 0.37) into docking-adoption; craft-ui and craft-docpath at craft-libs main a091680 | ||
|
|
c627ce7210 | egui-037: preserve modal input ownership and packaged font notices | ||
|
|
e7144adc1f | M0.37: reconcile current LightCraft main and qualify the migration | ||
|
|
7291abdc14 | Merge upstream main into docking-adoption | ||
|
|
31ec62cd03 |
Info panel Target Photo edits, Add Keywords cursor request and Fixed keys follow-ups (#2889)
* 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 |
||
|
|
c3feda15c0 | LR-META-INFO: the Info panel shows Selected Photos by default; gaps.md lists its change notes once | ||
|
|
c057ccc23f |
Migrate LightCraft to egui 0.37 and reconcile dependency and font locks
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. |
||
|
|
02aa1e0f59 | Docs: only the five split zones of ui.dock move have PascalCase names | ||
|
|
a27d8c05d4 | Docs: ui.dock above and below name the target's group | ||
|
|
5b7a4e9a94 | Docs: ui.dock move lists the tab, above and below zones | ||
|
|
5ce171da23 |
Docking: the workspace, its commands and their parser come from craft-ui (craft-libs 16fb3d4)
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. |
||
|
|
abf0caf201 |
KEY-TOGGLEZOOM: hold Space over a tool to pan and click-zoom (#1895)
With Crop, Masking, Remove, Red Eye or a picker on the photo, a held Space lends the pointer to the view until the key is up, as Lightroom's temporary Hand / Zoom: a click zooms to the click-zoom ratio at the point clicked (or back to Fit), a drag pans the zoomed photo, and the tool gets neither. A tap (under 0.4 s, no click) still toggles the zoom, on its release. Space's auto-repeat no longer toggles the zoom again. ui.key takes action press|down|up so a script can hold a key over a ui.pointer drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0b1b47ee7b |
LR-META-INFO: the Info panel can show metadata for the selected photos
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.
|
||
|
|
50df646183 | M0.37: port LightCraft UI, input and capture lifecycle to egui 0.37 | ||
|
|
cf93349071 |
Merge #2701@9c7bdbd44 (exists docs, root-safe permission test) into round3a-integration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9c7bdbd44c |
#1290: exists docs match the code; the permission test skips as root
CodeRabbit on #2689 (head
|
||
|
|
8ada8a196a |
Merge #2701@142e496f5 (a failed lookup isn't "not found"; no timer on found libraries) into round3a-integration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
142e496f5b |
#1290: a failed lookup isn't "not found"; recent libraries aren't checked on a timer
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> |
||
|
|
5223c80954 |
Merge #2701@da434b059 (presets dropped with no library open are refused) into round3a-integration
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> |
||
|
|
da434b0598 |
#1290: presets dropped with no library open are refused, not lost
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> |
||
|
|
edecba5ae8 |
Merge #2701@52ce53622 (photos dropped with no library open wait for one) into round3a-integration
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> |
||
|
|
52ce536225 |
#1290: photos dropped on the launch list wait for the library chosen
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> |
||
|
|
be51f07495 |
Round 3a: Lightroom import docs say when the destination question is asked; one revision table
CodeRabbit on #2689 (head
|
||
|
|
0d64d9ef36 |
Merge PR #2701 (review round e2210ed14)
|
||
|
|
e2210ed14e |
#1290: a failed open or an unreadable catalog never marks a recent library missing
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> |
||
|
|
22a8343ec7 |
Merge PR #2701 (review round a4ed555d4)
|
||
|
|
a4ed555d45 |
#1290: recent libraries are checked on workers, never on the UI thread
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>
|
||
|
|
81a9e8b298 |
Merge PR #2701: #2701 3fe02631e LRC-LIB-CATALOG-SWITCH: choose what opens at launch, the last library
# Conflicts: # crates/ui-egui/locales/fr.json # crates/ui-egui/src/panels/settings.rs # docs/parity-checklist.md # docs/whats-new.md |
||
|
|
2fe9101153 |
Merge PR #2685: #2685 e9b383978 LRC-LIB-CATALOG-IMPORT: ask whether a Lightroom catalog merges into th
# Conflicts: # crates/ui-egui/locales/cs-formats.json # crates/ui-egui/locales/de-formats.json # crates/ui-egui/locales/es-formats.json # crates/ui-egui/locales/fi-formats.json # crates/ui-egui/locales/fr-formats.json # crates/ui-egui/locales/fr.json # crates/ui-egui/locales/id-formats.json # crates/ui-egui/locales/it-formats.json # crates/ui-egui/locales/ja-formats.json # crates/ui-egui/locales/ka-formats.json # crates/ui-egui/locales/pl-formats.json # crates/ui-egui/locales/pt-br-formats.json # crates/ui-egui/locales/ru-formats.json # crates/ui-egui/locales/sv-formats.json # crates/ui-egui/locales/uk-formats.json # crates/ui-egui/locales/vi-formats.json # crates/ui-egui/locales/zh-hans-formats.json # crates/ui-egui/locales/zh-hant-formats.json # crates/ui-egui/src/panels/dialogs.rs # docs/parity-checklist.md # docs/whats-new.md |
||
|
|
3fe02631e6 |
#1290: list the recent libraries read-only
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 |
||
|
|
2c70336fc3 |
#1290: choose what opens at launch, the last library or a list of recent ones
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
|
||
|
|
e9b3839780 |
#2672: ask whether a Lightroom catalog merges into the open library or gets a new one
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
|
||
|
|
266f623480 |
LR-IMP-DRAGDROP: show what a file drop will do and open the import review
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> |
||
|
|
727e66babe |
Merge track r2-t03 into next
# Conflicts: # docs/gaps.md # docs/parity-checklist.md # docs/ui-parity.md # docs/whats-new.md |
||
|
|
c61ad57601 |
Merge PR #2425: LR-IMP-ADD-DIALOG: preserve album hierarchy in Import Photos
docs/parity-checklist.md: header keeps 2026-10-21 and lists both sides' change notes |