The equivalence of `header_plan` / `preview_plan` broke for the corpus's
ARW6 pair: a Compression-32766 strip was holed behind its first 64 KiB
prefix, but the header probe validates more there - `parse_tile_header`
walks the 13 stream header words of every tile, `stream_halves` reads
each stream's index table, and `first_half_shift` decodes the residual
stream's first half, which sits behind the earlier streams' data. The
probe read zeros (and 0xFF) there and failed with "ARW6 first-half row
count outside 2..=7" where the whole file decodes (first corpus case:
arw6-sony-ilce7rm6-apsc-land-hq.arw; the corpus is git-ignored, so CI
never saw it).
`tiff_plan` now asks `arw6_keep` (crates/raw/src/sparse.rs) for those
scattered ranges when `image()` reports Compression 32766 for one strip:
the tile table, then per tile, in staged rounds so each ask sits in one
`Need`, the picture/group tables, the stream header words (positions
from the new `arw6::stream_header_positions`, mirroring the group-table
walk), then with the real `parse_tile_header` run, the 13 index tables
and finally each residual first half (its length A is its index's first
entry). A layout that cannot be walked - a corrupt ARW6 - reads whole:
`arw6::decode` stops at the same validation of loaded bytes, so the
probe is still identical. `parse_tile_table`/`parse_tile_header`/`u16_be`
/`u24_be` are now `pub(crate)` for the walker, and `Want::pending_plan`
(renamed from `into_plan`) takes `&mut self` so the walk can keep asking
after the IFD walk answered.
The full-size JPEG these files carry as a `JPEGInterchangeFormat` image
(compression 6/7, where 0x0201/0x0202 are the interchange tag and its
length and `image()` therefore declines) reached no hole at all and made
the plan read it whole: together with the strip work, the header plan
now reads 26-28% -> ARW corpus average 1.7% (the two ARW6 files read
2 / 1%); nothing reads the interchange image in header mode, and under
`preview_plan` `embedded_preview` wants it, so the hole is header-mode
only (the corpus test's 0 / 0xFF fills guard both).
Tests: without the corpus, sparse.rs's synthetic single- and two-tile
ARW6 files (residual stream past the prefix, Qis::REAL) assert the plan
reads past the 512 KiB prefix and probes as the whole file does, for
both fills; with it, `sparse_corpus` passes for both real files, both
fills, both plans, and the ARW6 decode-sum corpus tests are unchanged.
Gates: fmt, clippy -D warnings, heif, parity (+ refreshed summary),
layers, assets and wasm all pass; `cargo test --workspace` has no
failure that clean main doesn't show on this Windows machine (engine
sidecar's 20 fs-fault and ui-egui's denoise-model tests fail there
identically with the fix stashed).
Co-Authored-By: GLM 5.3 Flash <noreply@z.ai>
Three boolean expressions clippy now wants simplified (pipeline's
normalize_wb, engine's learn_as_shot_wb, per nonminimal_bool), a
descriptor_copy only its #[cfg(unix)] test calls (dead on Windows), and
desktop_entries_give_their_program is unix-gated (Path::is_absolute
decides by the platform, and desktop entries run there). None change
behaviour; all four fail cargo xtask ci on this toolchain from main.
Co-Authored-By: GLM 5.3 Flash <noreply@z.ai>
On merge: the HDR histogram is applied in develop_reporting (#712 split develop), is skipped for
draft renders (a second render per drag tick), and HdrBins::bin clamps a hand-built split so it
can't underflow (test hdr_bins_with_any_split_stay_in_range).
#884 focuses a develop slider on click so the wheel and arrows adjust it, but a focused widget
owned the whole keyboard, so after touching a slider no shortcut fired (O for the mask overlay,
caught by mask_adjustments_hide_overlay_without_disabling_the_mask). Now a selected slider owns
the arrow keys only. The wheel test opens the Edit panel explicitly (the demo may start on it, and
panel.edit then toggles it shut), and the Export dialog snapshot is 546 px wide since #833's
nine watermark positions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4
photo.editExternal takes the selection (or ids, at most 50): each photo gets its own -Edit copy
stacked on its original, the copies are selected and returned in copies (path / id / original
still name the first). Photo ▸ Edit In opens each copy in the editor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4
On merge: Lightroom's default profiles (Adobe Standard, Adobe Color), named by nearly every sidecar,
give no camera-profile warning; other profiles still do (test default_lightroom_profiles_are_not_reported).
The import help string keeps main's onDeleted wording and gains warnings.
On merge: the quick preview path orients like the whole-file path (preview_orientation with the
raw's header prefix, so a portrait raw whose embedded JPEG carries no orientation is not sideways),
and its doc links point at preview_window.
- Settings ▸ General ▸ External Editing: the PhotoCraft location and Other editor
fields move from egui::TextEdit to TextField (edit menu, Esc restores).
- Edit In with the system default on Windows starts explorer.exe with the file
as one argument instead of cmd /C start, which read & ^ % | in file names as
shell syntax.
- psd::packbits walks the row with first()/get()/split_at_checked instead of
direct indexing and slicing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4
move_across_volumes read both whole files into memory to compare them; it now
compares lengths first, then streams 1 MiB chunks. The grid's Move to Folder…
asks through pick::ask (background dialog where the host has one) like the
other folder pickers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4