Files
lightcraft/docs/gaps.md
T
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 (14e0d128) picks the smallest covering
JPEG of a TIFF-based raw; this extends it to Canon CR3 and adds the 8-bit
decode:
- A CR3 keeps an 8192 px JPEG track, a 1620 px PRVW box and a 160 px THMB.
  embedded_preview_covering now picks among the three (cr3_boxes lists the
  boxes), so a grid thumbnail or a loupe up to 1620 px decodes the PRVW.
  Loader at 1280 px on 20 EOS R5 C CR3s: 168-322 ms -> 31 ms a photo.
- lightcraft_codecs::decode_jpeg_srgb8_fit decodes a plain 8-bit sRGB
  JPEG (DCT-scaled) and box-averages it in linear light straight into
  8-bit sRGB, without the float image the general path builds; anything
  with an ICC profile, an Adobe RGB declaration, CMYK or 12-bit takes the
  general path. 45 MP preview fitted to 2560 px: 182 -> 151 ms (min of
  15, loaded machine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a67bb398c2f438cbe16f4db1431ae0270f1c8b04)

* PERF-CULL-4: Camera preview label; thumbnails ahead of the step; 100 % shows the camera JPEG first

- While the loupe shows a raw's embedded JPEG (or its thumbnail), a small
  'Camera preview' label at the top right says so, with what that means
  on hover (widget loupe:cameraPreview; nine languages), so colour and
  tone are not judged on the camera's rendering.
- The look-ahead also asks for the next photos' thumbnails (a small
  embedded JPEG each), so a held arrow key has something to show in full
  screen too, where no filmstrip asks for them (2400x1500 window, 40 held
  steps: camera JPEG on 36, nothing on 2; before 1 and 4). Stepping
  through a whole wedding keeps at most 600 thumbnails, those around the
  open photo.
- Zoomed in on an unedited raw, its camera JPEG at full size (8192 px for
  an EOS R5 C) is drawn under the window render until that arrives:
  100 % in ~0.9 s instead of ~5.6 s; released when not zoomed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9f212b1e16815c3c0ca33130319a7ef41fb05a26)

* PERF-CULL-5: lightcraft-cli bench-cull and tools/cull-bench.py time culling a real shoot

bench-cull FOLDER (headless) times reading the folder into the Local view,
the first screen of grid thumbnails on the app's render threads, camera
previews at 1280 / 2560 px and full size, a cold 2560 px loupe render,
rating 50 photos in a row and importing N files in place / copying M,
as 'name: X ms wall' lines (p50, p95, n) and JSON. Libraries are made
under --work; nothing is written next to the photos.

tools/cull-bench.py drives the running app over the control channel
(browse, first photo, steps at rest, a held arrow key, 100 % zoom,
rating) and reads the app's own frame timings (perf.loupeSteps).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bd2eaac591b72f8258acae4389c67aa4d1da3b27)

* PERF-CULL-5: bench-cull --files K samples a slow folder; e2e test; cull-bench usage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c32c9556924ccdee6f8c103864df51c3e170df98)

* PERF-CULL: docs for culling speed (parity rows, gap 26, preview reads, opening a photo)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fb69b5975b51f882d82b2ca684e0f7ddc8373908)

* PERF-CULL: the whole-file preview decodes a plain sRGB camera JPEG the quick path's way

Upstream's quick-path test wants the bounded read and the whole file to give the same picture
byte for byte; with the branch's straight-to-8-bit decode on the quick path only, a 49 px
preview came out differently. Both paths now take it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0c8acab4c2f3e85d1c51fab5faf62b6e997d7c73)

* PERF-THUMB-FIRST: CR3 thumbnails decode the 1620 px PRVW copy, and grid thumbnails from camera JPEGs are kept in the thumbnail cache

A CR3 grid thumbnail decoded the full-size JPEG track (45 MP, ~150 ms each)
whatever size was asked; it now takes the smallest embedded copy that covers
the cell (PRVW, same shape), and the box search stops at mdat (was a scan of
the whole file). The decoded camera JPEG of an unedited raw is written to
the library's thumbnail cache, so a folder seen before fills from small
files. Measured on 250 R5 C raws (loaded machine): first screen fills in
294 ms (was 553), on a second visit 53 ms (was 610).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 12162f966dd859b7e53ac65b5c4564a8e837b7eb)

* PERF-CULL: cargo fmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: docs for culling speed (parity rows LR-IMP-LOCAL, LR-IMP-PREVIEW-READ, LR-VIEW-DETAIL; gap 26)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: review fixes (bench work folder, 8-bit APP1 cap, size sort)

- bench-cull makes its libraries in a new folder of the run's own under
  --work and never deletes what is already there (it removed lib-browse,
  lib-add and lib-copy under a folder the caller named).
- The 8-bit sRGB JPEG path refuses more than 128 APP1 segments before its
  decoders walk them again, as the general decode does.
- Browsing by file size keeps unreadable files last in both directions
  (they were sized 0, so first when ascending).
- --files K in the CLI synopsis and help.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the loupe uses built previews; photos are prepared ahead and in the background

Built standard previews were never what the loupe showed: it rendered
every photo again (~1.8 s on a raw), the rendered previews in memory were
capped at 128 MB (~84 photos) whatever the budget, the disk cache was a
fixed 2 GB (a wedding's previews pruned each other), the cache's writer
skipped disk writes when it fell behind, and Build Previews with one photo
open built only that photo though the menu said "Visible".

- The loupe's render job takes a cached view render of the same settings
  that is at least as big as asked for, fitted down (no render).
- Memory for rendered previews is a quarter of the cache share; the disk
  cache's automatic size makes room for every photo (about 1.5 MB each);
  Settings offers Automatic, 50 GB and 200 GB.
- A build skips photos whose previews are there, writes each to disk
  before counting it, builds at least the loupe's size, and with one photo
  selected builds the view.
- Prepare photos in the background (Settings > Performance), per mode
  (culling, library, editing), app-wide (kept in ui.json) or per library:
  photos ahead rendered for the loupe (a few at a time, 0 = off) and the
  whole view's previews built in the spare time once the user steps
  through photos: two lanes, waiting while the photo on screen renders,
  nearest first in the direction of travel, one cancellable row, replaced
  by a build that is asked for. Commands prefs.backgroundPrep /
  prefs.setBackgroundPrep; ui.inspect perf.previewCache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the bench work-folder test removes only a folder it made

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: browsing keeps the whole listing for the folder bookkeeping; cull-bench stops on small folders

The browse worker queues only the files the library lacks, but the folder
bookkeeping (browse times, records of files gone) needs every file listed:
a subfolder of known photos only was not stamped as browsed. The known
files now come along marked as already in the library. tools/cull-bench.py
stops waiting at min(12, photos) thumbnails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: review fixes (smart previews honour id, editing builds by the Library row, lenient prep settings)

- library.smartPreviews with an explicit id works on that photo, not the
  whole view.
- While editing, the whole view's background build follows the Library
  row (its switch and which photos); the Editing row's scope is for the
  photos kept ready ahead.
- The app-wide and per-library preparation settings are read leniently:
  a value this build doesn't know is the defaults (app) or none (library),
  and the other settings are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: cull-bench counts twelve thumbnails while the folder is still loading

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: a view change stops the background build for the old view

The running background build was only re-aimed at the photo on screen; after
another folder, filter or library it went on building photos no longer
shown. It is now stopped when the view's signature changes (not counted as
stopped by the user), and the view now shown is prepared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: cull-bench picks the thumbnail threshold from the final photo count

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the view signature covers every photo; the view-change test holds the build explicitly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: cull-bench stops waiting for twelve thumbnails 30 s after the folder loaded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: in the marked scope, marking a photo changes what the view's build prepares

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: cull-bench times only the thumbnails the browse adds

Thumbnails already cached before library.browse no longer count towards
the first-thumbnail and twelve-thumbnail times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: count every thumbnail made, for the browse benchmark

thumbTextures counts resident thumbnails, and evicting old ones can hide
new ones. The renderer now also counts each grid thumbnail it stores
(perf.thumbsMade, never lowered by eviction), and cull-bench times the
browse from that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the marked-scope test waits for its builds before cleaning up

Both preview builds finish before the test library is removed, and the
removal is checked, so no worker writes into a folder being deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: stand-ins made ahead keep their order, size and photo

Review fixes for the loupe's stand-ins and the browse benchmark:
- a stand-in still queued ahead moves to its new place in the order
  when the user steps;
