Take the whole run a shift-click names, not the part that happens to be loaded

Shift-clicking two photographs selected only the cells between them that were
in the loaded window. The grid is a window of a hundred or so over a library of
twenty thousand, and `apply_press` resolved the range against that window —
`ids[lo_row..=hi_row]`, clamped to what was there. Everything else in the run
had no id anywhere in the UI, so it was silently dropped. The user cannot see
that: the selection count is off screen along with the photographs, and the
gesture only announces itself when the drop files a dozen images instead of two
hundred.

The two ends are *ordinals*, and only the catalog knows what lies between them.
`read_ids_span` asks it, through the same predicates, the same rating filter and
the same ordering the window itself is read with — an ordinal names a photograph
only relative to an ordering, so a run taken through any other one is a run
through a different library. That ordering is now a constant, `GRID_ORDER`,
shared by the window, the trash's own order beside it, and the run: capture time
first, with the file name breaking ties and nothing more. A card written by two
cameras interleaves names that have nothing to do with each other, and what
"everything between these two" means to a photographer is a stretch of an
afternoon.

The query is reached through a closure handed to `CollectionsController` at
wiring time rather than a catalog handle, because the scope and the filter that
bound the run belong to the grid's controller. `apply_press` stays a pure
function of what it is given, which is what keeps the selection rules testable
with no library open — and the tests pass a run that reads a plain slice. Where
there is nothing to ask, the loaded window is still used: a poorer answer than
the catalog's and a far better one than a gesture that appears to do nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-24 19:50:21 +02:00
co-authored by Claude Opus 5
parent 22325415b1
commit 54f9cb54fb
4 changed files with 405 additions and 70 deletions
+5
View File
@@ -869,6 +869,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
let lib = library.clone();
let coll = collections.clone();
let lib_ids = library.clone();
let lib_span = library.clone();
let lib_session = library.clone();
collections_ui::wire(
&window,
@@ -885,6 +886,10 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
library_ui::reload(&w, &lib);
},
move || lib_ids.visible_ids(),
// What a shift-click selects: the whole run between its two
// ends, read from the catalog rather than from the hundred or
// so rows that happen to be loaded.
move |first, last| lib_span.ids_in_span(first, last),
// The trash's MOVE and DELETE go to the same account the scan
// and thumbnail workers use.
move || lib_session.session(),