Add the Identity screen

A third top-level screen beside the library and develop, because naming a
cluster and pulling a stranger out of it are tasks with their own rhythm
and need the whole window.

The screen is designed around the clustering being wrong, which is
FR-CULL-10 rather than pessimism: grouping over-merges on siblings, on
parents and children, and on the same person a decade apart. So Split off
sits next to Confirm all rather than behind a menu, the confirm/reject
pair is on the face itself, and a group the system found is drawn
differently from a person the user has vouched for.

Splitting rejects before it confirms. Without that the next clustering
pass suggests the face straight back and the user's correction becomes an
argument they keep having.

Face crops come from the proxies the grid already built, one decode per
image rather than per face -- a group photograph holding six faces of one
family is one JPEG.

Where the calibration is not fitted the screen says confidence is
unavailable instead of printing a percentage that looks measured, which
is FR-CULL-9's rule at the point it becomes visible.

The verdict controls use drawn icons, not tick and cross characters:
ui/icons.slint exists because those render as tofu on Android.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 21:05:41 +02:00
co-authored by Claude Opus 5
parent 2ac069a6b3
commit 10b10569e2
8 changed files with 1647 additions and 42 deletions
+25
View File
@@ -23,6 +23,8 @@ mod activity;
mod collections_ui;
mod derived_sync;
pub mod faces;
pub mod identity;
mod identity_ui;
mod develop;
mod export;
mod gradient;
@@ -871,6 +873,9 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
// needs it too: a paste onto "the selection" reads the selection from
// here, and that wiring happens once the develop session exists.
let collections = collections_ui::CollectionsController::new(activity.clone());
// The People screen's own state: which person is open, and which of their
// faces are ticked for a split.
let identity = std::rc::Rc::new(identity_ui::IdentityController::new());
// Launch screen: shown when there is nothing to display — no local paths
// and no configured library. A user who has already signed in and chosen
@@ -948,6 +953,26 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
// and thumbnail workers use.
move || lib_session.session(),
);
// The People screen reads the same catalog and the same thumbnail
// store the grid does — its face crops come from the proxies the
// grid already built, which is the whole reason face indexing is
// affordable (FR-CULL-8).
let lib_store = library.clone();
identity_ui::wire(
&window,
identity.clone(),
library.catalog(),
move || {
let (_, session) = lib_store.session()?;
dr_thumbs::ThumbStore::open(&library::thumbs_dir(
&session.server,
&session.user_id,
))
.ok()
.map(std::rc::Rc::new)
},
);
}
let weak = window.as_weak();