- one made ahead still shows when the loupe's own request failed;
- one made ahead for a smaller loupe isn't taken over by a larger one;
- quick and full-size camera-preview keys include the photo, so a
  virtual copy gets its own;
- perf.thumbsMade counts photos given a thumbnail, not a render
  replacing a stand-in.
Each has a test that fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a late stand-in never takes another photo's place in the loupe

A stand-in made ahead takes over after the loupe's own request failed
only while the loupe still wants that photo and has no other request
pending; the test covers stepping on before it arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a stand-in made ahead and no longer wanted is dropped when it arrives

Freeing a look-ahead slot whose job is already running now also forgets
its request, so the late result isn't kept as a hidden texture, unless
the loupe is waiting for exactly that stand-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a running stand-in the loupe waits for keeps its look-ahead slot

When the next look-ahead no longer wants the photo the loupe has just
stepped to, the slot whose job is still making that photo's stand-in stays
reserved instead of going to a new photo, so the result still reaches
the loupe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 10 (browse order ties, library skip, CR3 boxes after mdat, badge once, look-ahead crop key, cli test binary)

- browse_order: import- and edit-date sorts queue new files by name (they
  tie on those keys); ties go by name in the view's direction, as the
  catalog breaks them.
- Local browsing never lists the library's own folder (browse_files, as
  scan_with skips it).
- CR3 PRVW/THMB search skips only the mdat payload: boxes after it (a moov
  holding THMB) are still found.
- The camera-preview badge and the loupe timing are settled once, after
  the window renders (no double badge with the full-size camera JPEG).
- The loupe look-ahead key includes the crop.
- bench-cull CLI tests use binary() after upstream's test split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 11 (browse listing errors and library root, rating ties, every CR3 PRVW, checked CR3 box math, look-ahead view key, windows wait while settling)

- browse_files: a folder that can't be listed is an error (the scan ends
  as failed, nothing is forgotten as gone), and the library's folder or
  one inside it lists nothing.
- New photos tie on rating: queued by name in the view's direction.
- Every valid CR3 PRVW/THMB is a candidate (the smallest covering one is
  picked); the box offsets use checked arithmetic.
- The loupe look-ahead is keyed to the visible list's generation (a new
  sort, filter or source changes the neighbours).
- While stepping fast, zoomed-in window renders wait like the frame, and a
  window drawn before the frame is timed as the render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 12 (loupe timing: the After window, over any stand-in)

