diff --git a/core/dr-catalog/src/schema.rs b/core/dr-catalog/src/schema.rs index 4a05d85..c0ce138 100644 --- a/core/dr-catalog/src/schema.rs +++ b/core/dr-catalog/src/schema.rs @@ -502,7 +502,7 @@ const V12: &str = r#" -- across that replacement by box overlap. So this costs a re-fetch of the -- affected images and loses no work the user has done. -- --- The threshold is written out rather than taken from `dr_face::MIN_DETECT_EDGE` +-- The threshold is written out rather than taken from `dr_face::MIN_CROP_EDGE` -- on purpose. A migration has to keep meaning what it meant on the day it ran; -- binding it to a constant someone may raise later would silently change what -- an old catalog gets migrated to. diff --git a/core/dr-face/src/lib.rs b/core/dr-face/src/lib.rs index 96a72e8..9e16b98 100644 --- a/core/dr-face/src/lib.rs +++ b/core/dr-face/src/lib.rs @@ -43,6 +43,27 @@ pub mod embedding; pub mod naming; pub mod neighbours; +/// Smallest long edge a face crop may be sampled from. +/// +/// **A floor on the crop source, not on the detector input.** The distinction +/// is the whole of FR-CULL-8 and `docs/faces.md` §7: detection letterboxes +/// every buffer into 640×640, so its input resolution decides nothing, while +/// [`warp`] samples the 112×112 the embedder sees and so converts source +/// resolution directly into embedding quality. FR-CULL-8 requires that crop to +/// come from the native render; this is the guard that catches a caller +/// sampling from a proxy instead. +/// +/// 1025 rather than 1024 because 1024 is exactly `dr_thumbs::ThumbSize::Large`, +/// the stored proxy tier a caller is most likely to reach for by mistake, so +/// the floor has to exclude it rather than admit it. Written as a minimum so +/// the test is `edge < MIN_CROP_EDGE` with no boundary to get wrong. +/// +/// It is a coarse guard and deliberately so: whether any *individual* crop was +/// upsampled is answered exactly by `faces.crop_px` against [`ALIGNED_EDGE`], +/// and that is the number §7b measures. This only stops a whole pass reading +/// from the wrong tier. +pub const MIN_CROP_EDGE: u32 = 1025; + pub use align::{warp, Aligned112, Similarity, ALIGNED_EDGE, ARCFACE_TEMPLATE}; pub use assign::{identity_shares, RIVAL_FLOOR, TOP_MATCHES}; pub use calibrate::{Calibration, Pairs, ReliabilityBand}; diff --git a/docs/faces.md b/docs/faces.md index 5fef824..0a99b95 100644 --- a/docs/faces.md +++ b/docs/faces.md @@ -420,8 +420,10 @@ context the detector needs; CLAHE rescues underexposed frames. Both are plausibl and backlit photographs — worth trying at low priority, since a retry doubles the cost of exactly the images that yielded nothing. -Sampling is bilinear from the *source proxy*, in one step — never crop-then-warp, which resamples -twice and throws away detail the warp could have used. Pixels falling outside the source are black. +Sampling is bilinear from the **native render**, in one step — never crop-then-warp, which resamples +twice and throws away detail the warp could have used, and never from the downscaled buffer the +detector was given. The landmarks arrive in detector-input coordinates and are scaled back to +native before the warp reads a single pixel; §7 is why. Pixels falling outside the source are black. **The landmark order must match the template order.** The template above is written in the detector's own output order; if a future detector emits them differently, the template is reordered with it and @@ -465,18 +467,31 @@ drift that is otherwise invisible. ## 7. Which pixels the pipeline actually sees -FR-CULL-8 pins indexing to the FR-CULL-2 ladder: **the proxy tier, never a full decode.** The -relevant tier is `ThumbSize::Large` — 1024 px on the long edge (`dr-thumbs`). +**This section previously specified the proxy tier — `ThumbSize::Large`, 1024 px — as the buffer +both detection and cropping read. That was wrong, and §7b is the measurement that says so.** +FR-CULL-8 now separates the two, because they want opposite things: -That has a consequence worth stating in numbers rather than discovering in a clustering report. On a -1024 px proxy: - -| Face size in frame | Pixels across | What §6 receives | +| Stage | Resolution | Why | |---|---|---| -| A portrait, face fills a third of the frame | ~340 | Downsampled to 112. Ideal. | -| Two people, half-length | ~120 | Roughly native. Good. | -| A group of eight | ~50 | **Upsampled** to 112. Degraded, and usable. | -| A figure in a landscape | ~20 | At or below §4.3's floor. Rejected. | +| Source render | **native** | The only stage where more pixels exist to be had | +| Detector input | downscaled to ~640 | §4.1 letterboxes to 640×640 regardless; more is wasted CPU | +| Crop + align | **sampled from the native render** | The 112×112 is fixed, so this decides whether it holds real pixels | +| Embedding | 112×112 | §6 | + +The detector's indifference to resolution is the whole reason the split works. §4.1 fixes its input +at 640×640 and letterboxes whatever arrives, so a face occupying 2% of the frame presents at 12 px +to the model whether the buffer handed over is 1024 px or 6000 px. Feeding it native pixels buys +nothing. **Feeding the *crop* native pixels buys everything**, because §5's warp is the one place +where source resolution converts directly into embedding quality. + +What the crop receives, by face size, from a native render of a 24 MP frame (~6000 px long edge): + +| Face size in frame | Pixels across, native | Pixels across, 1024 proxy | What §6 receives | +|---|---|---|---| +| A portrait, face fills a third of the frame | ~2000 | ~340 | Downsampled. Ideal either way. | +| Two people, half-length | ~700 | ~120 | Native: comfortable. Proxy: marginal. | +| A group of eight | ~290 | ~50 | Native: real pixels. Proxy: **upsampled 2.2×**. | +| A figure in a landscape | ~120 | ~20 | Native: usable. Proxy: below §4.3's floor. | So `faces` records one column beyond catalog.md §10.1's schema: @@ -484,14 +499,12 @@ So `faces` records one column beyond catalog.md §10.1's schema: ALTER TABLE faces ADD COLUMN crop_px INTEGER NOT NULL; -- source pixels across the aligned crop ``` -`crop_px` earns its place three times over. It is the honest quality signal for the UI; it is a +`crop_px` earns its place four times over. It is the honest quality signal for the UI; it is a **feature in §8's calibration**, which FR-CULL-9 explicitly demands ("a raw cosine means something -different for every model, every population, and *every face size*"); and it is what a future -higher-resolution re-embedding pass would select on, so that pass becomes a query rather than a -re-index of everything. - -**No RAW decode is added.** Where the Large proxy is missing, the job enqueues a `Thumbnail` job at -background priority and re-queues itself, exactly as FR-CULL-8 requires. +different for every model, every population, and *every face size*"); it is what a +higher-resolution re-embedding pass selects on, so that pass is a query rather than a re-index of +everything; and it is the only way to audit whether the rule above is actually being followed — +which is how §7b found that it was not. --- @@ -525,6 +538,37 @@ Three things fall out of it that were not otherwise available: --- +## 7b. What the proxy tier actually cost, measured + +The table in §7 predicted upsampling for small faces and called it "degraded, and usable". On the +reference library of 23,531 images it was not the edge case that description implies. Reading +`crop_px` across the 18,671 faces stored under the proxy-tier implementation: + +| Source pixels across the aligned crop | Faces | Share | +|---|---|---| +| <56 (upsampled more than 2×) | 314 | 1.7% | +| 56–111 (upsampled) | 8,505 | 45.6% | +| 112–223 (roughly native) | 6,313 | 33.8% | +| ≥224 (downsampled — ideal) | 3,539 | 19.0% | + +**47.3% of every face in the library was upsampled to reach the embedder**, with `crop_px` as low +as 34 — a 3.3× enlargement — against a mean of 178. An upsampled crop does not fail loudly. It +produces a confident 512-d embedding describing detail that was interpolated rather than +photographed, and the damage appears three stages later as clusters that will not separate. + +A second effect, recorded here because it was measured and because the mechanism is **not** +established. Grouping the same runs by `face_index.source_edge` — the buffer detection ran +against — gives 0.078 faces per image at 1024 or below, against 1.82 at 2048 or better. Controlled +for file type and size (1,592 DNGs averaging 21.0 MB against 7,724 averaging 23.3 MB, same library, +same cameras), so it is not a composition artefact. But it cannot be a matter of the detector +seeing fewer pixels, since §4.1 letterboxes both to 640: a 1024 buffer and a 3072 buffer present +the same face at the same size to the model. The likeliest explanation is that the 1024 proxy is +itself a downscale of a larger preview, so the detector sees a twice-resampled image where the +larger buffer is resampled once — but that is a hypothesis, not a finding, and §12's M4 is where it +should be settled. **The crop measurement above stands on its own and does not depend on it.** + +--- + ## 8. Calibration — cosine to probability FR-CULL-9 makes this a hard requirement: no code path may threshold a bare cosine, every threshold diff --git a/docs/requirements.md b/docs/requirements.md index b238dc1..efed529 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -988,19 +988,54 @@ rejects a photograph. producing per-face a bounding box, five-point landmarks, a detector confidence, and a 512-dimension embedding. -Detection runs against the **thumbnail or proxy tier, never a full decode** (FR-CULL-2's ladder). -This is what makes indexing affordable: a library that has been browsed has already paid for its -proxies, so face indexing adds no RAW decodes that were not already happening. Where no proxy -exists, the job requests one at background priority rather than decoding inline. +**The two resolutions are separate, and conflating them is the failure this clause exists to +prevent.** Detection and cropping have opposite resolution needs, and a single buffer cannot serve +both well: + +1. **Source.** The image is rendered at **native resolution** through the full-quality path + (FR-EXP-9's pipeline, including the high-quality demosaic of FR-RAW-3). This is the same render + export uses and is deliberately not the FR-CULL-2 preview ladder. +2. **Detector input.** That render is downscaled for the detector, which fixes its input at 640×640 + regardless (faces.md §4.1). Detection gains nothing from more pixels than its own input, so the + downscale is free accuracy-wise and is what keeps the pass affordable in CPU. +3. **Crop.** Boxes and landmarks are mapped **back to native coordinates**, and the aligned crop is + sampled from the native render — never from the downscale the detector saw. +4. **Embedding.** The aligned crop is warped to 112×112 in one bilinear step (faces.md §5). + +The crop is the reason. ArcFace receives a fixed 112×112 whatever it is given, so the only question +that matters is whether those 112 pixels are real pixels or interpolated ones. Sampling the crop +from a preview means a face occupying a small part of the frame is *upsampled* to reach the +embedder, and an upsampled crop yields a confident embedding of detail that was never there — +which does not fail loudly, it degrades clustering three stages later. Measured on the reference +library under the previous preview-tier implementation: **47% of all stored faces had been +upsampled to reach 112×112**, with `crop_px` as low as 34. + +This supersedes the previous rule that detection ran against the thumbnail or proxy tier and never +a full decode. That rule was adopted for affordability and it bought exactly that, at a cost to +crop quality that was not measured until the library was large. Affordability is now met by *when* +the pass runs rather than by *what* it reads: it is background work, preempted by everything +visible, and resumable per image. + +**On a remote library this needs the original**, not FR-NC-3's byte-ranged preview — on the +reference library, 412 GB across 19,107 images rather than a range request each. So a whole-library +pass is a **transfer under FR-NC-6**: never automatic, subject to the unmetered-network and +charging constraints, and reported as the download it is before it starts rather than presented as +a local operation. An original already on the device is indexed from what is there. Nothing here +requires the original to be *kept*: it is rendered, cropped, and given back under the same rules as +any other borrowed file (ARCH §9.0a), so the pass costs transfer and time rather than permanent +disk. Detection is a job in the FR-CAT-3 queue and inherits its properties without exception: coalesced per image, interruptible, resumable across process death (FR-PLAT-AND-3), and strictly preempted by visible work (NFR-ARCH-2). A library indexes while idle or it does not index; it never competes with -the grid. +the grid. `face_index.source_edge` records the native edge each run was made at, and `faces.crop_px` +the pixels behind each individual crop, so raising the standard +later re-indexes only the images that stand to gain rather than all of them. *Acceptance:* indexing a 10k-image library completes without the grid dropping below NFR-P9's interaction target at any point, and survives being killed and restarted with no repeated work -beyond the in-flight image. +beyond the in-flight image. No face is stored whose aligned crop was upsampled beyond a stated +factor; the crop source resolution is recorded per face (`crop_px`) and is auditable. **FR-CULL-9 — Calibrated identity.** Face similarity shall be expressed as a **calibrated probability that two faces are the same person**, not as a raw embedding distance. Every threshold diff --git a/docs/traceability.md b/docs/traceability.md index 5dd53bc..d2a04b4 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -37,16 +37,16 @@ _None._ | FR-CAT-10 | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`platform/dr-plat/src/storage.rs:287`](../platform/dr-plat/src/storage.rs#L287), [`platform/dr-plat/src/storage.rs:586`](../platform/dr-plat/src/storage.rs#L586), [`platform/dr-plat/src/volumes.rs:1`](../platform/dr-plat/src/volumes.rs#L1), [`platform/dr-plat/src/volumes.rs:62`](../platform/dr-plat/src/volumes.rs#L62), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1254`](../ui/dr-ui/src/lib.rs#L1254), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1140`](../ui/dr-ui/ui/library.slint#L1140), [`ui/dr-ui/ui/library.slint:870`](../ui/dr-ui/ui/library.slint#L870), [`ui/dr-ui/ui/library.slint:924`](../ui/dr-ui/ui/library.slint#L924) | | FR-CAT-11 | [`core/dr-catalog/src/dedup.rs:1`](../core/dr-catalog/src/dedup.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:392`](../core/dr-ingest/src/lib.rs#L392), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1254`](../ui/dr-ui/src/lib.rs#L1254), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:2491`](../ui/dr-ui/src/library.rs#L2491), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | | FR-CAT-12 | [`core/dr-pipeline/src/sidecar.rs:119`](../core/dr-pipeline/src/sidecar.rs#L119) | -| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:753`](../core/dr-catalog/src/schema.rs#L753), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:457`](../core/dr-sync-nextcloud/src/lib.rs#L457), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:563`](../core/dr-sync/src/scan.rs#L563), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/collections_ui.rs:1283`](../ui/dr-ui/src/collections_ui.rs#L1283), [`ui/dr-ui/src/collections_ui.rs:2189`](../ui/dr-ui/src/collections_ui.rs#L2189), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:192`](../ui/dr-ui/src/library.rs#L192), [`ui/dr-ui/src/library.rs:241`](../ui/dr-ui/src/library.rs#L241), [`ui/dr-ui/src/library.rs:4029`](../ui/dr-ui/src/library.rs#L4029), [`ui/dr-ui/src/library.rs:4063`](../ui/dr-ui/src/library.rs#L4063), [`ui/dr-ui/src/library_ui.rs:195`](../ui/dr-ui/src/library_ui.rs#L195), [`ui/dr-ui/src/library_ui.rs:784`](../ui/dr-ui/src/library_ui.rs#L784), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:621`](../ui/dr-ui/ui/collections.slint#L621) | +| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:753`](../core/dr-catalog/src/schema.rs#L753), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:457`](../core/dr-sync-nextcloud/src/lib.rs#L457), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:563`](../core/dr-sync/src/scan.rs#L563), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/collections_ui.rs:1283`](../ui/dr-ui/src/collections_ui.rs#L1283), [`ui/dr-ui/src/collections_ui.rs:2189`](../ui/dr-ui/src/collections_ui.rs#L2189), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:192`](../ui/dr-ui/src/library.rs#L192), [`ui/dr-ui/src/library.rs:241`](../ui/dr-ui/src/library.rs#L241), [`ui/dr-ui/src/library.rs:4045`](../ui/dr-ui/src/library.rs#L4045), [`ui/dr-ui/src/library.rs:4079`](../ui/dr-ui/src/library.rs#L4079), [`ui/dr-ui/src/library_ui.rs:195`](../ui/dr-ui/src/library_ui.rs#L195), [`ui/dr-ui/src/library_ui.rs:784`](../ui/dr-ui/src/library_ui.rs#L784), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:621`](../ui/dr-ui/ui/collections.slint#L621) | | FR-CAT-1a | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:54`](../core/dr-types/src/lib.rs#L54), [`platform/dr-plat/src/storage.rs:1`](../platform/dr-plat/src/storage.rs#L1), [`platform/dr-plat/src/storage.rs:216`](../platform/dr-plat/src/storage.rs#L216), [`platform/dr-plat/src/storage.rs:46`](../platform/dr-plat/src/storage.rs#L46) | | FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) | -| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964), [`ui/dr-ui/src/library.rs:3472`](../ui/dr-ui/src/library.rs#L3472), [`ui/dr-ui/src/library_ui.rs:177`](../ui/dr-ui/src/library_ui.rs#L177), [`ui/dr-ui/src/library_ui.rs:4001`](../ui/dr-ui/src/library_ui.rs#L4001), [`ui/dr-ui/src/library_ui.rs:5021`](../ui/dr-ui/src/library_ui.rs#L5021), [`ui/dr-ui/ui/app.slint:412`](../ui/dr-ui/ui/app.slint#L412), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | +| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964), [`ui/dr-ui/src/library.rs:3488`](../ui/dr-ui/src/library.rs#L3488), [`ui/dr-ui/src/library_ui.rs:177`](../ui/dr-ui/src/library_ui.rs#L177), [`ui/dr-ui/src/library_ui.rs:4001`](../ui/dr-ui/src/library_ui.rs#L4001), [`ui/dr-ui/src/library_ui.rs:5021`](../ui/dr-ui/src/library_ui.rs#L5021), [`ui/dr-ui/ui/app.slint:412`](../ui/dr-ui/ui/app.slint#L412), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | | FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:222`](../ui/dr-ui/src/library.rs#L222), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1) | -| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1171`](../core/dr-catalog/src/schema.rs#L1171), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:136`](../core/dr-pipeline/src/sidecar.rs#L136), [`ui/dr-ui/src/collections_ui.rs:1579`](../ui/dr-ui/src/collections_ui.rs#L1579), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1734`](../ui/dr-ui/src/collections_ui.rs#L1734), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4077`](../ui/dr-ui/src/library.rs#L4077), [`ui/dr-ui/src/library_ui.rs:659`](../ui/dr-ui/src/library_ui.rs#L659), [`ui/dr-ui/src/library_ui.rs:6953`](../ui/dr-ui/src/library_ui.rs#L6953), [`ui/dr-ui/src/library_ui.rs:6964`](../ui/dr-ui/src/library_ui.rs#L6964), [`ui/dr-ui/src/library_ui.rs:6977`](../ui/dr-ui/src/library_ui.rs#L6977), [`ui/dr-ui/src/library_ui.rs:6992`](../ui/dr-ui/src/library_ui.rs#L6992), [`ui/dr-ui/src/library_ui.rs:7001`](../ui/dr-ui/src/library_ui.rs#L7001), [`ui/dr-ui/ui/app.slint:333`](../ui/dr-ui/ui/app.slint#L333), [`ui/dr-ui/ui/app.slint:547`](../ui/dr-ui/ui/app.slint#L547), [`ui/dr-ui/ui/library.slint:1246`](../ui/dr-ui/ui/library.slint#L1246), [`ui/dr-ui/ui/library.slint:1270`](../ui/dr-ui/ui/library.slint#L1270), [`ui/dr-ui/ui/library.slint:1323`](../ui/dr-ui/ui/library.slint#L1323), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:3963`](../ui/dr-ui/ui/library.slint#L3963) | -| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4470`](../ui/dr-ui/src/library.rs#L4470), [`ui/dr-ui/src/library_ui.rs:342`](../ui/dr-ui/src/library_ui.rs#L342), [`ui/dr-ui/src/library_ui.rs:416`](../ui/dr-ui/src/library_ui.rs#L416), [`ui/dr-ui/src/library_ui.rs:5688`](../ui/dr-ui/src/library_ui.rs#L5688), [`ui/dr-ui/src/library_ui.rs:5741`](../ui/dr-ui/src/library_ui.rs#L5741), [`ui/dr-ui/src/library_ui.rs:7093`](../ui/dr-ui/src/library_ui.rs#L7093), [`ui/dr-ui/ui/app.slint:345`](../ui/dr-ui/ui/app.slint#L345), [`ui/dr-ui/ui/app.slint:547`](../ui/dr-ui/ui/app.slint#L547), [`ui/dr-ui/ui/app.slint:850`](../ui/dr-ui/ui/app.slint#L850), [`ui/dr-ui/ui/library.slint:110`](../ui/dr-ui/ui/library.slint#L110), [`ui/dr-ui/ui/library.slint:1433`](../ui/dr-ui/ui/library.slint#L1433), [`ui/dr-ui/ui/library.slint:3963`](../ui/dr-ui/ui/library.slint#L3963), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | -| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1073`](../ui/dr-ui/src/collections_ui.rs#L1073), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1598`](../ui/dr-ui/src/collections_ui.rs#L1598), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1913`](../ui/dr-ui/src/collections_ui.rs#L1913), [`ui/dr-ui/src/collections_ui.rs:1973`](../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:965`](../ui/dr-ui/src/collections_ui.rs#L965), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4275`](../ui/dr-ui/src/library.rs#L4275), [`ui/dr-ui/src/library.rs:4301`](../ui/dr-ui/src/library.rs#L4301), [`ui/dr-ui/src/library.rs:4356`](../ui/dr-ui/src/library.rs#L4356), [`ui/dr-ui/src/library_ui.rs:2467`](../ui/dr-ui/src/library_ui.rs#L2467), [`ui/dr-ui/src/library_ui.rs:3863`](../ui/dr-ui/src/library_ui.rs#L3863), [`ui/dr-ui/src/library_ui.rs:4590`](../ui/dr-ui/src/library_ui.rs#L4590), [`ui/dr-ui/ui/app.slint:336`](../ui/dr-ui/ui/app.slint#L336), [`ui/dr-ui/ui/app.slint:542`](../ui/dr-ui/ui/app.slint#L542), [`ui/dr-ui/ui/app.slint:571`](../ui/dr-ui/ui/app.slint#L571), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:1213`](../ui/dr-ui/ui/library.slint#L1213), [`ui/dr-ui/ui/library.slint:2748`](../ui/dr-ui/ui/library.slint#L2748), [`ui/dr-ui/ui/library.slint:3941`](../ui/dr-ui/ui/library.slint#L3941), [`ui/dr-ui/ui/library.slint:889`](../ui/dr-ui/ui/library.slint#L889) | +| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1171`](../core/dr-catalog/src/schema.rs#L1171), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:136`](../core/dr-pipeline/src/sidecar.rs#L136), [`ui/dr-ui/src/collections_ui.rs:1579`](../ui/dr-ui/src/collections_ui.rs#L1579), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1734`](../ui/dr-ui/src/collections_ui.rs#L1734), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4093`](../ui/dr-ui/src/library.rs#L4093), [`ui/dr-ui/src/library_ui.rs:659`](../ui/dr-ui/src/library_ui.rs#L659), [`ui/dr-ui/src/library_ui.rs:6953`](../ui/dr-ui/src/library_ui.rs#L6953), [`ui/dr-ui/src/library_ui.rs:6964`](../ui/dr-ui/src/library_ui.rs#L6964), [`ui/dr-ui/src/library_ui.rs:6977`](../ui/dr-ui/src/library_ui.rs#L6977), [`ui/dr-ui/src/library_ui.rs:6992`](../ui/dr-ui/src/library_ui.rs#L6992), [`ui/dr-ui/src/library_ui.rs:7001`](../ui/dr-ui/src/library_ui.rs#L7001), [`ui/dr-ui/ui/app.slint:333`](../ui/dr-ui/ui/app.slint#L333), [`ui/dr-ui/ui/app.slint:547`](../ui/dr-ui/ui/app.slint#L547), [`ui/dr-ui/ui/library.slint:1246`](../ui/dr-ui/ui/library.slint#L1246), [`ui/dr-ui/ui/library.slint:1270`](../ui/dr-ui/ui/library.slint#L1270), [`ui/dr-ui/ui/library.slint:1323`](../ui/dr-ui/ui/library.slint#L1323), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:3963`](../ui/dr-ui/ui/library.slint#L3963) | +| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4486`](../ui/dr-ui/src/library.rs#L4486), [`ui/dr-ui/src/library_ui.rs:342`](../ui/dr-ui/src/library_ui.rs#L342), [`ui/dr-ui/src/library_ui.rs:416`](../ui/dr-ui/src/library_ui.rs#L416), [`ui/dr-ui/src/library_ui.rs:5688`](../ui/dr-ui/src/library_ui.rs#L5688), [`ui/dr-ui/src/library_ui.rs:5741`](../ui/dr-ui/src/library_ui.rs#L5741), [`ui/dr-ui/src/library_ui.rs:7093`](../ui/dr-ui/src/library_ui.rs#L7093), [`ui/dr-ui/ui/app.slint:345`](../ui/dr-ui/ui/app.slint#L345), [`ui/dr-ui/ui/app.slint:547`](../ui/dr-ui/ui/app.slint#L547), [`ui/dr-ui/ui/app.slint:850`](../ui/dr-ui/ui/app.slint#L850), [`ui/dr-ui/ui/library.slint:110`](../ui/dr-ui/ui/library.slint#L110), [`ui/dr-ui/ui/library.slint:1433`](../ui/dr-ui/ui/library.slint#L1433), [`ui/dr-ui/ui/library.slint:3963`](../ui/dr-ui/ui/library.slint#L3963), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | +| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1073`](../ui/dr-ui/src/collections_ui.rs#L1073), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1598`](../ui/dr-ui/src/collections_ui.rs#L1598), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1913`](../ui/dr-ui/src/collections_ui.rs#L1913), [`ui/dr-ui/src/collections_ui.rs:1973`](../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:965`](../ui/dr-ui/src/collections_ui.rs#L965), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4291`](../ui/dr-ui/src/library.rs#L4291), [`ui/dr-ui/src/library.rs:4317`](../ui/dr-ui/src/library.rs#L4317), [`ui/dr-ui/src/library.rs:4372`](../ui/dr-ui/src/library.rs#L4372), [`ui/dr-ui/src/library_ui.rs:2467`](../ui/dr-ui/src/library_ui.rs#L2467), [`ui/dr-ui/src/library_ui.rs:3863`](../ui/dr-ui/src/library_ui.rs#L3863), [`ui/dr-ui/src/library_ui.rs:4590`](../ui/dr-ui/src/library_ui.rs#L4590), [`ui/dr-ui/ui/app.slint:336`](../ui/dr-ui/ui/app.slint#L336), [`ui/dr-ui/ui/app.slint:542`](../ui/dr-ui/ui/app.slint#L542), [`ui/dr-ui/ui/app.slint:571`](../ui/dr-ui/ui/app.slint#L571), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:1213`](../ui/dr-ui/ui/library.slint#L1213), [`ui/dr-ui/ui/library.slint:2748`](../ui/dr-ui/ui/library.slint#L2748), [`ui/dr-ui/ui/library.slint:3941`](../ui/dr-ui/ui/library.slint#L3941), [`ui/dr-ui/ui/library.slint:889`](../ui/dr-ui/ui/library.slint#L889) | | FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1669`](../core/dr-pipeline/src/sidecar.rs#L1669), [`core/dr-pipeline/src/sidecar.rs:93`](../core/dr-pipeline/src/sidecar.rs#L93), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:3954`](../ui/dr-ui/src/develop.rs#L3954), [`ui/dr-ui/src/develop.rs:3983`](../ui/dr-ui/src/develop.rs#L3983), [`ui/dr-ui/src/export.rs:750`](../ui/dr-ui/src/export.rs#L750), [`ui/dr-ui/src/lib.rs:1037`](../ui/dr-ui/src/lib.rs#L1037), [`ui/dr-ui/src/lib.rs:1548`](../ui/dr-ui/src/lib.rs#L1548), [`ui/dr-ui/src/lib.rs:2038`](../ui/dr-ui/src/lib.rs#L2038), [`ui/dr-ui/src/lib.rs:2173`](../ui/dr-ui/src/lib.rs#L2173), [`ui/dr-ui/src/lib.rs:589`](../ui/dr-ui/src/lib.rs#L589), [`ui/dr-ui/src/library.rs:1903`](../ui/dr-ui/src/library.rs#L1903), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:5313`](../ui/dr-ui/src/library_ui.rs#L5313), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | -| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`ui/dr-ui/src/develop.rs:3381`](../ui/dr-ui/src/develop.rs#L3381), [`ui/dr-ui/src/library.rs:1222`](../ui/dr-ui/src/library.rs#L1222), [`ui/dr-ui/src/library.rs:1275`](../ui/dr-ui/src/library.rs#L1275), [`ui/dr-ui/src/library.rs:1306`](../ui/dr-ui/src/library.rs#L1306), [`ui/dr-ui/src/library.rs:1411`](../ui/dr-ui/src/library.rs#L1411), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library.rs:1939`](../ui/dr-ui/src/library.rs#L1939), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4578`](../ui/dr-ui/src/library.rs#L4578), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1241`](../ui/dr-ui/src/library_ui.rs#L1241), [`ui/dr-ui/src/library_ui.rs:1832`](../ui/dr-ui/src/library_ui.rs#L1832), [`ui/dr-ui/src/library_ui.rs:1876`](../ui/dr-ui/src/library_ui.rs#L1876), [`ui/dr-ui/src/library_ui.rs:1892`](../ui/dr-ui/src/library_ui.rs#L1892), [`ui/dr-ui/src/library_ui.rs:1986`](../ui/dr-ui/src/library_ui.rs#L1986), [`ui/dr-ui/src/library_ui.rs:237`](../ui/dr-ui/src/library_ui.rs#L237), [`ui/dr-ui/src/library_ui.rs:2706`](../ui/dr-ui/src/library_ui.rs#L2706), [`ui/dr-ui/src/library_ui.rs:270`](../ui/dr-ui/src/library_ui.rs#L270), [`ui/dr-ui/src/library_ui.rs:278`](../ui/dr-ui/src/library_ui.rs#L278), [`ui/dr-ui/src/library_ui.rs:3144`](../ui/dr-ui/src/library_ui.rs#L3144), [`ui/dr-ui/src/library_ui.rs:3366`](../ui/dr-ui/src/library_ui.rs#L3366), [`ui/dr-ui/src/library_ui.rs:3644`](../ui/dr-ui/src/library_ui.rs#L3644), [`ui/dr-ui/src/library_ui.rs:3732`](../ui/dr-ui/src/library_ui.rs#L3732), [`ui/dr-ui/src/library_ui.rs:3914`](../ui/dr-ui/src/library_ui.rs#L3914), [`ui/dr-ui/src/library_ui.rs:4028`](../ui/dr-ui/src/library_ui.rs#L4028), [`ui/dr-ui/src/library_ui.rs:469`](../ui/dr-ui/src/library_ui.rs#L469), [`ui/dr-ui/src/library_ui.rs:532`](../ui/dr-ui/src/library_ui.rs#L532), [`ui/dr-ui/src/library_ui.rs:5786`](../ui/dr-ui/src/library_ui.rs#L5786), [`ui/dr-ui/src/library_ui.rs:5901`](../ui/dr-ui/src/library_ui.rs#L5901), [`ui/dr-ui/src/presets.rs:446`](../ui/dr-ui/src/presets.rs#L446), [`ui/dr-ui/src/presets.rs:458`](../ui/dr-ui/src/presets.rs#L458), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`ui/dr-ui/src/develop.rs:3381`](../ui/dr-ui/src/develop.rs#L3381), [`ui/dr-ui/src/library.rs:1222`](../ui/dr-ui/src/library.rs#L1222), [`ui/dr-ui/src/library.rs:1275`](../ui/dr-ui/src/library.rs#L1275), [`ui/dr-ui/src/library.rs:1306`](../ui/dr-ui/src/library.rs#L1306), [`ui/dr-ui/src/library.rs:1411`](../ui/dr-ui/src/library.rs#L1411), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library.rs:1939`](../ui/dr-ui/src/library.rs#L1939), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4594`](../ui/dr-ui/src/library.rs#L4594), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1241`](../ui/dr-ui/src/library_ui.rs#L1241), [`ui/dr-ui/src/library_ui.rs:1832`](../ui/dr-ui/src/library_ui.rs#L1832), [`ui/dr-ui/src/library_ui.rs:1876`](../ui/dr-ui/src/library_ui.rs#L1876), [`ui/dr-ui/src/library_ui.rs:1892`](../ui/dr-ui/src/library_ui.rs#L1892), [`ui/dr-ui/src/library_ui.rs:1986`](../ui/dr-ui/src/library_ui.rs#L1986), [`ui/dr-ui/src/library_ui.rs:237`](../ui/dr-ui/src/library_ui.rs#L237), [`ui/dr-ui/src/library_ui.rs:2706`](../ui/dr-ui/src/library_ui.rs#L2706), [`ui/dr-ui/src/library_ui.rs:270`](../ui/dr-ui/src/library_ui.rs#L270), [`ui/dr-ui/src/library_ui.rs:278`](../ui/dr-ui/src/library_ui.rs#L278), [`ui/dr-ui/src/library_ui.rs:3144`](../ui/dr-ui/src/library_ui.rs#L3144), [`ui/dr-ui/src/library_ui.rs:3366`](../ui/dr-ui/src/library_ui.rs#L3366), [`ui/dr-ui/src/library_ui.rs:3644`](../ui/dr-ui/src/library_ui.rs#L3644), [`ui/dr-ui/src/library_ui.rs:3732`](../ui/dr-ui/src/library_ui.rs#L3732), [`ui/dr-ui/src/library_ui.rs:3914`](../ui/dr-ui/src/library_ui.rs#L3914), [`ui/dr-ui/src/library_ui.rs:4028`](../ui/dr-ui/src/library_ui.rs#L4028), [`ui/dr-ui/src/library_ui.rs:469`](../ui/dr-ui/src/library_ui.rs#L469), [`ui/dr-ui/src/library_ui.rs:532`](../ui/dr-ui/src/library_ui.rs#L532), [`ui/dr-ui/src/library_ui.rs:5786`](../ui/dr-ui/src/library_ui.rs#L5786), [`ui/dr-ui/src/library_ui.rs:5901`](../ui/dr-ui/src/library_ui.rs#L5901), [`ui/dr-ui/src/presets.rs:446`](../ui/dr-ui/src/presets.rs#L446), [`ui/dr-ui/src/presets.rs:458`](../ui/dr-ui/src/presets.rs#L458), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | | FR-CULL-1 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | | FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:376`](../core/dr-catalog/src/schema.rs#L376), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/develop.rs:120`](../ui/dr-ui/src/develop.rs#L120), [`ui/dr-ui/src/develop.rs:129`](../ui/dr-ui/src/develop.rs#L129), [`ui/dr-ui/src/develop.rs:195`](../ui/dr-ui/src/develop.rs#L195), [`ui/dr-ui/src/develop.rs:2011`](../ui/dr-ui/src/develop.rs#L2011), [`ui/dr-ui/src/develop.rs:688`](../ui/dr-ui/src/develop.rs#L688), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2145`](../ui/dr-ui/src/lib.rs#L2145), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-11 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/library.rs:287`](../ui/dr-ui/src/library.rs#L287), [`ui/dr-ui/src/library.rs:317`](../ui/dr-ui/src/library.rs#L317), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | @@ -54,7 +54,7 @@ _None._ | FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463) | | FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:3106`](../ui/dr-ui/src/develop.rs#L3106), [`ui/dr-ui/src/develop.rs:3119`](../ui/dr-ui/src/develop.rs#L3119), [`ui/dr-ui/src/develop.rs:3164`](../ui/dr-ui/src/develop.rs#L3164), [`ui/dr-ui/src/develop.rs:3175`](../ui/dr-ui/src/develop.rs#L3175), [`ui/dr-ui/src/develop.rs:3181`](../ui/dr-ui/src/develop.rs#L3181), [`ui/dr-ui/src/develop.rs:3198`](../ui/dr-ui/src/develop.rs#L3198), [`ui/dr-ui/src/develop.rs:6081`](../ui/dr-ui/src/develop.rs#L6081), [`ui/dr-ui/src/develop.rs:6145`](../ui/dr-ui/src/develop.rs#L6145), [`ui/dr-ui/src/develop.rs:6168`](../ui/dr-ui/src/develop.rs#L6168), [`ui/dr-ui/src/develop.rs:726`](../ui/dr-ui/src/develop.rs#L726), [`ui/dr-ui/src/develop.rs:746`](../ui/dr-ui/src/develop.rs#L746), [`ui/dr-ui/src/develop.rs:752`](../ui/dr-ui/src/develop.rs#L752), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1587`](../ui/dr-ui/src/lib.rs#L1587), [`ui/dr-ui/src/lib.rs:1665`](../ui/dr-ui/src/lib.rs#L1665), [`ui/dr-ui/src/lib.rs:1764`](../ui/dr-ui/src/lib.rs#L1764), [`ui/dr-ui/src/lib.rs:1805`](../ui/dr-ui/src/lib.rs#L1805), [`ui/dr-ui/src/lib.rs:3055`](../ui/dr-ui/src/lib.rs#L3055), [`ui/dr-ui/src/lib.rs:349`](../ui/dr-ui/src/lib.rs#L349), [`ui/dr-ui/src/peaking.rs:1`](../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:1820`](../ui/dr-ui/ui/app.slint#L1820), [`ui/dr-ui/ui/app.slint:2398`](../ui/dr-ui/ui/app.slint#L2398), [`ui/dr-ui/ui/app.slint:89`](../ui/dr-ui/ui/app.slint#L89), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../ui/dr-ui/ui/peaking.slint#L56) | | FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:136`](../core/dr-pipeline/src/sidecar.rs#L136), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492) | -| FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:419`](../core/dr-catalog/src/schema.rs#L419), [`ui/dr-ui/src/bursts.rs:1`](../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library.rs:202`](../ui/dr-ui/src/library.rs#L202), [`ui/dr-ui/src/library.rs:5179`](../ui/dr-ui/src/library.rs#L5179) | +| FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:419`](../core/dr-catalog/src/schema.rs#L419), [`ui/dr-ui/src/bursts.rs:1`](../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library.rs:202`](../ui/dr-ui/src/library.rs#L202), [`ui/dr-ui/src/library.rs:5195`](../ui/dr-ui/src/library.rs#L5195) | | FR-CULL-8 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:487`](../core/dr-catalog/src/schema.rs#L487), [`core/dr-catalog/src/schema.rs:513`](../core/dr-catalog/src/schema.rs#L513), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:2965`](../ui/dr-ui/src/library.rs#L2965), [`ui/dr-ui/src/library.rs:3069`](../ui/dr-ui/src/library.rs#L3069), [`ui/dr-ui/ui/settings.slint:419`](../ui/dr-ui/ui/settings.slint#L419), [`ui/dr-ui/ui/settings.slint:82`](../ui/dr-ui/ui/settings.slint#L82) | | FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/ui/identity.slint:283`](../ui/dr-ui/ui/identity.slint#L283) | | FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../core/dr-pipeline/src/sidecar.rs#L1) | @@ -92,15 +92,15 @@ _None._ | FR-NC-12 | [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:40`](../core/dr-sync-nextcloud/src/lib.rs#L40), [`core/dr-sync-nextcloud/src/lib.rs:914`](../core/dr-sync-nextcloud/src/lib.rs#L914), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:1`](../core/dr-sync/src/account.rs#L1), [`core/dr-sync/src/account.rs:81`](../core/dr-sync/src/account.rs#L81), [`core/dr-sync/src/lib.rs:218`](../core/dr-sync/src/lib.rs#L218), [`core/dr-sync/src/lib.rs:51`](../core/dr-sync/src/lib.rs#L51), [`core/dr-sync/src/provider.rs:106`](../core/dr-sync/src/provider.rs#L106), [`core/dr-sync/src/provider.rs:1`](../core/dr-sync/src/provider.rs#L1), [`core/dr-sync/src/provider.rs:53`](../core/dr-sync/src/provider.rs#L53), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`ui/dr-ui/src/remote.rs:1`](../ui/dr-ui/src/remote.rs#L1) | | FR-NC-13 | [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync/src/provider.rs:106`](../core/dr-sync/src/provider.rs#L106), [`ui/dr-ui/src/launch_ui.rs:316`](../ui/dr-ui/src/launch_ui.rs#L316), [`ui/dr-ui/src/remote.rs:1`](../ui/dr-ui/src/remote.rs#L1) | | FR-NC-2 | [`core/dr-sync/src/account.rs:1`](../core/dr-sync/src/account.rs#L1), [`core/dr-sync/src/account.rs:298`](../core/dr-sync/src/account.rs#L298), [`core/dr-sync/src/account.rs:355`](../core/dr-sync/src/account.rs#L355), [`core/dr-sync/src/account.rs:59`](../core/dr-sync/src/account.rs#L59), [`platform/dr-plat/src/secrets.rs:82`](../platform/dr-plat/src/secrets.rs#L82) | -| FR-NC-3 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`core/dr-sync/src/capability.rs:85`](../core/dr-sync/src/capability.rs#L85), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964), [`ui/dr-ui/src/library.rs:3472`](../ui/dr-ui/src/library.rs#L3472), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:4001`](../ui/dr-ui/src/library_ui.rs#L4001), [`ui/dr-ui/src/library_ui.rs:5021`](../ui/dr-ui/src/library_ui.rs#L5021), [`ui/dr-ui/ui/app.slint:412`](../ui/dr-ui/ui/app.slint#L412), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | +| FR-NC-3 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`core/dr-sync/src/capability.rs:85`](../core/dr-sync/src/capability.rs#L85), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964), [`ui/dr-ui/src/library.rs:3488`](../ui/dr-ui/src/library.rs#L3488), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:4001`](../ui/dr-ui/src/library_ui.rs#L4001), [`ui/dr-ui/src/library_ui.rs:5021`](../ui/dr-ui/src/library_ui.rs#L5021), [`ui/dr-ui/ui/app.slint:412`](../ui/dr-ui/ui/app.slint#L412), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | | FR-NC-4 | [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-nextcloud/src/propfind.rs:103`](../core/dr-sync-nextcloud/src/propfind.rs#L103), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:218`](../core/dr-sync/src/lib.rs#L218), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61) | | FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488) | | FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1) | | FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1126`](../core/dr-catalog/src/schema.rs#L1126), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3413`](../ui/dr-ui/src/collections_ui.rs#L3413), [`ui/dr-ui/src/collections_ui.rs:636`](../ui/dr-ui/src/collections_ui.rs#L636), [`ui/dr-ui/src/collections_ui.rs:704`](../ui/dr-ui/src/collections_ui.rs#L704), [`ui/dr-ui/src/lib.rs:2068`](../ui/dr-ui/src/lib.rs#L2068), [`ui/dr-ui/src/lib.rs:3191`](../ui/dr-ui/src/lib.rs#L3191), [`ui/dr-ui/src/library.rs:1565`](../ui/dr-ui/src/library.rs#L1565), [`ui/dr-ui/src/library.rs:1588`](../ui/dr-ui/src/library.rs#L1588), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library_ui.rs:1317`](../ui/dr-ui/src/library_ui.rs#L1317), [`ui/dr-ui/src/library_ui.rs:1390`](../ui/dr-ui/src/library_ui.rs#L1390), [`ui/dr-ui/src/library_ui.rs:1491`](../ui/dr-ui/src/library_ui.rs#L1491), [`ui/dr-ui/src/library_ui.rs:1546`](../ui/dr-ui/src/library_ui.rs#L1546), [`ui/dr-ui/src/library_ui.rs:1681`](../ui/dr-ui/src/library_ui.rs#L1681), [`ui/dr-ui/src/library_ui.rs:1799`](../ui/dr-ui/src/library_ui.rs#L1799), [`ui/dr-ui/src/library_ui.rs:2424`](../ui/dr-ui/src/library_ui.rs#L2424), [`ui/dr-ui/src/library_ui.rs:294`](../ui/dr-ui/src/library_ui.rs#L294), [`ui/dr-ui/src/library_ui.rs:305`](../ui/dr-ui/src/library_ui.rs#L305), [`ui/dr-ui/src/library_ui.rs:313`](../ui/dr-ui/src/library_ui.rs#L313), [`ui/dr-ui/src/library_ui.rs:325`](../ui/dr-ui/src/library_ui.rs#L325), [`ui/dr-ui/src/library_ui.rs:334`](../ui/dr-ui/src/library_ui.rs#L334), [`ui/dr-ui/src/library_ui.rs:428`](../ui/dr-ui/src/library_ui.rs#L428), [`ui/dr-ui/src/library_ui.rs:438`](../ui/dr-ui/src/library_ui.rs#L438), [`ui/dr-ui/src/library_ui.rs:485`](../ui/dr-ui/src/library_ui.rs#L485), [`ui/dr-ui/src/library_ui.rs:548`](../ui/dr-ui/src/library_ui.rs#L548), [`ui/dr-ui/src/library_ui.rs:579`](../ui/dr-ui/src/library_ui.rs#L579), [`ui/dr-ui/src/library_ui.rs:5803`](../ui/dr-ui/src/library_ui.rs#L5803), [`ui/dr-ui/src/library_ui.rs:5821`](../ui/dr-ui/src/library_ui.rs#L5821), [`ui/dr-ui/src/library_ui.rs:5833`](../ui/dr-ui/src/library_ui.rs#L5833), [`ui/dr-ui/src/library_ui.rs:591`](../ui/dr-ui/src/library_ui.rs#L591), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1483`](../ui/dr-ui/ui/app.slint#L1483), [`ui/dr-ui/ui/app.slint:2674`](../ui/dr-ui/ui/app.slint#L2674), [`ui/dr-ui/ui/app.slint:536`](../ui/dr-ui/ui/app.slint#L536), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:360`](../ui/dr-ui/ui/collections.slint#L360), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:372`](../ui/dr-ui/ui/collections.slint#L372), [`ui/dr-ui/ui/collections.slint:395`](../ui/dr-ui/ui/collections.slint#L395), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:596`](../ui/dr-ui/ui/collections.slint#L596), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | | FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1546`](../ui/dr-ui/src/library_ui.rs#L1546), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | -| FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:167`](../core/dr-sync-nextcloud/src/desktop_client.rs#L167), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-sync/src/error.rs:39`](../core/dr-sync/src/error.rs#L39), [`core/dr-sync/src/lib.rs:158`](../core/dr-sync/src/lib.rs#L158), [`core/dr-sync/src/types.rs:101`](../core/dr-sync/src/types.rs#L101), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`core/dr-types/src/lib.rs:202`](../core/dr-types/src/lib.rs#L202), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3413`](../ui/dr-ui/src/collections_ui.rs#L3413), [`ui/dr-ui/src/collections_ui.rs:636`](../ui/dr-ui/src/collections_ui.rs#L636), [`ui/dr-ui/src/collections_ui.rs:704`](../ui/dr-ui/src/collections_ui.rs#L704), [`ui/dr-ui/src/derived_sync.rs:555`](../ui/dr-ui/src/derived_sync.rs#L555), [`ui/dr-ui/src/derived_sync.rs:627`](../ui/dr-ui/src/derived_sync.rs#L627), [`ui/dr-ui/src/library.rs:1685`](../ui/dr-ui/src/library.rs#L1685), [`ui/dr-ui/src/library.rs:1775`](../ui/dr-ui/src/library.rs#L1775), [`ui/dr-ui/src/library.rs:3254`](../ui/dr-ui/src/library.rs#L3254), [`ui/dr-ui/src/library.rs:3592`](../ui/dr-ui/src/library.rs#L3592), [`ui/dr-ui/src/library.rs:981`](../ui/dr-ui/src/library.rs#L981), [`ui/dr-ui/src/library_ui.rs:1317`](../ui/dr-ui/src/library_ui.rs#L1317), [`ui/dr-ui/src/library_ui.rs:1390`](../ui/dr-ui/src/library_ui.rs#L1390), [`ui/dr-ui/src/library_ui.rs:1589`](../ui/dr-ui/src/library_ui.rs#L1589), [`ui/dr-ui/src/remote.rs:40`](../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | +| FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:167`](../core/dr-sync-nextcloud/src/desktop_client.rs#L167), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-sync/src/error.rs:39`](../core/dr-sync/src/error.rs#L39), [`core/dr-sync/src/lib.rs:158`](../core/dr-sync/src/lib.rs#L158), [`core/dr-sync/src/types.rs:101`](../core/dr-sync/src/types.rs#L101), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`core/dr-types/src/lib.rs:202`](../core/dr-types/src/lib.rs#L202), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3413`](../ui/dr-ui/src/collections_ui.rs#L3413), [`ui/dr-ui/src/collections_ui.rs:636`](../ui/dr-ui/src/collections_ui.rs#L636), [`ui/dr-ui/src/collections_ui.rs:704`](../ui/dr-ui/src/collections_ui.rs#L704), [`ui/dr-ui/src/derived_sync.rs:555`](../ui/dr-ui/src/derived_sync.rs#L555), [`ui/dr-ui/src/derived_sync.rs:627`](../ui/dr-ui/src/derived_sync.rs#L627), [`ui/dr-ui/src/library.rs:1685`](../ui/dr-ui/src/library.rs#L1685), [`ui/dr-ui/src/library.rs:1775`](../ui/dr-ui/src/library.rs#L1775), [`ui/dr-ui/src/library.rs:3254`](../ui/dr-ui/src/library.rs#L3254), [`ui/dr-ui/src/library.rs:3608`](../ui/dr-ui/src/library.rs#L3608), [`ui/dr-ui/src/library.rs:981`](../ui/dr-ui/src/library.rs#L981), [`ui/dr-ui/src/library_ui.rs:1317`](../ui/dr-ui/src/library_ui.rs#L1317), [`ui/dr-ui/src/library_ui.rs:1390`](../ui/dr-ui/src/library_ui.rs#L1390), [`ui/dr-ui/src/library_ui.rs:1589`](../ui/dr-ui/src/library_ui.rs#L1589), [`ui/dr-ui/src/remote.rs:40`](../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | | FR-NC-6d | [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1) | -| FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:105`](../core/dr-sync-nextcloud/src/lib.rs#L105), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:3472`](../ui/dr-ui/src/library.rs#L3472), [`ui/dr-ui/src/library_ui.rs:4001`](../ui/dr-ui/src/library_ui.rs#L4001), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387) | +| FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:105`](../core/dr-sync-nextcloud/src/lib.rs#L105), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:3488`](../ui/dr-ui/src/library.rs#L3488), [`ui/dr-ui/src/library_ui.rs:4001`](../ui/dr-ui/src/library_ui.rs#L4001), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387) | | FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1254`](../ui/dr-ui/src/lib.rs#L1254), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | | FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:122`](../ui/dr-ui/src/import.rs#L122), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import.rs:584`](../ui/dr-ui/src/import.rs#L584), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1254`](../ui/dr-ui/src/lib.rs#L1254) | | FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:119`](../core/dr-pipeline/src/sidecar.rs#L119), [`core/dr-pipeline/src/sidecar.rs:93`](../core/dr-pipeline/src/sidecar.rs#L93), [`ui/dr-ui/src/lib.rs:2038`](../ui/dr-ui/src/lib.rs#L2038), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library_ui.rs:500`](../ui/dr-ui/src/library_ui.rs#L500) | @@ -108,7 +108,7 @@ _None._ | FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-sync-folder/src/lib.rs:242`](../core/dr-sync-folder/src/lib.rs#L242), [`core/dr-sync-folder/src/tests.rs:51`](../core/dr-sync-folder/src/tests.rs#L51), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/error.rs:195`](../core/dr-sync/src/error.rs#L195), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-sync/src/scan.rs:490`](../core/dr-sync/src/scan.rs#L490), [`core/dr-sync/src/scan.rs:519`](../core/dr-sync/src/scan.rs#L519), [`ui/dr-ui/src/library.rs:1222`](../ui/dr-ui/src/library.rs#L1222), [`ui/dr-ui/src/library.rs:1275`](../ui/dr-ui/src/library.rs#L1275), [`ui/dr-ui/src/library.rs:1306`](../ui/dr-ui/src/library.rs#L1306), [`ui/dr-ui/src/library.rs:1411`](../ui/dr-ui/src/library.rs#L1411), [`ui/dr-ui/src/library_ui.rs:1162`](../ui/dr-ui/src/library_ui.rs#L1162), [`ui/dr-ui/src/library_ui.rs:1241`](../ui/dr-ui/src/library_ui.rs#L1241), [`ui/dr-ui/src/library_ui.rs:1840`](../ui/dr-ui/src/library_ui.rs#L1840), [`ui/dr-ui/src/library_ui.rs:278`](../ui/dr-ui/src/library_ui.rs#L278), [`ui/dr-ui/src/library_ui.rs:469`](../ui/dr-ui/src/library_ui.rs#L469) | | FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) | | FR-PLAT-AND-4 | [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) | -| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-gpu/src/detail.rs:161`](../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop.rs:3303`](../ui/dr-ui/src/develop.rs#L3303), [`ui/dr-ui/src/identity_ui.rs:115`](../ui/dr-ui/src/identity_ui.rs#L115), [`ui/dr-ui/src/lib.rs:1078`](../ui/dr-ui/src/lib.rs#L1078), [`ui/dr-ui/src/lib.rs:1522`](../ui/dr-ui/src/lib.rs#L1522), [`ui/dr-ui/src/memory.rs:163`](../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | +| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-gpu/src/detail.rs:161`](../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop.rs:3303`](../ui/dr-ui/src/develop.rs#L3303), [`ui/dr-ui/src/identity_ui.rs:123`](../ui/dr-ui/src/identity_ui.rs#L123), [`ui/dr-ui/src/lib.rs:1078`](../ui/dr-ui/src/lib.rs#L1078), [`ui/dr-ui/src/lib.rs:1522`](../ui/dr-ui/src/lib.rs#L1522), [`ui/dr-ui/src/memory.rs:163`](../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | | FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:388`](../apps/darkroom-android/src/lib.rs#L388) | | FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:954`](../ui/dr-ui/src/lib.rs#L954), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1) | @@ -138,7 +138,7 @@ _None._ | NFR-P13 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | | NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop.rs:3198`](../ui/dr-ui/src/develop.rs#L3198), [`ui/dr-ui/src/lib.rs:1764`](../ui/dr-ui/src/lib.rs#L1764) | | NFR-P3 | [`tools/bench/src/thumbnails.rs:1`](../tools/bench/src/thumbnails.rs#L1) | -| NFR-P5 | [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`ui/dr-ui/src/library.rs:4169`](../ui/dr-ui/src/library.rs#L4169), [`ui/dr-ui/src/library.rs:5103`](../ui/dr-ui/src/library.rs#L5103), [`ui/dr-ui/src/library_ui.rs:4468`](../ui/dr-ui/src/library_ui.rs#L4468), [`ui/dr-ui/src/library_ui.rs:64`](../ui/dr-ui/src/library_ui.rs#L64) | +| NFR-P5 | [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`ui/dr-ui/src/library.rs:4185`](../ui/dr-ui/src/library.rs#L4185), [`ui/dr-ui/src/library.rs:5119`](../ui/dr-ui/src/library.rs#L5119), [`ui/dr-ui/src/library_ui.rs:4468`](../ui/dr-ui/src/library_ui.rs#L4468), [`ui/dr-ui/src/library_ui.rs:64`](../ui/dr-ui/src/library_ui.rs#L64) | | NFR-P9 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/export.rs:942`](../ui/dr-ui/src/export.rs#L942), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1) | | NFR-PORT-1 | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:270`](../core/dr-types/src/lib.rs#L270), [`core/dr-types/src/lib.rs:303`](../core/dr-types/src/lib.rs#L303), [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/storage.rs:1`](../platform/dr-plat/src/storage.rs#L1), [`platform/dr-plat/src/storage.rs:216`](../platform/dr-plat/src/storage.rs#L216), [`platform/dr-plat/src/storage.rs:287`](../platform/dr-plat/src/storage.rs#L287), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`platform/dr-plat/src/volumes.rs:1`](../platform/dr-plat/src/volumes.rs#L1), [`platform/dr-plat/src/volumes.rs:62`](../platform/dr-plat/src/volumes.rs#L62) | | NFR-PORT-2 | [`core/dr-gpu/src/lib.rs:1`](../core/dr-gpu/src/lib.rs#L1) | @@ -149,7 +149,7 @@ _None._ | NFR-R6 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/lib.rs:255`](../core/dr-catalog/src/lib.rs#L255), [`core/dr-catalog/src/recovery.rs:1`](../core/dr-catalog/src/recovery.rs#L1) | | NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) | | NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) | -| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop.rs:3303`](../ui/dr-ui/src/develop.rs#L3303), [`ui/dr-ui/src/identity_ui.rs:115`](../ui/dr-ui/src/identity_ui.rs#L115), [`ui/dr-ui/src/lib.rs:91`](../ui/dr-ui/src/lib.rs#L91), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | +| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop.rs:3303`](../ui/dr-ui/src/develop.rs#L3303), [`ui/dr-ui/src/identity_ui.rs:123`](../ui/dr-ui/src/identity_ui.rs#L123), [`ui/dr-ui/src/lib.rs:91`](../ui/dr-ui/src/lib.rs#L91), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | | NFR-RES-4 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964) | | NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) | | NFR-SEC-2 | [`core/dr-sync/src/account.rs:298`](../core/dr-sync/src/account.rs#L298), [`platform/dr-plat/src/crash.rs:1`](../platform/dr-plat/src/crash.rs#L1), [`platform/dr-plat/src/diagnostics.rs:1`](../platform/dr-plat/src/diagnostics.rs#L1), [`platform/dr-plat/src/diagnostics/redact.rs:1`](../platform/dr-plat/src/diagnostics/redact.rs#L1), [`platform/dr-plat/src/secrets.rs:82`](../platform/dr-plat/src/secrets.rs#L82) | diff --git a/ui/dr-ui/examples/face_index.rs b/ui/dr-ui/examples/face_index.rs index f8d09c3..a890505 100644 --- a/ui/dr-ui/examples/face_index.rs +++ b/ui/dr-ui/examples/face_index.rs @@ -160,6 +160,26 @@ fn main() { return; } + // Say it here rather than letting the pass return an empty result and + // print "done: 0 image(s)". That is the shape of report this whole change + // exists to stop producing. + if faces::FACE_TIER.edge() < dr_face::MIN_DETECT_EDGE { + println!( + "\n--run cannot index from the local store any more.\n\ + \n\ + Detection needs a proxy longer than {}px and the store's largest\n\ + tier is {}px, so every image here would be refused. The measurement\n\ + behind that floor is docs/faces.md §7c.\n\ + \n\ + Index from the app's Identity screen instead: that pass fetches the\n\ + embedded preview at full size, which is the resolution detection\n\ + needs.", + dr_face::MIN_DETECT_EDGE - 1, + faces::FACE_TIER.edge(), + ); + return; + } + println!("\nindexing {} image(s)…", audit.ready); let rx = faces::spawn_store_face_sweep( catalog_path, @@ -189,13 +209,16 @@ fn main() { ); } } + FaceSweepMessage::Failed { images } => seen += images, FaceSweepMessage::Finished { images, faces, failed, + too_small, } => { println!( - "\ndone: {images} image(s), {faces} face(s), {failed} failed, in {:.0}s", + "\ndone: {images} image(s), {faces} face(s), {failed} failed, \ + {too_small} with no proxy big enough, in {:.0}s", start.elapsed().as_secs_f64() ); } diff --git a/ui/dr-ui/src/faces.rs b/ui/dr-ui/src/faces.rs index 6efc3a6..9147b5a 100644 --- a/ui/dr-ui/src/faces.rs +++ b/ui/dr-ui/src/faces.rs @@ -50,11 +50,29 @@ pub enum FaceSweepMessage { Total(usize), /// One image finished, with the faces found in it. Indexed { image: ImageId, faces: usize }, + /// Images the pass gave up on, counted since the last message. + /// + /// **Progress has to advance on these or it does not advance at all.** + /// The count moved only on `Indexed`, so a run where every image failed + /// sat at 0/169 from the first tick to the last and then reported + /// success: the receiver was told the total, told nothing, and told the + /// pass had ended. That is exactly what happened on the reference library + /// for a day, and it is why the sweep was thought to be hanging when it + /// was in fact finishing in fourteen seconds. + /// + /// A batch rather than one message per image, because failures come back + /// lane-sized and the interesting number is how many. The reason for each + /// is logged where it happens. + Failed { images: usize }, /// The pass ended. Finished { images: usize, faces: usize, failed: usize, + /// Refused for want of a big enough proxy rather than failed; see + /// `docs/faces.md` §7c. Counted apart because a library of small + /// scans is not a broken network and must not read as one. + too_small: usize, }, } @@ -351,6 +369,7 @@ pub fn spawn_store_face_sweep( images: 0, faces: 0, failed: 0, + too_small: 0, }); }; @@ -508,6 +527,7 @@ pub fn spawn_store_face_sweep( images, faces: found, failed, + too_small: 0, }); }); diff --git a/ui/dr-ui/src/identity_ui.rs b/ui/dr-ui/src/identity_ui.rs index c61aad8..071109c 100644 --- a/ui/dr-ui/src/identity_ui.rs +++ b/ui/dr-ui/src/identity_ui.rs @@ -43,6 +43,14 @@ pub struct IdentityController { /// Images visited so far in the current sweep, and the total it announced. progress: std::cell::Cell<(usize, usize)>, faces_found: std::cell::Cell, + /// Images the running sweep could not get through, and images it refused + /// for want of a big enough proxy. + /// + /// On screen because a pass that fails everything used to be + /// indistinguishable from one that succeeded at everything: both ended + /// with a tidy "0 face(s) in 0 image(s)". + sweep_failed: std::cell::Cell, + sweep_too_small: std::cell::Cell, /// Whether opening this screen left the library rather than develop. /// /// Recorded so leaving puts the user back where they were. The screen is @@ -1035,6 +1043,8 @@ pub fn wire( ctl.progress.set((0, 0)); ctl.faces_found.set(0); + ctl.sweep_failed.set(0); + ctl.sweep_too_small.set(0); *ctl.activity.borrow_mut() = Some(activity.begin(crate::activity::Kind::Index, "Indexing faces")); *ctl.sweep.borrow_mut() = Some(crate::library::spawn_face_sweep( @@ -1079,19 +1089,42 @@ pub fn wire( ctl_tick.progress.set((seen + 1, total)); ctl_tick.faces_found.set(ctl_tick.faces_found.get() + faces); } - FaceSweepMessage::Finished { .. } => done = true, + FaceSweepMessage::Failed { images } => { + // Advances the same counter. Work that + // failed is still work the pass got + // through, and a bar that only moves on + // success reports a run of pure failure as + // no progress at all. + let (seen, total) = ctl_tick.progress.get(); + ctl_tick.progress.set((seen + images, total)); + ctl_tick + .sweep_failed + .set(ctl_tick.sweep_failed.get() + images); + } + FaceSweepMessage::Finished { + failed, too_small, .. + } => { + // The sweep's own tallies win over the + // running ones: a cancelled or offline + // pass can end without every batch having + // been reported. + ctl_tick.sweep_failed.set(failed); + ctl_tick.sweep_too_small.set(too_small); + done = true; + } } } } let (seen, total) = ctl_tick.progress.get(); - w.set_identity_indexing_status( - format!( - "indexing {seen}/{total} — {} face(s) found", - ctl_tick.faces_found.get() - ) - .into(), + let mut status = format!( + "indexing {seen}/{total} — {} face(s) found", + ctl_tick.faces_found.get() ); + if ctl_tick.sweep_failed.get() > 0 { + status.push_str(&format!(", {} failed", ctl_tick.sweep_failed.get())); + } + w.set_identity_indexing_status(status.into()); if let Some(a) = ctl_tick.activity.borrow().as_ref() { a.progress(seen, total); } @@ -1099,10 +1132,28 @@ pub fn wire( if done { *ctl_tick.sweep.borrow_mut() = None; if let Some(a) = ctl_tick.activity.borrow_mut().take() { - a.finish(format!( + // Say what did not work. A pass that failed every + // image used to finish with the same sentence as + // one that succeeded at every image, which is how + // 169 consecutive failures went unnoticed for a + // day. + let mut msg = format!( "{} face(s) in {seen} image(s)", ctl_tick.faces_found.get() - )); + ); + if ctl_tick.sweep_failed.get() > 0 { + msg.push_str(&format!( + ", {} could not be read", + ctl_tick.sweep_failed.get() + )); + } + if ctl_tick.sweep_too_small.get() > 0 { + msg.push_str(&format!( + ", {} with no proxy big enough", + ctl_tick.sweep_too_small.get() + )); + } + a.finish(msg); } w.set_identity_indexing(false); // Newly indexed faces belong to nobody until they are diff --git a/ui/dr-ui/src/library.rs b/ui/dr-ui/src/library.rs index 32c62e5..5fdc360 100644 --- a/ui/dr-ui/src/library.rs +++ b/ui/dr-ui/src/library.rs @@ -3359,6 +3359,22 @@ pub fn spawn_face_sweep( done += attempted; failed += lane_failed; offline |= lane_offline; + // Before the indexed ones, so a screen watching this sees + // the count move for work that produced nothing. Images + // refused for size are *not* reported here: they are in + // `indexed` below, having earned a marker, and counting + // them twice would run the progress figure past its total. + if lane_failed > 0 + && tx + .send(FaceSweepMessage::Failed { + images: lane_failed, + }) + .is_err() + { + log::info!("face sweep: cancelled after {images} image(s)"); + pool.release_all(&*backend).await; + return; + } for (image_id, faces, edge, keep) in indexed { // Before the detections, so a kill between the two // leaves a proxy with no faces recorded — which the