One slow drag only makes the pair suspect, so a single hidden-window
drag passed even without the FRAME_MS_KNOWN upper bound. Two such drags
followed by a full-size third fail (0.6) when the bound is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A busy moment (another app, a backup) could make a slider draft small for
the rest of the session. DragScale keeps timing in wall-clock time, what the
user waits for, but now:
- a (photo, control) pair is slow only after two drags in a row over 20 ms
(1.25x the 16 ms budget); the first makes it suspect, and a drag within
20 ms forgets the verdict;
- a slow verdict lasts 30 s, then a drag tries full size again; a slow
retry doubles the wait, up to 240 s (after WebRTC's CPU overuse detector:
40 s ramp-up delay, doubled after a failed up-switch, capped at 240 s).
The time comes from egui's input clock, which the web build has too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A full set of slow drags forgets only the one remembered longest ago
instead of all 256 (each forgotten pair costs one more slow full-size drag).
- DragScale's doc says a slow drag is remembered for its photo only.
- A host frame time over 1 s (a hidden, occluded or stalled window) counts
as unknown, like no frame time at all, so it can't mark a drag slow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A full-size draft's time is its render plus the CPU time of the frame
that uploaded it (eframe's cpu_usage, Perf::host_frame_ms): render
times alone said 231 of 234 1:1 drafts fit 16 ms while no complete
update did (PR #1009 benchmarks).
- A drag whose full-size drafts take more than twice the budget drops to
0.6 after three of them instead of at its end (a refined mask ran at
190 ms a step for the whole first drag).
- Under a zoom window the hidden whole-frame draft is the 0.6 one, and it
is not timed (the frame's time there is the window's upload).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 10 MP zoom window's ColorImage conversion ran on the UI thread on every
slider step. The pool now hands back the render with its texture pixels;
offloaded (browser) results and results presented again on the UI thread
are still converted there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loupe sized its slider-drag drafts by its own copy of try_render's
"CPU only" list (soft proof, HDR range). A new CPU-only overlay added to
try_render (e.g. #995's sharpening mask) would have left the loupe
drafting full size on the CPU for its first drag. lightcraft_gpu::handles
is now the single rule: try_render returns NotTried by it and the loupe
asks it, so the two can't drift apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up. "A GPU device exists" does not mean the GPU draws the
view: soft proofs and the HDR range render on the CPU, and a LUT profile,
refined masks or a photo past a GPU limit fall back per render. Those drags
had become full-size CPU renders.
region::DragScale now picks the draft size when a drag starts and keeps it
until the drag ends (no resolution jump mid-gesture): full size where the
GPU draws the view (not soft-proofed, no HDR range) and no earlier drag of
the same control on this photo went over the 16 ms slider budget at full
size; 0.6 otherwise. Full-size drafts are timed as they land (the first,
rebuilding stage caches, is left out; the median decides).
ViewSizes carries one `draft: Option<f32>` instead of a scale and a flag
that could disagree. Tests: DragScale unit tests (budget, lock, hostile
times) and headless drags for a remembered slow drag and a soft-proofed
drag, each checked to fail without its guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slider drag rendered the loupe's whole frame at 0.6 of its size, so the
fit view went soft while dragging. On the GPU a canvas-sized frame takes a
few ms, so drags now render at full size there; the CPU keeps the 0.6 draft
(region::drag_scale, CPU_DRAFT_SCALE). The no-window path's canvas cap now
follows an explicit `drafting` flag instead of `scale < 1.0`, so a GPU drag
at 1:1 still never renders every pixel of the photo each frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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