In a split view only the After side's window says the raw's render is on
screen; and a window drawn over the camera JPEG counts as the render too
(the source shown is a render only when it is "render" or "cached").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 13 (loupe timing: the Before window when the Before is shown alone)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 14 (loupe timing: a Before window side by side with \ held is not the shown view)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 15 (the split-view timing test asserts its precondition)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 16 (side by side with \ held, each window stays in its pane)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a cancelled browse stops ordering its files; browsing hands back earlier probes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the web build runs the browse job in place as FnMut (it hands the probes back)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a scan hands the probes it took back however it ends (CodeRabbit, #3077 / #3080)

- The browse worker sends a result on every path: the listing, or `Stopped` (the folder couldn't be listed, or
  another folder was chosen) with the probes `ScanInput::new` took from the session. A listing failure is never
  sent as an empty listing.
- A scan stopped without waiting (its cross, or a browse replaced by another) keeps its channel until the worker
  ends, so the probes still come back; a finished scan's probes join the session's instead of replacing them.
- A cancelled scan whose worker ended without a result is not reported as "Scan failed".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: built previews are the ones on disk; stand-ins ahead follow the preparation settings (CodeRabbit, #3077)

- A build counts a photo as built only when its previews are in the disk cache (when it has one): a loupe
  render kept only in memory, its deferred write skipped, is built again and written (`stored_dims`).
- `DiskCache::put` and `PreviewCache::put_at` say whether the file was written; a view preview the cache
  folder can't take fails the photo instead of counting it as built.
- The loupe's stand-ins ahead honour Settings > Performance for the mode: none for "Only the photo on screen"
  or no photos ahead (the queued ones are dropped), only marked photos in the marked scope. The settings are
  part of what the look-ahead was made for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: a preview for a discarded cache fails the photo; cache file sizes are read from 4 KiB first (CodeRabbit, #3077 / #3080)

- `PreviewCache::put_at` returns false for a job made before the cache was cleared or retired (nothing is
  kept), so a build that overlaps Discard Preview Cache counts that photo as failed, not built. The warning
  says which of the two happened.
- `DiskCache::dims` reads a file's first 4 KiB, and up to 64 KiB only when the size marker isn't there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: a photo another build renders counts once it is built; a write pruned away is not kept (CodeRabbit, #3077 / #3080)

- A build that meets a photo another build is rendering waits for that render, then counts the photo as
  built only when its previews are there, else renders it itself (it was counted as shared at once).
- `DiskCache::put` returns false when the pruning its own write starts removes that file (a picture bigger
  than the room pruning leaves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: put_memory holds the cache generation through its insertion (CodeRabbit, #3080)

A clear can no longer finish between put_memory's look at the generation and its insertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: browsing skips the library's folder by any name it has (a symlink, a path through ..) (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: rustfmt the library-folder check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: cancelled builds stop before their test library is removed (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the library folder is skipped by its resolved path only; lib/../outside is browsed, an unresolvable folder reports its error (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the replaced background build is held by TestHold, not a 250 ms deadline (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a browsed folder has one spelling (. and .. taken out by name) for the view, its listing and the catalog's records; an unresolvable root inside the library is still skipped (CodeRabbit, #3055 / #3077 / #3080)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: TestHold counts its holders: one test's release doesn't free a build another test holds (CodeRabbit, #3077 / #3080)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: TestHold's release without the deprecated fetch_update

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: browsing resolves `..` the way the system does, and `.` is the current folder

`lexical_path` took `..` out by name, which is wrong on POSIX when the folder before it is a
symlink (`link/..` is the folder above where the link points). `browse_path` makes the root
absolute first (so `.` and `a/..` name the current folder, not an empty path), resolves a link
just before a `..` on POSIX, and keeps every other spelling as written (a mapped drive stays a
drive letter, matching the catalog's records). Windows takes `..` out by name, as before.
Tests: a folder link followed by `..` lists what the system lists there; `.` is the current folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: browsing resolves the folder on its worker, never on the window's thread

`browse()` called `browse_path` (which may canonicalize a link) and `is_dir()` before starting the
worker, so a link into a stalled network mount could freeze the window. The UI now spells the
folder with `browse_spelling` (`.`/`..` by name, no disk access) and shows it as given; the worker
resolves it with `browse_path`, checks it is a folder (else the scan ends as failed, with the
usual toast), and sends the resolved spelling, which the view takes before the listing arrives.
Tests: a browse whose resolution is held returns at once and waits, then reads the folder; a
missing folder fails on the worker; `browse_spelling` matches `browse_path` without links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the browse worker resolves the path as given, not the view's spelling (CodeRabbit, #3055 / #3077 / #3080)

On Unix `photos/link/../select` with `link` pointing at `real/day` is `real/select`; the spelling with `..`
taken out by name read `photos/select`. The view keeps the spelling until the worker answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: bench-cull measures small folders and never writes its results among the photos (CodeRabbit, #3077)

A folder with no more photos than the grid sample was reported with empty camera-preview and loupe timings: the samples now fall back to the photos it has. A --json path in the source folder is refused before the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: editing renders the loupe from its source; the loupe holds background work back only while its render is on the way (CodeRabbit, #3077)

In Develop a cached view render left the source undecoded until the first slider draft. foreground() was renewed every loupe frame even with the render shown, so background builds mostly waited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: loupeSteps.total, a step cursor that survives the 256-step list (CodeRabbit, #3077)

tools/cull-bench.py sliced the list by its length, which stops growing at 256 steps, and reported no steps on repeated runs. Also documents perf.thumbsMade and perf.previewCache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the camera preview is located once when the fast decode declines (CodeRabbit, #3077)

The general decode takes the stand-in JPEG the fast path already found instead of parsing the container again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the uncapped preview cache warns once per session when its drive runs low

The automatic preview cache budget grows with the library and has no ceiling. When the library
opens, and at most every five minutes while previews are built, the engine reads the free space of
the drive holding the cache (fs4, MIT OR Apache-2.0, pure Rust over rustix / windows-sys); under
10 % or under 20 GB free it raises one alert per session. The desktop app shows it as a notice with
the drive's free space and the cache's size and a button to Settings > Performance, where a size
limit can be set. A drive that can't say its free space gives no alert (logged). library.info
reports lowDiskSpace; the notice is in ui.inspect's notices like the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the free-space read runs off the UI thread; a queued low-disk notice goes with its library; --work outside the source folder (CodeRabbit, #3077)

- Session::check_disk_space reads the drive on a worker thread and takes the answer on a later
  call (take_disk_space_alert, library.info, the next check): a slow network share no longer
  holds up a library open or a UI frame. Test: a_slow_drive_does_not_hold_up_the_check.
- The UI keeps the low-disk notice with the library generation it was raised for and drops it
  when another library opens. Test: a_queued_notice_goes_when_another_library_opens.
- The real-drive test runs on native targets only (read is None on wasm32).
- bench-cull refuses a --work folder inside the measured folder, also one not made yet, before
  anything is created. Test: the_work_folder_never_goes_in_the_source_folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: one free-space read at a time across library switches, the cache counted on its worker; a --json link into the source folder is refused (CodeRabbit, #3077/#3080)

- A library opened while the drive's free-space read still runs keeps that read (marked stale)
  instead of starting a second worker beside it; its answer is dropped and the current library's
  drive is read once it has come. Switching libraries while a share hangs no longer piles up
  workers. Test: switching_libraries_while_a_drive_hangs_starts_no_second_read.
- When the drive is low, the preview cache is counted on the read's worker and the count travels
  with the answer: taking it on the UI thread no longer scans a big cache on a slow share.
  DiskCache::known_size reads the count without touching the disk.
  Test: the_preview_cache_is_counted_off_the_ui_thread.
- bench-cull: a --json path that exists is resolved itself (a link to a photo in the source folder
  is that photo), a link that points nowhere yet by the file writing through it would create, a
  loop of links is refused. Test: a_json_link_into_the_source_folder_is_refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: bench-cull --json replaces its file instead of writing into it, so a hard link to a photo can't overwrite the photo (CodeRabbit, #3077)

The results go to a new file next to the destination, renamed over it; the
temporary file is removed on error. Test:
a_json_hard_link_to_a_photo_leaves_the_photo_alone (fails with the old write).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: bench-cull refuses a --json entry in the source folder, also a link there to a file outside (CodeRabbit, #3080)

Writing the results replaces the --json entry itself, so a link sitting in the
source folder that points outside it would be replaced, removing that entry.
check_outputs now checks where the entry sits (its folder resolved, the final
link not followed) as well as where it resolves to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a frame without the loupe ends its step timing, so time away from Detail never counts (CodeRabbit, #3055)

LoupeTiming::left was never called: a photo's record stayed open while the grid, Compare, Survey,
Reference, People or the slideshow was up, so dwell / any / full / sharp ms counted time outside
Detail. observe() marks the frame; finish_ui_pass ends the step when the loupe drew nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: Build Standard-Sized Previews keeps 2560 px before a loupe was drawn (CodeRabbit, #3077)

On Automatic with no loupe size known yet (a fresh session) the command left `edge` out, so the
engine built its 2048 px minimum and a typical fit view rendered again. The loupe-aware engine
size still applies once a loupe was drawn; a chosen limit is used as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: main's merge keeps one Russian "★ Favorites" and its browse test uses the scan's end channel

Both sides had added the same key; main's new Auto Import browse test sends through the scan
channel, which this branch carries as `ScanEnd`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: stepping fast drops the queued Before render too; the thumbnail count is per photo across eviction (CodeRabbit, #3055)

While an arrow is held the Before view's raw render waits like the loupe's own, and one still
queued for a photo left behind is dropped. `thumbsMade` counts each photo once per library, so a
thumbnail evicted and made again, or a library left and opened again, doesn't count twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the free space is read every few minutes, not only while previews are built (CodeRabbit, #3080)

Thumbnails and the loupe's previews fill the cache too, so a drive could run low after the library
opened without the notice: the low-disk check now runs whatever fills the cache (still at most
every five minutes). The engine gains two hidden test hooks (a reader and forgetting the last
read).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: a slow free-space read shows in the activity stack and an idle window wakes for the disk check (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: background preparation advances on every logic pass, hidden ones too (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a photo the loupe opens on is never a fast step; only a step inside the loupe has a predecessor (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the cull benchmark rates one named photo and puts its rating back (CodeRabbit, #3077)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the cull benchmark refuses a full step history, says when thumbnails were in memory, fails on a lost key and ends a zoom that needs no region (CodeRabbit, #3055)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: a forced build renders a photo another build was rendering itself (CodeRabbit, #3080)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the cull benchmark stops on a folder with no photos and times a zoom by the region that ended it (CodeRabbit, #3055/#3080)

- browse: a folder that stays loaded with no supported photo ends the browse loop after 3 s
  instead of waiting 30 minutes, and the run stops with a clear message before the loupe
  section instead of failing on an empty selection;
- zoom to 100 %: the time reported is the moment the loop ended on (the region's arrival, or
  the settled whole frame when no region is asked for), so a whole frame that settled before
  a region was requested no longer gives a too-short zoom time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the cull benchmark says no photos are visible, keeps an earlier result, and records an unfinished zoom as missing (CodeRabbit, #3055/#3077/#3080)

- an empty selection after browsing means no photo is visible (a filter can hide them), not that
  the folder has none: say so and point at the filters;
- LABEL.json is no longer overwritten: an existing one stops the run before it starts unless
  --overwrite is given (the final write opens with "x" then);
- a zoom that did not finish within 60 s is written as null, not as a 60 s zoom time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a raw's preview pointer is read with checked bounds on its offset and size (FredApps, #3080)

The JPEGInterchangeFormat pointer and the undefined-tag JPEGs were sliced with `as usize` casts of
the file's own u64 numbers: safe on 64-bit (the slice is `get`, its end cut at the file's end), but
an offset past 4 GiB would wrap to a small, wrong one on a 32-bit build (wasm32). `file_span` now
converts with `try_from` and saturates the end.

Test: `a_preview_pointer_past_the_end_of_the_file_finds_no_preview` (pointers past the end, lengths
past it, u32::MAX for both; a real strip preview beside the lying pointer is still found; every
truncation of the file is safe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: bench-cull refuses a --work or --json inside the photo folder (FredApps, #3055)

Nothing is ever written next to the photos: a --work folder or a --json file inside FOLDER (the
folder itself, a subfolder, or a path that goes there through `..`) is refused before anything is
made. Paths are compared resolved (links followed as far as they exist). The cleanup already
removes only what the run made (its own `bench-cull-run-…` folder's `lib-copy`), never --work.

Test: `work_and_json_inside_the_photo_folder_are_refused`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: a test that building previews writes nothing next to the photos (FredApps, #3077)

`building_previews_writes_nothing_next_to_the_photos`: Build Previews and the background
preparation while culling leave the shoot folder with the same files, byte for byte; previews go
only to the library's own cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: cull-bench.py prints its usage on missing arguments and refuses a dangling LABEL.json link up front (CodeRabbit, #3055/#3077)

- PORT / FOLDER missing (or PORT not a number), or an option with no value or an unreadable one,
  stop with the usage line instead of a traceback;
- the early "LABEL.json exists" check uses `os.path.lexists`, so a dangling link there is refused
  before the run, not at the final exclusive write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a preview cut short by the end of the file is not taken (CodeRabbit, #3077)

`file_span` keeps a span the file's end cuts short only while it still holds a JPEG end marker
(an overstated length with the whole JPEG inside). A truncated JPEG passed the frame-header check,
won as the largest preview and then failed to decode, so no preview was shown.

Test: `a_preview_cut_short_by_the_end_of_the_file_is_not_taken`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-PREVIEW-PREP: the shoot-folder test waits for the background build to finish (CodeRabbit, #3077)

`building_previews_writes_nothing_next_to_the_photos` used a 5 s capped poll and then went on; it
now asserts `wait_finished` (30 s), so a late write by a slow build can't slip past the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a cut-short preview must reach its own JPEG end marker; cull-bench refuses bad options up front (CodeRabbit, #3055/#3077/#3080)

- raw: file_span keeps a span the file's end cuts short only when walking the JPEG's marker
  segments reaches its end marker inside it, and cuts it there. An FF D9 inside an APP payload no
  longer counts (it is skipped by the segment length), and bytes that don't start with FF D8 are
  refused without scanning the rest of the file. Tests: the cut-short test gains an APP1 payload
  holding FF D9 (taken before this fix) and a whole JPEG followed by other bytes;
  the_end_of_a_jpeg_is_its_own_end_marker covers stuffed bytes, restart markers, bad segment
  lengths and every truncation.
- tools/cull-bench.py: an unknown option, an option where a value belongs (--label --overwrite),
  --steps/--hold-ms below 1, or a port outside 1-65535 stop with the usage line before the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: cull-bench refuses a run whose steps wouldn't fit the app's loupe history (CodeRabbit, #3055)

The app keeps its last 256 loupe steps. A --steps that can't fit a run (1 + 11 + STEPS + 1) stops
before the run, and each phase checks the room left before it starts, so a hold whose first steps
were dropped is never reported as the whole hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: bench-cull refuses ".." in --json and --work (CodeRabbit, #3544)

A ".." after a folder the run hasn't made yet can't be resolved by the source-folder check, and
the run then makes that folder: --json NEW/../FOLDER/r.json would land in the source folder.
Test: a_dot_dot_in_the_outputs_is_refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a cut-short preview needs a scan before its end and the walks share a budget; cull-bench catches dropped steps and odd arguments (CodeRabbit, #3055/#3077/#3080)

- raw: jpeg_end accepts an end marker only after a start of scan (an end with no image data
  before it is refused), and the walks of a file's pointers past its end share scan_budget, as
  scan_for_jpeg's do. Tests: a whole-looking JPEG with no scan is not taken;
  the_walks_to_a_jpegs_end_share_a_budget.
- tools/cull-bench.py: the port must be a decimal number int() takes (isdecimal, guarded); a
  FOLDER that looks like an option stops with usage; --steps goes up to 254 (only the hold's
  STEPS + 1 must fit, with one to spare); each phase starts with room to spare, so a full history
  means other steps came in and the phase stops instead of reporting a shifted window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: the camera JPEG goes only under the window render, and a stand-in decoded across a cache clear isn't kept (CodeRabbit, #3544)

- ui-egui: zoomed in, the full-size camera JPEG is painted only where the window render will go
  (window_cover), so a window capped smaller than the view leaves the developed whole-frame render
  in the strips beside it instead of an unlabelled camera JPEG. Test:
  the_camera_jpeg_covers_only_where_the_window_goes.
- engine/preview: QuickJob's camera-JPEG cache carries the cache generation it was made in and
  writes with put_deferred_at / the new put_memory_at, so a job decoding when Discard Preview
  Cache clears the cache can't put its picture back (as render jobs already do). Test:
  a_camera_jpeg_decoded_across_a_cache_clear_is_not_kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a bad build request leaves the background build alone, and preparation settings that can't be saved aren't kept (CodeRabbit, #3544)

- library.buildPreviews stops a running background build only after its own ids are known to be
  good, and a build whose thread can't start is marked finished instead of left "running".
- prep_tick clears started_for when the background build fails to start, so the view retries.
- set_prep_settings / clear_library_prep put the previous settings back when prefs.json can't be
  written. Tests: a_bad_build_request_leaves_the_background_build_running,
  a_prep_setting_that_cant_be_saved_is_not_kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a background build that can't start waits 30 s before the view tries again

Clearing `started_for` on a failed start made every tick call library.buildPreviews again while
the cause lasted (CodeRabbit on #3080). The view now waits RETRY_AFTER first. Test
`a_build_that_cant_start_waits_before_trying_again`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a build waiting to retry asks for a frame at its time; a build whose worker can't start leaves the background one running

CodeRabbit on #3077/#3080: a background build that couldn't start retried only on
the next input. The session now says how long until the retry (prep_retry_in) and the
app asks for a frame then.

CodeRabbit on #3544: a build asked for whose worker thread couldn't start cancelled the
background preparation and left nothing running. The background build is now stopped
only once the new one has started; otherwise it stays the session's build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: a retry time that has passed asks for no more frames

CodeRabbit on #3077 / #3080: once the retry deadline passed, prep_retry_in returned
zero, so a view that couldn't be tried (grid, preparation off) asked for a frame at
once on every pass and kept an idle window drawing. It returns None then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit items from #3577 that live in #3077 and #3055

- disk space: a read whose thread can't start (or whose worker ends without
  answering) is tried again after 10 s, not after the 5-minute check interval;
  a hung read is polled every 5 s after its first 5 s instead of every 250 ms
- the low-disk notice of the library before is not drawn in the frame after a
  library switch
- library.smartPreviews reads its ids before stopping the background build
- a forced build renders the grid thumbnail again instead of reading its cache
- the loupe's look-ahead asks for its stand-ins again after a renderer reset
- browse: a broken link before `..` is an error, not the link's parent folder
- the probes of two stopped scans: the later scan's wins whichever ends first
- camera JPEG fast path: a JPEG with an incomplete ICC sequence goes the
  general way; a CR3 without a JPEG track picks its box preview by size too
- tests: bench-cull compares the source photos' bytes, the disk notice test
  waits for the open's read

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PERF-CULL: CodeRabbit round 4 on #3077 and the #3583 items in its code

- A background smart-preview request cancels the running build only once its worker started;
  a worker that can't start leaves the earlier build running and the session's (test hook).
- The notice logic looks again at the next disk check (10 s after a read that couldn't start).
- CR3 boxes: largest by pixels; every top-level mdat left out; whole-file fallback takes the
  first box of a kind; a copy without an end marker isn't chosen to cover a size; a test
  decodes the chosen copy.
- The fitted JPEG decode reads a SEF file as the image before its trailer; its doc says a
  header that can't be read is Ok(None).
- Settings: a saved cache size the list doesn't offer shows as its own option.
- bench-cull rejects a second FOLDER; a browse through `..` reads again.
- cull-bench.py: --size checked, baseline waits for renders, --skip-browse claims no folder,
  a short rest phase warns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Format the quick-full draw call

* Loupe stand-in choice moves to its own function

* Render results: the newer-draft and display checks move to their own functions

* Render results: a frame left on the GPU is shown by its own function

* Format the frame's texture entry

* Docs: one row each for the import and performance entries

* Format the quick view's key

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 08:32:26 +01:00

43 KiB
Raw Blame History

Gaps: where LightCraft falls short of Lightroom

Last reviewed: 2026-10-11 · Last updated: 2026-11-05 · Change: minor (gap 26: culling uses built previews, background preparation per mode; before: Contrast and Blacks calibrated in process V4, #523; PERF-STEP-STALL: flags, ratings and edits never wait for the drive; gap 1: the Canon EOS D60 is the first chart-calibrated model, its matrices from our own ColorChecker shots give its white balance a Kelvin scale; gap 20: Canon CRW decodes; LR-LIB-VIRTUALCOPY: default number exhaustion is validated before copy creation; gap 8: lens profile work is tracked in #329; gap 8: lensfun's lens database offered on the first launch and downloaded on request, looked up for the library; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; typed GPS input now rejects malformed coordinates before metadata changes; LR-MAP-TRACKLOG decodes GPX character references in coordinates, elevations and timestamps; malformed references error; CDATA stays literal; ranked gaps unchanged; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; import: checked rename-sequence exhaustion; import review: remembered thumbnail size and density-aware previews (#2237, part of #2056); pre-import loupe/keyboard culling remains open; no ranked gap closes; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; gap 8: Sony ILCE-7RM6 in-file distortion conversion extends to the APS-C ILCE-6700; LR-META-EXIF-FULL rejects extra EXIF clock components and preserves date fallbacks; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; JPEG ICC APP2 sequence validation; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 557 models verified with CRW and MRW; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: \ shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF; gap 24: extreme brush segments skip the intermediate dabs; Last reviewed: 2026-10-11 · Last updated: 2026-10-20 · Change: minor (gap 15: Print Page Options — page numbers, page info, crop marks)

Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and how hard it blocks them), then by effort. Agents pick work from the top (the first entry nobody is working on). When you close a gap, delete or shrink its entry, update its rows in parity-checklist.md, and the numbers in target-app-parity.md and ROADMAP.md if they move.

Hours are Opus 5.5 agent wall-clock hours (see target-app-parity.md). B marks a gap that blocks beta.

Ranked

# Gap Evidence User impact Hours Doc
1 B Camera colour is estimated, not measured (LR-PROF-CAMERACOLOR). Raws start from a per-file fit to the camera JPEG, 10 bundled profiles, 52 spectral matrices (plus same-sensor bodies, e.g. the X-T30 through the X-T3) or chart-measured matrices (the EOS D60); others use a neutral matrix (a black-and-white camera JPEG gives its tone curve only, #577; dark JPEG tones also guide the toe, #1307) ΔE vs camera JPEG 2.9–10 after fitting (raw-parity.md); 1 chart-calibrated model (EOS D60: its Kelvin scale and fallback colour, the look stays the JPEG fit) vs 1,500 Adobe Standard profiles every raw shooter sees colour that is close but not Lightroom's; some get neutral-matrix colour 50–90 raw-parity
2 B CR3 variants no public file uses (LR-IMP-FORMATS): crx.rs refuses vertical-tile C-RAW, partial-subband and other plane coding, multi-tile 0x200 and adaptive low-frequency gains, and those files fall back to the preview. All 110 CC0 CR3s on raw.pixls.us (31 folders) decode from sensor data, so none of these variants has a public sample; 26 CR3 bodies are verified (M50 / R100 / R8 exact, R5 Mark II pinned, 22 more pinned on one CC0 file each and checked against the camera JPEG, cr3.md). HEIF stills are .HIF files, not CR3; no CR3 sRAW / mRAW sample exists measured: Canon's share whose camera fully works 73% → 94% (raw-parity.md); 18 of 22 popular Canon bodies write CR3, all 18 now verified +0.3 points of photographers; the variants surface only as user files that open as a preview 10–25 raw-parity
3 B Render fidelity partial (LR-BEHAV-RENDER-FIDELITY): calibrated camera-look Contrast and Blacks; other operators need measurement process V3 reduces held-out response error from 0.136 to 0.038 stops for Contrast and 0.424 to 0.077 for Blacks on one Sony camera; Whites remains V2 pending a follow-up imported Lightroom edits still differ, especially on uncalibrated operators and cameras 30–60 calibrated Light operators, process versions
4 Original-pixel detail at 100% (LR-VIEW-ZOOM): closed 2026-10-12, see Closed recently checklist row ✅ — 0 ui-parity
5 B AI masks: Subject, Sky, People, Background, Landscape are heuristics; Object / Describe run SAM 3 but its download has no mirror configured crates/segment; ai-masks.md; MASK rows 67% masking is how most Lightroom users edit today 40–70 + model decision target-app-parity
6 B Compressed ORF (every Olympus / OM body since ~2008) measured: 66 of 78 Olympus / OM models preview-only; orf.rs refuses; OM bucket 3% of photographers, 3% of them fully work +2.6 points; best gain per hour of any raw gap (~0.2) 8–15 raw-parity (the two Olympus patents that describe the method)
7 Nikon High Efficiency NEF (HE / HE★ on Z 8, Z 9, Z 6III, Z f, Z50II, Z5II when set to HE) named and preview only (#193); needs a pure-Rust JPEG XS decoder +1.1 points (only shots taken in HE; lossless works on the same bodies); low gain per hour (~0.04), so not a beta blocker 20–40 raw-parity
8 B Lens profiles: lensfun only, no picker, no own calibration (LR-EDIT-OPTICS-PROFILE): the desktop app offers lensfun's database (CC BY-SA 3.0, pinned, never bundled) once on the first launch and downloads it on that click or from Settings, then looks every raw up by its lens (lens-profiles.md), but those profiles are not validated against Lightroom, there is no profile picker (in the file / lens database / own calibration) and no calibration wizard; in-file corrections: DNG opcodes, RW2 in-file distortion, Sony ILCE-7RM4A, ILCE-7M3, full-frame ILCE-7CR distortion (Auto only) and ILCE-7RM6 and ILCE-6700 distortion (docs/sony-ilce7rm6-lens-corrections.md) and Nikon Z 6II / Z 8 / Z f distortion (docs/nikon-lens-corrections.md); tracked in #329 Adobe ships 3,635 lens profiles; we never use them distortion / vignetting correction is a default step for many 40–80 + calibration shots raw-parity
9 B Tests don't run in CI on macOS or Windows, and no CI job runs the GPU equivalence tests: the CI gate (ci.yml, #2667) runs cargo xtask ci, which includes cargo test, on Linux x64 for every push to main and every pull request; FreeBSD runs cargo test nightly, on pushes to release and on PRs that change its workflow or packaging/freebsd/; the macOS and Windows jobs build packages .github/workflows/ (ci, freebsd, packaging-lint, release, sonarcloud, windows-arm64); the gate's runner has Mesa software Vulkan drivers installed, but Gpu::new refuses software adapters (crates/gpu/src/ctx.rs), so the CPU ↔ GPU equivalence tests skip; the linux-riscv64 release job added in 0.6.0 (#844) runs only lightcraft-cli --version under QEMU; #1148 (open, not merged; #2667 names it superseded) has the macOS and Windows test legs and a LIGHTCRAFT_GPU_ALLOW_SOFTWARE opt-in that runs the GPU tests on lavapipe, Metal and WARP a regression that only shows on macOS or Windows, or only on a GPU, reaches users; cargo xtask ci runs only on Linux in CI 6–12 target-app-parity
10 B Per-model verification thin, plus cheap per-body decode fixes: 557 of 1,446 Adobe models verified (39%), ~86% of photographers on a verified body; Nikon 5 bodies verified, Canon 34, Leica 14 (the Q3 by a recorded check of 38 private DNGs). Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 regression (#702); F828 SRF now discovered and imported as preview-only, sensor decoding still missing, DSLR-A200…A390 / A850 12-bit ARW (#535) measured: raw/coverage.tsv per-body bugs (levels, crops, WB) surface only from users 25–45 raw-parity
11 Enhance: Raw Details, Super Resolution; AI denoise without shippable weights or X-Trans ENH rows 0%; denoise Bayer only, RawNIND weights GPL and opt-in Fujifilm users get no AI denoise; nobody gets it out of the box 30–60 + model decision raw-parity
12 Content-aware / generative remove, detect distractions, reflections removal REM rows 75%; patch synthesis missing cleanup work goes back to Photoshop 25–45 (+ generative model) target-app-parity
13 Lightroom catalog migration is one-way and approximate: unsupported Adobe profiles / AI settings archived, history kept as data; main-file pages read on demand up to 16 GiB, with dense-table and WAL bounds retained (#798); offline record counts and destination are now shown in a lasting import result; catalog VIDEO records match the Videos filter, with older classification repaired on reimport; before an import the app asks whether the catalog merges into the open library or goes into a new library (opt-out in Settings, #2672) lightroom-catalog-import.md switching users lose some looks 8–12 file-format-parity
14 HDR display (EDR / Windows HDR); the headroom limit and SDR rendition from crs XMP (the HDR switch, HDREditMode, is read). HLG AVIF export done; the display's SDR/HDR decision is tested, blocked on eframe's 8-bit surface HDR rows 70%; LR-VIEW-HDR-DISPLAY ⬜ HDR editing works blind on HDR monitors 12–25 hardware-parity
15 Print module: identity plate / watermark / photo info overlays, Cut Guides for the packages, Custom Package Auto Layout, printer-driver Page Setup and direct printing, JPEG file output (the Print dialog writes a colour-managed PDF) LRC-PRINT rows Classic users who print 15–25 target-app-parity
16 Map view, Book, Slideshow module, publish services LRC-MAP / BOOK / SS rows ⬜ Classic users; publish services matter for Flickr / SmugMug workflows 60–100 target-app-parity
17 Tethered capture ⬜ studio photographers 25–45 + cameras hardware-parity
18 Video: playback, trim, global edits, export VID rows 0% users who mix stills and clips 25–45 target-app-parity
19 Plug-in ecosystem: no Lightroom SDK equivalent (export / publish plug-ins, metadata plug-ins) ⬜ pro workflows built on plug-ins (LR/Enfuse, Jeffrey's exporters, Negative Lab Pro) 40–80 + owner decision target-app-parity
20 Long-tail raw formats: compressed SRW, ARQ pixel shift, IIQ, Hasselblad compression, X3F, KDC, DCR, GPR, old FinePix (MRW decodes since 2026-10-18; ERF and MEF since 2026-10-12; CRW since 2026-10-13, the PowerShot G5 anomaly and pre-CIFF compacts remain preview) raw-parity.md small user share each 35–75 raw-parity
21 Localization: 10 of 16 shipped languages partial (de, es, fr, ja, uk now 97–99% of catalog messages and id every message but What's New, awaiting native review; pt-BR, ru, zh-hans, zh-hant 74–92%), lower-layer errors English; Hindi, Arabic, Korean, Vietnamese absent; no RTL or complex shaping; exports add Thai glyphs only (#2052), with horizontal mark positioning still incomplete and Arabic fallback deferred until joining and bidi exist measured from catalogs non-English users see mixed English 40–70 (shipped to full); 135–239 (four new) localization-parity
23 Accessibility: AccessKit tree audited by a test; canvas handles, spots and mask pins by keyboard; left: brush and red-eye by keyboard, a session with a real screen reader LR-BEHAV-ACCESS 🟡 blind and low-vision users 2–4 ui-parity
22 Pen pressure on macOS / Linux tablets (brush size and flow follow pressure where winit reports a pen force), OS monitor profiles on Windows (Linux reads colord's, macOS the screen's), follow-window colour on Linux / Windows hardware-parity.md brush users with tablets; multi-monitor setups 11–22 hardware-parity
24 Brush live preview and masking feel (#517, #518); Feather / Edge mask refinement (15.5) issues masking feels slower than Lightroom; extreme brush segments now skip the intermediate dabs 8–12 ui-parity
25 File writes: AVIF import; Render to DNG as a catalog command (lossy DNG and the Render to DNG export format landed) (PSD export landed with Edit in PhotoCraft, #953) file-format-parity.md interchange edge cases 19–34 file-format-parity
26 Performance at scale: flags, ratings and edits are written and synced to the catalog log by a thread of its own, so a key never waits for a busy drive (a 300 ms sync: 0.2 ms per flag, was 305 ms), and a write that fails is held, retried and counted as unsaved, never dropped (PERF-STEP-STALL); 85k-photo library opens in 1.7–4.7 s; 100k grid test pending; colour NR at half resolution; at 100k photos stack and album lookups are indexed (an "In a Stack" or "Album" smart album counts in ~14 ms, was 0.6–2.7 s); culling a wedding: first photos of a 2,933-raw folder in 0.9 s and camera JPEGs ready ahead of each step, but a full-size 45 MP camera JPEG still takes ~150 ms to decode (held-arrow stepping at a 2,560 px loupe can outrun it), the 100 % view of the raw itself still takes ~5 s to render (the camera JPEG is shown at 100 % meanwhile, ~0.9 s), and browsing or importing still reads and hashes every raw whole (~10 ms a file on SSD, ~1 s a 25 MB file from a NAS); the loupe uses built previews and photos are prepared ahead and the whole view in the background (face detection, AI masks and denoise are not yet part of that preparation) ROADMAP log, gpu-pipeline.md large-catalog users 15–30 target-app-parity
27 Windows / Linux runtime coverage: the Windows x64 installer is verified on Windows 11 (0.9.1: install and upgrade end on the completion page, a custom folder is kept, Launch works, removal is clean), but uninstalling from Settings → Apps ends with no confirmation (#892, #931); GPU path proven on Apple and on one AMD GPU on Linux (Radeon 8060S, Vulkan); NVIDIA, Intel and DX12 unverified; Windows DirectComposition VRR workaround implemented, monitor verification pending; the Linux riscv64 tarball (new in 0.6.0, #844) is cross-compiled and never run beyond a QEMU smoke test ROADMAP, issues, release.yml non-Mac users find bugs first 13–27 hardware-parity
29 HDR merge leftovers (LR-MERGE-HDR): AI Denoise doesn't run on merged (linear) DNGs; JPEG brackets are treated as linear (no camera response curve); every frame is held as 32-bit float, ~0.86 GB per 67 MP frame (half-float frames would halve it) tested on Sony A7R VI brackets 2026-10-14 (merge.md); Lightroom's Denoise supports HDR DNGs HDR shooters who denoise their merges, merge JPEGs, or merge 7+ frames of high-resolution raws on 16 GB machines 10–20 (+ denoise linear-RGB contract, gap 11) merge
28 Small Classic shortcuts and keys: Tab / ⇧Tab (hide the side panels / all panels; Tab moves the keyboard focus here) and F5. Rating [ ], flag cycling (`), \ (Filter Bar), ⌘L (Enable Filters), ⌘⌥⇧C / ⌘⌥⇧V (Copy / Paste Metadata), F1 help, F6 / F7 / F8 (filmstrip, left panel, right panel) are bound; the grid-cycle key is shown beside both grid rows in View (#2500) ui-parity Classic muscle memory 2–4 ui-parity
30 Multi-select metadata editing in the UI: photo.setMeta already iterates all selected photos, so the engine applies changes to every target. The Info panel's Show Metadata For choice (Target Photo / Selected Photos) shows the values the selected photos share and marks each field where they differ as Mixed; the All Metadata dialog still shows one photo's values with no "mixed" indication standard in Lightroom Classic's multi-select Info panel anyone editing metadata on a batch (copyright, keywords, custom fields) 1–3 ui-parity

Beta-blocking gaps (B) total ~230–430 h; with the stability and platform work they need around them, ~340–600 h to beta (incl. 40–80 h for the ~92 open core-path user issues) (see target-app-parity.md).

Gaps by kind

  • Feature: 5, 8, 11, 12, 13, 15, 16, 18, 19, 29
  • UI / UX: 4, 23, 24, 28, 30
  • File format: 2, 6, 7, 10, 13, 20, 25 (raw ones in raw-parity.md)
  • Hardware: 14, 17, 22, 27
  • Localization: 21
  • Quality / fidelity: 1, 3
  • Stability and platforms: 9, 26, 27

Closed recently (kept for calibration)

  • 2026-10-29: the import review has a remembered 80–320 pt thumbnail-size slider, density-aware previews and bounded visible retention (#2237, part of #2056). Small sources do not reconstruct the same preview job every frame. The pre-import loupe and keyboard culling remain open in #2056; no ranked gap or headline feature count closes.

  • 2026-10-22: saving XMP preserves foreign namespaces even when they use lc: or xmp: prefixes; importing them no longer interprets their properties as native settings or ratings. Parser, merge and save-command regressions cover the correction; no ranked gap or readiness estimate changes.

  • 2026-10-22: PSD/PSB imports and metadata readers now expose embedded EXIF resource 1058 and XMP resource 1060 for capture dates, GPS and All Metadata (#2519); imported PSD edit copies retain their capture date, and the removeLocation Original-export guard sees embedded PSD/PSB GPS. No ranked gap or headline feature count closes.

  • 2026-10-22: GPX track points decode XML character references in coordinate attributes and element text before parsing. Malformed or undeclared references return an error; CDATA stays literal. Track positions and timestamps no longer silently change or lose points.

  • 2026-10-20: the import review album picker follows the sidebar folder hierarchy and searches album paths (#2362, LR-IMP-ADD-DIALOG), retaining None/New album and existing destination IDs. This closes a usability subgap; no ranked gap or headline feature count closes.

  • 2026-10-12: original-pixel detail at 100% (gap 4, LR-VIEW-ZOOM, #323): soft proofing gets zoom windows like Detail, Before/After, Compare, Reference and mask overlays; 1:1 tested equal to the full-size export.

  • 2026-10-11: Sony ILCA-99M2 profile.

  • 2026-10-11: Nikon edge bars on bodies without CropArea (#976, #914): 36 model / size / depth groups measured.

  • 2026-10-10: Nikon lossy-after-split NEF (#633), ORF 12.8 bpp (#651), HDR gain map / PQ AVIF / float TIFF export (#646), activity stack (#345), JPEG XL export (#247).

  • 2026-10-09: EOS R7 C-RAW with an odd-height QP map (a45c2b66; part of gap 2), HEIC in release builds (#609), Samsung uncompressed SRW (#459), contact-sheet PDF (#563), Ukrainian UI (#564), Sony ILCE-7CR profile.

  • 2026-10-08: CR3 CRX lossless and C-RAW (#279), Fujifilm compressed RAF (#282), AI RAW denoise (#384), Lightroom catalog import (#218), German, Spanish, Russian UI.

  • 2026-10-07: RW2 every format (#215), faces recognition in pure Rust (#174).

  • 2026-10-05: compressed NEF (#86). The older history is in ROADMAP.md.

Revision history

Date Change Summary
2026-10-27 minor Gap 26: the loupe shows built previews (they were rendered again), photos ahead and the whole view are prepared in the background with settings per mode; gap not closed
2026-10-27 minor Gap 26: culling speed measured and improved (progressive Local browse, camera previews made ahead, a CR3's smallest covering JPEG, camera JPEG first at 100 %); what is left listed; gap not closed
2026-11-02 minor PERF-STEP-STALL: the catalog log is written and synced off the UI thread, so stepping and flagging never wait for the drive (a 300 ms sync: slowest flag 0.2 ms, was 305 ms); a failed write is held and retried, counted as unsaved, never dropped; gap 26 not closed
2026-10-28 minor Gap 8 shrinks: the desktop app offers lensfun's lens database (CC BY-SA, pinned) once on the first launch and downloads it on request and looks the library's raws up when it arrives; validation, a profile picker and own calibration remain
2026-10-23 patch Gap 8: the Sigma 10–18 mm on the ILCE-6700 gets a measured lens exception at 10–10.5 mm (0.55 px worst over 99 photos, was about 1.4 px)
2026-10-29 minor Import thumbnails render when the camera JPEG is too small, retaining preview-only/error fallbacks; import review: remembered thumbnail size and density-aware previews (#2237, part of #2056); pre-import loupe/keyboard culling remains open; no ranked gap closes
2026-10-24 minor Gap 8: lens profile work is tracked in #329; hours unchanged
2026-10-25 patch Import Copy and Move report rename-sequence exhaustion per file without losing earlier transfers; no ranked gap or readiness changes
2026-10-25 patch ISO timestamp parsing rejects incomplete or trailing clock/fraction text; XMP falls back to a valid capture-time property and GPX counts malformed times as untimed; ranked gaps and readiness unchanged
2026-10-24 minor Gap 8: lens profile work is tracked in #329; hours unchanged
2026-10-25 patch LR-META-EXIF-FULL rejects extra clock components rather than projecting a plausible prefix; valid DateTimeDigitized, IFD0 DateTime and XMP fallbacks remain available, raw tag values stay visible, and the status / feature counts do not change
2026-10-25 patch JPEG ICC APP2 chunks are validated as a complete, uniquely numbered sequence before colour conversion; ranked gaps and readiness counts unchanged
2026-10-29 minor Gap 5: Object / Describe masks are enlarged along the photo's edges, so a raised Exposure no longer outlines the subject (#3272); the gap (heuristic Subject / Sky / People, no model mirror) and its hours are unchanged
2026-10-13 minor Gap 20: Canon CRW decoded (clean-room derivation; EOS and G1/G6 verified pixel-exact; G5 preview) — removed from the long-tail list; hours 40–80 → 35–75 after merging main (MRW)
2026-10-24 minor Gap 28: ⌘⌥⇧C / ⌘⌥⇧V (Ctrl+Alt+Shift+C / V on Windows and Linux) copy and paste metadata; no key leaves the entry; hours unchanged
2026-10-24 minor Gap 30 shrinks: the Info panel's Show Metadata For (Target Photo / Selected Photos, view.infoSelectedPhotos) shows the selected photos' shared values and marks fields that differ as Mixed; the All Metadata dialog remains; 1–3 h (was 4–8)
2026-10-24 patch Typed GPS input now validates the complete coordinate expression, rejecting unknown text, wrong-axis or conflicting hemispheres, and invalid DMS minutes/seconds before changing metadata; ranked gaps, estimates and readiness counts unchanged
2026-10-24 patch LR-EXP-METADATA: Original export refuses embedded GPS or nonblank place names when Remove Location Info is on; ranked gaps, estimates and readiness unchanged
2026-10-24 patch Gap 9 corrected: since #2667 the CI gate runs cargo xtask ci (so cargo test) on Linux x64 for every push to main and every pull request; FreeBSD tests now run nightly, on release and on FreeBSD packaging PRs; macOS, Windows and the GPU equivalence tests still have no CI run (#1148 open but superseded by #2667's Linux-only gate); rank and 6–12 h estimate unchanged
2026-10-23 minor Gap 13: before a Lightroom catalog import the app asks whether it merges into the open library or goes into a new one (opt-out in Settings, #2672); hours unchanged
2026-10-22 patch Gap 28: View shows the current grid-cycle key beside Photo Grid and Square Grid (#2500); no new binding and no change to the remaining shortcut gap or hours
2026-10-22 minor Gap 15: the Print page setup and PDF gain the Japanese photo and postcard paper sizes (L判 89×127, 2L判 127×178, KG判 102×152, はがき 100×148, 往復はがき 148×200, 六切 203×254, 四切 254×305, 名刺 55×91 mm), by their customary names; gap not closed
2026-10-22 minor #2052: Thai glyphs added to shared export text; gap 21 retains Thai mark positioning, Arabic joining and bidi limits. No ranked gap closes; estimates unchanged
2026-10-24 minor LR-LIB-SMARTALBUM: relative-date views and source totals expire on the session-clock minute, including nested smart albums; ranked gaps and readiness counts unchanged
2026-10-24 patch LR-LIB-VIRTUALCOPY: default numbering exhaustion is an actionable validation error before batch mutation; explicit names bypass suffix allocation; ranked gaps and readiness counts unchanged
2026-10-23 minor Gap 8 narrows: Sony ILCE-6700 (APS-C) in-file distortion through the ILCE-7RM6 conversion, knots spanning its own crop (30 photos, 3 lenses; 0.73 px worst except 1.38 px at 10 mm); the gap stays open (no lens database), hours unchanged
2026-10-22 patch XMP namespace-prefix collisions no longer overwrite foreign metadata; ranked gaps and estimates unchanged
2026-10-22 patch Issue #2519: PSD/PSB EXIF resource 1058 and XMP resource 1060 now use the common metadata extractor, and .psb files are accepted by import; no ranked gap or readiness count changes
2026-10-22 minor Gap 30 added: multi-select metadata editing — Info panel and All Metadata dialog show one photo's values with no mixed-value indication
2026-10-22 patch LR-MAP-TRACKLOG: GPX coordinate attributes and point text decode XML references; malformed / unknown references return errors; CDATA remains literal; ranked gaps unchanged
2026-10-21 minor Gap 28 shrinks: F8 shows / hides the right panel (view.rightPanel); rating [ ], flag cycling and F1 help were already bound, so the entry lists only filter-bar keys, Tab / ⇧Tab and F5; 2–4 h (was 4–8)
2026-10-20 minor #2362: import destination albums use the shared hierarchy/search picker instead of a flat name list; existing import workflows retained; ranked gaps/readiness counts unchanged
2026-10-19 minor Gap 9 shrinks: .github/workflows/ci.yml runs cargo xtask ci on Linux x64 for every PR and push to main; macOS and Windows remain
2026-10-19 minor Gap 21: Indonesian (id) shipped with every catalog message but What's New; four new languages left (Hindi, Arabic, Korean, Vietnamese), 135–239 h
2026-10-19 minor Gap 27: the released 0.9.1 x64 MSI is tested on Windows 11 Pro 24H2 (a fresh install and an upgrade from 0.8.0 end on the completion page, so #790 is fixed; the upgrade keeps a custom folder; Launch starts the app; removal leaves nothing behind); uninstalling from Settings → Apps still ends with no confirmation (#892, #931); hours unchanged
2026-10-18 patch Gap 1: six X-Trans CMOS 4 Fujifilm bodies (X-T30 …) take the X-T3's spectral matrices instead of the neutral matrix
2026-10-15 minor Gap 15 shrinks: the Page panel's Page Options (Page Numbers, Page Info, Crop Marks) print on Single Image and Contact Sheet pages; identity plate, watermark, photo info, Cut Guides, direct printing and JPEG output remain
2026-10-14 minor Gap 4 follow-up: the diagnostic overlays (Visualize Spots, Point Color range, HDR range) render zoom windows too; soft proofing runs on the GPU
2026-10-12 minor Gap 10: SR2/SRF folder discovery fixed; F828 imports as preview-only. Sensor decoding remains a gap; readiness scores unchanged
2026-10-23 minor Gap 28: ⌘L / Ctrl+L (Library ▸ Enable Filters) puts the filter aside and brings it back as it was; the entry drops ⌘L; hours unchanged
2026-10-15 minor Gap 28: \ shows / hides the Filter Bar in the grids; the entry drops [ ] (#509), flag cycling (#1490) and F1 (Help), already on main; ⌘L is the filter-bar key left; hours unchanged
2026-10-12 minor Measured Contrast and Blacks calibration; Whites and broader fidelity remain open
2026-10-16 patch Gap 13: catalog VIDEO records match the Videos filter, including after reimport of older records (#1940, filter issue only). Video playback, frame previews and folder-sync discovery remain outside this repair; no gap closed or counts changed.
2026-10-14 minor Gap 26: at 100k photos stack and album lookups are indexed, so an "In a Stack" or "Album" smart album counts in ~14 ms (was 0.6–2.7 s); open time unchanged
2026-10-14 minor Gap 10: Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (coverage.py 515 → 545 verified); sensor verification does not establish colour parity (#1277)
2026-10-14 minor Gap 21: de, es, fr, ja and uk catalogs filled (97–99%); 55–90 h → 40–70 h
2026-10-14 minor Gap 10: 17 uncompressed Olympus and Samsung models verified on one CC0 file each (254 models)
2026-10-14 minor Gap 29 added: the HDR merge, tested on real 67 MP brackets, keeps moving clipped highlights and merges in batches now; AI Denoise on merged DNGs, JPEG response curves and per-frame memory remain
2026-10-14 minor Gap 4 closed: soft proofing at 1:1 renders a window (mask overlays already did); 1:1 pixels tested equal to export; only diagnostic overlays (Visualize Spots, Point Color range, HDR range) stay whole-frame
2026-10-13 patch Gaps 10, 22 and 23 each had two rows (two branches edited them; the merges kept both): the older copies are removed, and cargo xtask parity (in ci) now fails when a gap number is listed twice
2026-10-13 minor Closed recently: Sony ILCA-99M2 profile
2026-10-14 minor Gap 10: 21 Sony and Leica models verified on one CC0 file each (258 models)
2026-10-12 minor Gap 20 shrinks: Mamiya MEF decodes (vendor/mef.rs) and is imported; gap 10 count 240 → 241
2026-10-12 minor Gap 20 shrinks: Epson ERF decodes (vendor/erf.rs), 3 bodies verified; gap 10 count 237 → 240
2026-10-13 minor Gap 28: F6 (filmstrip) and F7 (left panel) are bound, so the entry no longer lists them as missing; hours unchanged
2026-10-20 patch Gap 24: extreme brush segments skip the intermediate dabs; the gap stays open
2026-10-11 minor Gap 10 shrinks: Leica Q3 verified by a recorded check of 38 private DNGs (236 models verified; Leica 14); gap not closed
2026-10-12 minor Camera-look deep shadows follow the JPEG toe (#1307); camera calibration remains partial
2026-10-12 minor Gap 23 (accessibility): crop handles, spots, gradient handles and mask pins by keyboard; 4–8 h → 2–4 h; gap not closed
2026-10-12 minor Gap 23 (accessibility): the AccessKit tree audit and its fixes landed; what is left is canvas placement by keyboard and a real screen-reader session; 10–20 h → 4–8 h; gap not closed
2026-10-12 minor Gap 22: Linux previews use the display profile from colord
2026-10-12 minor Gap 27: the CPU ↔ GPU equivalence suite passes on an AMD Radeon 8060S (Vulkan, Linux), the first non-Apple GPU verified; 15–30 h → 13–27 h; gap not closed
2026-10-12 minor Gap 25: Render to DNG lands as an export format; lossy DNG and AVIF import remain
2026-10-12 minor Gap 10 shrinks: the Hasselblad Lunar (a Sony NEX-7 ARW under its own Make) decodes and is verified, 236 models (#1275)
2026-10-12 minor Gap 14: HLG AVIF export (hdrTransfer) and a tested SDR/HDR display decision
2026-10-12 minor Gap 15: printer colour management lands (printer / paper ICC profiles or Managed by Printer, intent, black point compensation, print adjustment, print sharpening, 16-bit ICC-tagged PDF with an output intent); page overlays and direct printing remain
2026-10-12 minor Gap 15 shrinks: File → Print… is a Print dialog with the four layout styles, cell editing, image settings, guides, page setup and templates; printer colour management, page overlays and direct printing remain
2026-10-12 minor Gap 15: the Print module's layout styles, cells, image settings, templates and PDF are print.* commands (CLI, control, MCP); the Print view, printer colour management and page overlays remain
2026-10-12 minor Gap 15: print pages compose in the engine (fit / Zoom to Fill, Rotate to Fit, border, stroke, background) with an interim PDF writer; commands and UI still to land
2026-10-12 minor Gap 15: the Print layout model (paper sizes, margins, grid, package cells, Auto Layout packer, snapping) is in the engine; commands, composition and UI still to land
2026-10-12 minor Gap 25: lossy DNG export (DNG compression 34892) lands; AVIF import and Render to DNG as a catalog command remain
2026-10-12 minor Gap 22: pen pressure scales brush size and flow (Brush ▸ Pen Pressure) where winit reports a pen force
2026-10-12 minor Gaps 2 and 10 shrink: 22 more CR3 bodies verified on one CC0 file each (every CC0 CR3 on raw.pixls.us decodes), EOS R6 Mark III white balance read; Canon 34 verified, 259 models, ~59% on a verified body; CR3 gain +6.2 → +0.3 points
2026-10-12 minor Gap 10 shrinks: 117 Nikon and Fujifilm bodies verified on one CC0 file each (354 models, ~58% on a verified body); COOLPIX 5700 / 8400 / 8800 decode wrongly; #702 is fixed
2026-10-11 minor Nikon edge bars on bodies without CropArea fixed for 36 model / size / depth groups (#976, #914; survey in #1053); gap 10 notes the survey and the pending bodies; gap not closed
2026-10-11 minor Reviewed against main after 0.6.0: CR3 entry names the CRX variants crx.rs still refuses and the R7 / R6 Mark II C-RAW state (decoding, not verified, so the measured numbers don't move); CI and Windows / Linux entries note the riscv64 release build (#844); R7 fix added to Closed recently. Panel docking (UI.9, #869) closes no listed gap
2026-10-11 minor Camera colour entry: raws with a black-and-white camera JPEG take its tone curve (#577); gap not closed
2026-10-11 minor Gap 4: Compare and Reference views get zoom windows per pane (1:1 was a 2,560 px render magnified); overlays and soft proofing remain, 8–15 h → 4–8 h
2026-10-10 minor Compressed ORF entry links the two Olympus patents that describe the method
2026-10-11 minor Gap 14: crs:HDREditMode is read from XMP (#663); the headroom limit and SDR rendition still aren't
2026-10-11 minor Gaps 2 and 10 shrink: EOS R5 Mark II CR3 decodes and is verified (fourth CR3 body, Canon 12 verified, 235 models, ~42% on a verified body); CR3 gain +6.5 → +6.2 points
2026-10-10 minor RAW gaps re-measured model by model (raw-parity.md): CR3, compressed ORF and verification entries carry measured shares; HE NEF no longer a beta blocker (+1.1 points for 20–40 h); verification entry absorbs the cheap per-body fixes (#769, #702, A200 series)
2026-10-10 minor Beta total raised to 340–600 h to include the ~92 open core-path user issues
2026-10-10 major Created from the checklist's Top gaps (whose items 1–2 had been duplicated by a merge) and ROADMAP's Where we stand; re-ranked; added evidence, impact, hours and beta flags; added platform-CI, lens-database, HE NEF and localization gaps
2026-10-13 minor Gap 8 narrows: Nikon Z 6II / Z 8 / Z f NEFs use the camera's in-file distortion correction (docs/nikon-lens-corrections.md)
2026-10-12 minor Catalog migration reports offline records as additions, keeps the destination/result readable, skips missing-original probes and stages only masters needed for virtual copies. Approximate rendering and database/table limits remain.
2026-10-13 minor Catalog import review: offline Local records join All Photos, redundant operation/settings copies removed, and result invalidation across library switches covered. Approximate rendering and database/table limits remain.
2026-10-14 patch LRC-LIB-CATALOG-IMPORT: reconciled with upstream phased progress, cancellation kinds, WAL-only import, translations and current progress tracking. Status and summary counts unchanged.
2026-10-10 minor Gap 8: Sony ILCE-7RM6 in-file distortion added next to the ILCE-7RM4A's, its lateral CA parsed but off by default (docs/sony-ilce7rm6-lens-corrections.md); the gap stays open (no lens database), hours unchanged
2026-10-14 minor Gap 10 shrinks: 97 Canon CR2, Pentax and Ricoh models verified on one CC0 file each (334 models, ~50% on a verified body)
2026-10-14 minor Gap 10: 515 of 1,446 models verified (36%), ~84% of photographers on a verified body (integration batch 36)
2026-10-24 patch LR-META-XMP: sidecar keywords are written as names in dc:subject and paths in lr:hierarchicalSubject, so other DAMs can search them; ranked gaps and estimates unchanged
2026-10-20 minor Opt-in Windows DirectComposition preference added; VRR hardware verification pending; parity counts unchanged
2026-10-25 minor Gap 1: first chart-calibrated model, the Canon EOS D60 (ColorChecker Classic under tungsten and sun, crates/raw/src/chart_table.rs); the gap stays open, hours unchanged