Merge: face quality gates, and identity filters that combine
Build and test / Desktop (Linux) (push) Successful in 32m56s
Build and test / Layer separation (push) Successful in 50s
Traceability / Requirement traces (push) Successful in 39s
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Android (aarch64) (push) Failing after 52m43s

Two things the People screen was missing.

Faces were being indexed at any size and any sharpness — 40 pixels on the box
and no blur gate at all — so most of what the library held was background
strangers and motion-blurred passers-by, and the blurred ones were quietly
bridging unrelated clusters. Both floors are now measured on the real library
with `face_index --quality` rather than guessed: 64 source pixels across the
aligned crop, and a contrast-invariant sharpness of 0.020.

And the grid could only ever be narrowed to one person, which cannot express
"the pictures the two of them are in together". The filter now holds a set with
a union/intersection mode, built a person at a time from the Identity screen and
taken apart chip by chip on the filter bar.

fmt, clippy -D warnings and the full workspace suite pass on the merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 22:23:37 +02:00
co-authored by Claude Opus 5
10 changed files with 896 additions and 108 deletions
+15 -9
View File
@@ -6,7 +6,7 @@ import { CropOverlay } from "crop.slint";
import { HistoryPanel, HistoryRow } from "history.slint";
import { LaunchScreen } from "launch.slint";
import { IdentityScreen, IdentityPerson, IdentityFace } from "identity.slint";
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow } from "library.slint";
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChip } from "library.slint";
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
import { CollectionsPanel, CollectionRow, OfflinePrompt } from "collections.slint";
import { HistogramPanel, HistogramView } from "histogram.slint";
@@ -14,7 +14,7 @@ import { SettingsPage } from "settings.slint";
import { ImportPage } from "import.slint";
import { StatusBar, InfoPanel } from "develop.slint";
export { LibraryCell, TimelineBar, CollectionRow, ActivityRow, HistogramView }
export { LibraryCell, TimelineBar, CollectionRow, ActivityRow, HistogramView, PersonChip }
export { ViewMode, GradientHandle, HandleRole, SpotHandle, SpotRole }
export component AppWindow inherits Window {
@@ -366,7 +366,7 @@ export component AppWindow inherits Window {
in property <bool> identity-selected-ignored: false;
callback identity-toggle-show-ignored();
callback identity-ignore-person(int, bool);
callback identity-show-photos(int);
callback identity-show-photos(int, bool);
callback identity-index();
callback identity-stop-indexing();
callback identity-check-coverage();
@@ -542,9 +542,12 @@ export component AppWindow inherits Window {
/// the two arguments is -1, saying which axis was *not* meant.
callback library-judged(int, int);
/// Whose photographs the grid is narrowed to. Empty when it is not.
in property <string> library-filter-person-name;
callback library-filter-person-cleared();
/// Whose photographs the grid is narrowed to, one entry each.
in property <[PersonChip]> library-filter-people;
/// Whether those people are an intersection rather than a union.
in property <bool> library-filter-people-all: false;
callback library-filter-person-cleared(int);
callback library-filter-people-mode-toggled();
in property <int> library-filter-min-rating: 0;
in property <bool> library-filter-unjudged: false;
in property <int> library-filter-flag: 0;
@@ -1199,6 +1202,7 @@ in property <bool> panel-visible: true;
confirm-all() => { root.identity-confirm-all(); }
split-picked() => { root.identity-split-picked(); }
compact: !root.expanded;
photos-filtered: root.library-filter-people.length > 0;
regrouping: root.identity-regrouping;
regroup-status: root.identity-regroup-status;
ignored-count: root.identity-ignored-count;
@@ -1206,7 +1210,7 @@ in property <bool> panel-visible: true;
selected-ignored: root.identity-selected-ignored;
toggle-show-ignored() => { root.identity-toggle-show-ignored(); }
ignore-person(id, on) => { root.identity-ignore-person(id, on); }
show-photos(id) => { root.identity-show-photos(id); }
show-photos(id, add) => { root.identity-show-photos(id, add); }
recluster() => { root.identity-recluster(); }
index-faces() => { root.identity-index(); }
stop-indexing() => { root.identity-stop-indexing(); }
@@ -1432,7 +1436,8 @@ in property <bool> panel-visible: true;
root.library-remove-from-collection();
}
filter-person-name: root.library-filter-person-name;
filter-people: root.library-filter-people;
filter-people-all: root.library-filter-people-all;
filter-min-rating: root.library-filter-min-rating;
filter-unjudged: root.library-filter-unjudged;
filter-flag: root.library-filter-flag;
@@ -1463,7 +1468,8 @@ in property <bool> panel-visible: true;
root.library-filter-unjudged-changed(on);
}
filter-flag-changed(f) => { root.library-filter-flag-changed(f); }
filter-person-cleared() => { root.library-filter-person-cleared(); }
filter-person-cleared(id) => { root.library-filter-person-cleared(id); }
filter-people-mode-toggled() => { root.library-filter-people-mode-toggled(); }
}
}
}