Commit Graph
3556 Commits
Author SHA1 Message Date
Paulanddeepseek-flash 5eebb9c2f9 Tone Curve: expose ParametricCurve from pipeline and preserve other regions on drag
- Expose ParametricCurve from lightcraft-pipeline and use it in both
  curve_luts and the UI curve editor, eliminating duplicate implementations
  and ensuring render and graph never drift.
- Invert the parametric model while holding the other three regions fixed,
  converting vertical pointer motion relative to the grabbed region's initial
  amount so a horizontal drag with zero vertical travel leaves all sliders
  untouched even with heavy overlapping neighbouring bumps (fixes AlexChambers
  report: Darks=60, Lights=0 grabbed at x=0.51).
- Replace all raw table indexing with .get() per never-crash rules.
- Add unit and headless integration tests verifying that horizontal drag
  with other regions non-zero changes nothing.

Co-Authored-By: deepseek-flash <noreply@deepseek.com>
2026-10-24 09:34:24 +02:00
deunderscorepaulanddeepseek-flash 7b6d88ce14 Tone Curve: the parametric graph is a clean curve you drag
The graph now shows the Effective curve and nothing else, as the parametric tab
should: the four region handles and three split notches are gone. You grab the
line itself, anywhere, and drag it up or down. Which slider moves is decided by
where on the line you grabbed — the region whose window that input level falls
in — and that slider follows on the same frame, so the line, the handle-free
graph and the sliders are one state.

The mapping is the renderer's own: a point's curve level against the region's
slider is a straight line, so the drag inverts it exactly and the line tracks
the hand instead of drifting away from it. The four regions tile the graph, so
any grab moves exactly one slider. ⌥ is still a tenth of the motion, ⌥ or not
the grab itself moves nothing, and a press away from the line is not a gesture
(no value change, no undo step).

The whole gesture is still one `develop` interaction, so it is one undo step.

Note: `ui.data` stores and loads one type, so the drag state is kept as
`Option<(region, x)>` and loaded with `.flatten()` — storing one shape and
loading another silently returns `None` every frame, which is what made the
first attempt drop the drag after a single frame.

Tests rewritten for the line: a drag down and up, which region each quarter of
the graph belongs to, the level/amount inverse at off-centre points, the region
tiling, hovering naming the region, and one undo step per gesture.

Co-Authored-By: deepseek-flash <noreply@deepseek.com>
2026-10-24 00:03:13 +02:00
deunderscorepaulanddeepseek-flash 3c3df53ca1 Tone Curve: drag the parametric graph, the sliders follow
The parametric graph is now a control surface. Each of the four regions has a
handle on its own bump: drag it up or down and that region's slider moves with
it. Each of the three splits has a notch on the graph's bottom edge: drag it
left or right and the region boundary moves. Every write goes through
`develop.set` on the frame it happens, so the sliders under the graph, the
curve and the handles are all the same state and are seen to agree mid-drag.

A drag runs inside one `develop` interaction, so a whole gesture is one undo
step and Ctrl+Z takes it back at once rather than unwinding it frame by frame.

Details that make it usable:
- The handle is the one under the *press*, not under the pointer once the drag
  threshold has passed — that is most of a handle's width away, and hit-testing
  the moved pointer grabbed nothing at all on a short drag.
- The drag follows the pointer's motion from the handle's own centre, so a grab
  never makes the control jump by the distance from the pointer to that centre.
- Splits are looked for before region handles: a notch sits on the bottom edge
  where the curve itself passes on the dark end of a shallow line, so on equal
  terms a region handle would always win and a notch could never be grabbed.
- A split is kept a step clear of its neighbours, and a region handle saturates
  at ±100 rather than jumping when the pointer goes past what the region can
  reach. ⌥ is a tenth of the motion, as the point curve's drag is.

Tests cover a region drag down and up, a split drag (including its bounds), a
drag that starts off every handle moving nothing, the readout naming the control
under the pointer, the level/amount inverse, and one undo step per gesture.

Co-Authored-By: deepseek-flash <noreply@deepseek.com>
2026-10-24 00:03:13 +02:00
deunderscorepaulanddeepseek-flash 286a5ead2c Tone Curve: the parametric graph draws the renderer's own curve
The panel drew the Effective curve with its own copy of the region formula,
which omitted the running maximum `lightcraft_pipeline::finish::curve_luts` uses to
keep the curve monotone. With neighbouring regions pushed opposite ways the
drawn line folded back into a dip the renderer never produced, so the graph and
the four sliders under it disagreed.

