crates/engine/src/export.rs: main moved the code to export/; the 16-bit ZIP size test ported to export/tests.rs
crates/ui-egui/src/panels/dialogs.rs: main moved the dialog to dialogs.rs and dialogs/export.rs; the LZW note helpers, preset hover hint and tests ported there
crates/ui-egui/src/panels/grid.rs: calls crate::dialogs::preset_lzw_note
The doc comment of export_dialog_params stays directly above that function (review fix)
Found while testing crates/mcp/src/base64.rs:20 (base64_decode)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>
16-bit LZW TIFFs come out larger than uncompressed ones and slower than
ZIP. The export keeps LZW exactly as picked (some software reads only
LZW), and says what it costs where it is chosen:
- Export dialog: a note under Compression when LZW is selected, worded
more strongly at 16 bit, a milder one at 8 bit and 32-bit float.
- Export presets that write LZW TIFFs carry the same hint on hover, in
the dialog's Preset list and the grid's Export with Preset menu.
- tiffCompression docs: MCP schema and headless params, the user export
guide and the parity checklist.
Tests: the note shows for LZW only and under the Compression choice
(headless), presets get the same hint, the dialog keeps LZW; 16-bit ZIP
is never larger than uncompressed on a noisy synthetic render (noise
0 to 0.1) and on the demo photo's export.
Fixes#2882
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
* 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)
The render_photo, render_preview and screenshot tools had the backend write their PNG to lightcraft-mcp-<pid>-<n>-<tag>.png right in the temp dir. On Linux and FreeBSD, where that is the shared /tmp, other users could read the rendered photos and screenshots, and a name someone else created first made the render fail. Each image now goes in a new lightcraft-mcp-<pid>-<random> folder, made 0700 on Unix and never reused when the name already exists, and the folder is removed with anything left in it once the image is read or the call fails. On Windows, and on macOS with its usual per-user TMPDIR, the temp dir was already private; there the PNG just sits one folder deeper.
`recursive_discovery_includes_sony_sr2_and_srf_case_insensitively` (engine) and
`folder_discovery_matches_engine_extensions` (mcp) joined "trip/day2/..." as one string, so
on Windows the expected paths kept `/` while discovery returns `\`, and both failed. They now
join one component at a time. Test-only change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/parity-checklist.md: LR-IMP-FORMATS row keeps main's DNG/Hasselblad text and adds #1165's SR2/SRF phrase; revision rows are the union
docs/raw-parity.md, docs/gaps.md: revision rows are the union
Lightroom's Resize to Fit has two boxes: Dimensions (the larger number to
the long edge, whichever way round the photo is) and Width & Height (the
width limited to W and the height to H as the photo stands). LightCraft
had only the first, labelled "Width × Height". Add `ResizeMode::WidthHeight`,
`fit: widthHeight` beside `width` + `height` in app.export, the mode in the
Export dialog (the old one is now called Dimensions), and the docs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
A preset without `format` lost its `bitDepth` before the call's `path` was merged in,
because it was judged against JPEG: `app.export {preset, path: a.png}` with a 16-bit
preset wrote an 8-bit PNG. Session::export_params now merges first and drops a preset's
depth only if the format written (with the caller's fallback) lacks it; a depth the call
passes is still validated. export.savePreset judges the depth at once only when the preset
has a `format`.
app.export with a path like `output` or `output.xyz` and no `format` skipped the depth
check: the desktop app then wrote JPEG and the headless backend PNG, both at 8 bits.
ExportOptions::export_format resolves the format written (with the caller's fallback for
such a path), from_params_or validates against it, and both callers use it instead of
their own override.
On Windows 11, `cargo test --workspace` on main fails 11 tests:
- tests_export::same_folder_as_original_exports_next_to_each_photo built its
expected paths with Path::join (`\`), but export joins the subfolder and
file name with `/` (join_path) on every platform. Expect what export writes.
- tests_folder_drop (8 tests) looked rows up as
`source:libfolder:C:\...`, while the Folders section names them with `/`
(`source:libfolder:C:/...`), so no row was found. Look them up the same way.
- mcp backend::tests::{valid_replies_and_errors_are_preserved,
malformed_replies_are_rejected_without_resending} failed only when the whole
crate ran: the shutdown and one-shot-process tests start a copy of the test
binary, the child can inherit a socket a fake server just accepted, and when
the child exits Windows closes its copy abortively, resetting the test's
connection. A test-only lock keeps socket tests and child-process tests from
overlapping (passes 6 of 6 whole-crate runs, failed 4 of 4 before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reuse the engine's import extensions in MCP and CLI discovery. Add
recursive, case-insensitive discovery tests and record that DSC-F828
SRF import remains preview-only.
Co-Authored-By: Codex <noreply@openai.com>
The connect-mode MCP client accepted any newline-less or wrong-id reply as
the answer to its request. Require a complete newline-terminated reply of
at most 64 MiB, the matching integer id, boolean ok and a result or string
error; otherwise close the connection and report an unknown outcome without
resending. Idless pre-dispatch refusals from the control server still work.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Wake the serving loop after protocol output fails and preserve the actual writer error. Use one process-owned stdin worker so the CLI can finish normal backend cleanup without waiting for client EOF, and terminate the output worker even when a progress hook is retained.
Closes#1527
Co-Authored-By: OpenAI Codex <noreply@openai.com>
Document polling of selection/settings/control ranges and shared edit
interactions. Clarify explicit-photo reads, empty selection and navigation,
and correct photo-tool ID descriptions and MCP initialization guidance.
Partial documentation work for issue 251;
hardware feedback remains outside this change.
Co-authored-by: Codex <codex@openai.com>