`ParametricCurve` is now the one curve both use: the region bumps summed and
then forced monotone, sampled at the renderer's resolution. The graph asks it
every frame and holds no state of its own, so a slider moved by hand, Reset and
Undo all redraw the line from the settings in the frame they change — the two
can no longer be left showing different things.

Reset and the undo step were already atomic (the whole Tone Curve is one
`develop` state, and a drag is one engine interaction); tests now pin that the
painted line, not just the settings, follows Reset and Undo.

Tests: the folded shape proves the bare sum is not monotone while the model is;
the painted path is compared point-by-point against the model; Reset clears the
four sliders and the graph together; one undo takes back a whole drag.

Co-Authored-By: deepseek-flash <noreply@deepseek.com>
2026-10-24 00:03:13 +02:00
Brandon Thomas 98702b5c44 Merge PR #2744: integration batch 43 (21 community PRs)
Integration batch 43: 21 community PRs
2026-10-23 17:36:11 -04:00
Brandon ThomasandClaude Opus 5.5 82eef5b266 Integration: What's New lists 2026-10-23 above 2026-10-22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:21:34 -04:00
Brandon Thomas 73f1b4906e Merge PR #2622
# Conflicts:
#	docs/gaps.md
2026-10-23 17:21:18 -04:00
Brandon Thomas 1acd2938ea Merge PR #2546
# Conflicts:
#	docs/gaps.md
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-23 17:21:18 -04:00
Brandon Thomas 708e8a8bfc Merge PR #2688
# Conflicts:
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-23 17:21:17 -04:00
Brandon Thomas 92bd08ba5c Merge PR #2618
# Conflicts:
#	docs/whats-new.md
2026-10-23 17:21:17 -04:00
Brandon Thomas bbf9b713f2 Merge PR #2526
# Conflicts:
#	docs/gaps.md
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-23 17:21:17 -04:00
Brandon Thomas e700170dc6 Merge PR #2727
# Conflicts:
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-23 17:21:17 -04:00
Brandon Thomas b106ceaca1 Merge PR #2621
# Conflicts:
#	docs/whats-new.md
2026-10-23 17:21:16 -04:00
Brandon Thomas 0605272e69 Merge PR #2620 2026-10-23 17:21:16 -04:00
Brandon Thomas 4d9914d4db Merge PR #2709 2026-10-23 17:21:16 -04:00
Brandon Thomas 652fa88c12 Merge PR #2670 2026-10-23 17:21:16 -04:00
Brandon Thomas d7763492c4 Merge PR #2210 2026-10-23 17:21:16 -04:00
Brandon Thomas c342c480cf Merge PR #2665 2026-10-23 17:21:16 -04:00
Brandon Thomas a344e277f0 Merge PR #2728 2026-10-23 17:21:16 -04:00
Brandon Thomas 433ce2375d Merge PR #2109 2026-10-23 17:21:16 -04:00
Brandon Thomas 8c52e2ee95 Merge PR #2630 2026-10-23 17:21:16 -04:00
Brandon Thomas 3ab88f50ac Merge PR #2612 2026-10-23 17:21:16 -04:00
Brandon Thomas cb94eb0e3a Merge PR #2626 2026-10-23 17:21:16 -04:00
Brandon Thomas 4f30720374 Merge PR #2702 2026-10-23 17:21:16 -04:00
Brandon Thomas 6a9e1b504f Merge PR #2694 2026-10-23 17:21:16 -04:00
Brandon Thomas d9db2d32e7 Merge PR #2596 2026-10-23 17:21:16 -04:00
Brandon Thomas e44cbc8725 Merge PR #2543 2026-10-23 17:21:16 -04:00
Brandon ThomasandClaude Opus 5.5 5e5602b4ca Enable Filters: Find Similar turns filtering back on; a filter put aside follows renamed keywords, people and folders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:20:11 -04:00
Brandon ThomasandClaude Opus 5.5 089bf60ede Colour-range mask: mask.update caps the samples too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:13:39 -04:00
Brandon ThomasandClaude Opus 5.5 4d3ea22e9c Masking: drop the one-off overlay screenshot example and its png dev-dependency
Keeps CanvasMap::new and mask_overlay private to detail.rs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:06:43 -04:00
Dylan M. Taylor 5ca49b3eab ci(sonar): don't cancel superseded runs on main, so coverage is published (#2737) 2026-10-23 17:01:32 -04:00
aiongg b264557916 i18n: missing translations are reported, never a failure (#2729)
A message a catalog lacks, or translates as an empty string, shows in
English and fails nothing:

- build.rs no longer requires every formats catalog to carry the same
  messages, and tr_format! gets a last arm that formats the English
  source, so a message no catalog has builds.
- Coverage tests (every language translates X) and tests of one
  language's exact wording are gone; the language-switching, layout,
  date, placeholder and user-value tests read the catalog's own value
  instead of a hard-coded translation.
- i18n::tests::translation_gaps_report lists, per language, the plain
  and format messages it lacks and its untranslated What's New lines.
  It always passes; cargo xtask ci
  runs it from the test step's binary after the tests.
- catalogs_load checks that every catalog file is a JSON object of
  strings with each key once; it.json named "Dimensions" twice.
2026-10-23 16:42:28 -04:00
aiongg d71b83b2bc Catalog import: resolve catalog paths with craft-docpath's resolve_absolute (#2412)
doc_path::resolve uses craft_docpath::resolve_absolute; a path that resolves but is not absolute here is reported as not on this computer. Adds tests for Windows-form, GVFS-relative and colon-in-name paths, and a git-sources check in nix.yml and release.yml.
2026-10-23 16:42:09 -04:00
Nathaniel Ledford c13875f77d LR-BEHAV-ACCESS: the feature row records the dialog Tab order and its test 2026-10-23 15:33:59 -04:00
NetterSilverandClaude Haiku 5.5 c7cff2d9a7 Fix smart album gte/lte rules missing equal f-number and focal length
Found while testing crates/catalog/src/rules.rs:673 (num_op), reached via Rule::matches at :956-957

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 21:24:32 +02:00
Nathaniel Ledford aae03c8f47 LR-BEHAV-ACCESS: Tab leaves the name field of a dialog
dialog_field took the focus back every frame while the field wasn't being edited, so Tab never
reached the check box, Cancel or OK of New Album, New Smart Album, Save Metadata Preset and the
other one-field dialogs. It now takes the focus once, when the dialog opens (the way the keyword
dialogs already did).
2026-10-23 15:20:09 -04:00
Nathaniel Ledford f0cdb20a86 Tests: a photo chosen during an import is chosen while the import is held, not while a clock runs 2026-10-23 15:10:15 -04:00
aiongg 069e32f909 CI: shared gate workflow; FreeBSD nightly and on release (#2667)
* CI: run the gate through craft-common-actions' shared gate.yml; FreeBSD nightly and on release

ci.yml is new: it calls the shared workflow (cached apt packages, nextest, main-only cache saves, Cargo.lock check). FreeBSD and Windows ARM64 run nightly and on pushes to release instead of on pull requests. Depends on storytold/craft-common-actions#69.

* CI: the gate installs no nextest (xtask ci runs cargo test)
2026-10-23 14:23:53 -04:00
Dylan M. Taylor 91fcaaaca3 coverage: keep the body scan linear and build the zero counter with dict.fromkeys (SonarCloud S8786/S7519) (#2713)
- ORF_DECODED's Camedia branch drops the redundant \\S+ before .*; with two anchored greedies the scanner can no longer retry the same substring count-for-count at lower and lower split points (same match set, verified on 14 body strings).
- {c: 0 for c in classes} is now dict.fromkeys(classes, 0), which the rule wants and which stones the per-class counters.
2026-10-23 13:35:38 -04:00
Marco Arnulfo b6d94b447b Drop a removed folder from the filter Enable Filters put aside 2026-10-23 19:35:06 +02:00
aiongg f3bb7a9f30 CI: one test binary per crate, line tables only, one WASM check (#2710)
* CI: dev profile keeps line tables for workspace crates and no debug info for dependencies

* Control server tests: persistent_requests_and_slow_replies_outlive_the_idle_read_timeout no longer races the idle timeout

Each request (and the closing half-shutdown) reaches the socket before the
worker reads, so on a slow machine only the reply waits outlast the 100 ms
idle timeout, as the test intends. The idle test sends its input the same
way, and the no-frames test allows 5 s instead of 2 s to give up.

* CI: one integration-test binary per crate

Each crate's tests/*.rs become modules of tests/it/main.rs (51 test files
into 13 binaries, plus 6 that stay their own). Inner #![cfg] attributes
move to the mod lines. Files that change state for the whole process keep
a binary of their own: codecs jxl_tail_heap and raw sr2_heap (dhat global
allocator), gpu shutdown, shutdown_first and memory, ui-egui shutdown.
Docs, comments and the GPU probe name the new paths and targets
(--test it <module>::); the CI report names an it binary's skips after
the crate.

* CI: tests under nextest, doctests apart, one wasm check

xtask ci runs the tests with cargo nextest run --lib --bins --tests when
cargo-nextest is installed, else cargo test with the same targets and a
one-line notice; doctests run after them with cargo test --workspace --doc.
The heif and hardware GPU steps use the same runner. Examples stay out of
the test build (clippy --all-targets still checks them). Concurrent tests
under nextest follow the RUST_TEST_THREADS cap (NEXTEST_TEST_THREADS).
The wasm check is one cargo check --keep-going over the same crates, and
clippy runs with --keep-going.

* CI: tests run under cargo test by default, nextest with LIGHTCRAFT_NEXTEST=1 (per-process scene cache)
2026-10-23 13:34:30 -04:00
aiongg 0e6b0b150f Packaging: pin the signing client and appimagetool, and check their hashes (#2413)
* Packaging: pin the Windows signing client and check its hash

* Packaging: pin appimagetool and check its hash

* Packaging: no pinned tool or runtime for an architecture exits clearly; pin the AppImage runtime and pass it with --runtime-file
2026-10-23 13:00:02 -04:00
Dylan M. Taylor d5e639b3d1 packaging: bash tests, named positional parameters and stderr errors (SonarCloud S7688/S7679/S7682/S7677) (#2655)
Bash scripts use [[ ]] for tests, functions name their positional
parameters and end with an explicit return, and the app-specific packaging
check writes its ::error:: annotations to stderr. No behaviour change.
2026-10-23 11:45:18 -04:00
Marco Arnulfo 6c55a76e95 LR-LIB-FILTER: Cmd+L / Ctrl+L (View > Enable Filters) switches the library filter off and on again as it was 2026-10-23 17:38:34 +02:00
Dylan M. Taylor 1c03b7b18f Merge pull request #2700 from dylanmtaylor/sonar/security-reliability
fix: document the deliberate buffer leak during thread teardown (SonarCloud)
2026-10-23 10:31:35 -04:00
Dylan M. Taylor 6fb6815cd9 fix: document the deliberate buffer leak during thread teardown (SonarCloud)
Dropping a wgpu buffer after the device's thread-locals are gone can
abort; the device owns the memory either way.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-23 09:44:05 -04:00
Nathaniel LedfordandClaude 63f4a3197d LR-META-LOCATION: no test starts the GeoNames download
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>
2026-10-23 09:43:44 -04:00
Nicolas FalesyandClaude Opus 5.5 3f4fc66d1a contributors: add nicolasfalesy (GitHub profile name)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121DZuByhKyGLHw5C9JkUue
2026-10-23 08:43:08 -04:00
Nicolas FalesyandClaude Opus 5.5 5407888184 perf(pipeline): Highlights/Shadows median about 2x faster on the CPU, bit for bit the same
`median_row` (the running median under process V2's highlights/shadows base) kept a sorted
window of f32 and, per pixel, found the leaving and entering values with two binary searches
on `total_cmp`, then `remove` + `insert`. The searches mispredicted on most steps.

Now the window holds `i32` keys that order exactly like `f32::total_cmp` (the same transform
it applies), both ranks come from one branch-free counting pass the compiler vectorizes, and
one `copy_within` moves the keys between them. Equal keys are equal bits, so the output is
unchanged bit for bit.

24 MP Sony A7 III log luminance, one P-core, interleaved: r = 9 (export) 1977-2027 ms ->
912-958 ms, r = 3 1249-1292 -> 658-724, r = 24 2180-2261 -> 972-1030. All cores, r = 9:
170-188 -> 86-102 ms. The GPU path has its own median and is untouched.

New test: special values (NaN of both signs, signed zeros, infinities), long ties and planes
narrower than the window match a per-pixel sorted median bit for bit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121DZuByhKyGLHw5C9JkUue
2026-10-23 08:43:08 -04:00
Nathaniel Ledford 6889fe223b MENU-BAR: a menu item's cached accelerator follows the native call, and a failed one is retried 2026-10-23 08:23:43 -04:00