From 5768100816d735bdab47dd62b487bbb808918a0d Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 29 Aug 2026 08:41:42 +0200 Subject: [PATCH] Borrow the library to index it, and give it back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The passes that need every photograph's bytes — thumbnails, face indexing — now borrow each one and release it at the end. On a placeholder library that is the difference between peak disk being the working set and being the whole library. Including on cancellation, which was nearly missed: the face sweep returns mid-loop when the user presses Stop, and without releasing there the disk is spent and nothing is delivered for it. `materialise` now answers whether *it* fetched the content. The pool used to work that out by listing a file's parent directory — one listing per file across a library — when the backend already had to `stat` it to decide whether to ask. One syscall instead of a directory walk, and it removes the bug class the tests found earlier: a file at the library root has no `parent()`, so every one of them read as already-downloaded. **Pinning is the retention control**, and it drives the model the catalog already had rather than a second one. `tier_desired` is what the user asked to keep hydrated, `pending_pins` is the resumable work list, and a pinned collection is never dehydrated for the same reason it was never evicted. It was in fact *broken* here before: `get` on a stub failed, and the pin worker logged "one unreadable file must not abandon the whole pin" and silently did nothing. Pinned originals on such a library are recorded with `path = NULL` (`Cache::record_in_place`) rather than copied under `originals/`. Two reasons, and the second is the important one. A copy would hold every pinned photograph twice, with the budget able to evict the half that was not costing the disk. And `release` deletes the file a row names — so a row that names none cannot delete anything, which puts the one catastrophic operation out of reach by construction rather than by remembering not to call it. Deleting a materialised file inside a synced tree removes the photograph from the server and every other device. Handing disk back is `spawn_dehydrate`, which asks the client. Two gaps written down rather than papered over (docs/storage.md §7): a hydrating pass cannot yet quote its cost, because a stub reports no size; and the two sweeps hold separate pools, so a library indexed for both fetches twice. --- core/dr-catalog/src/cache.rs | 50 ++++++ core/dr-sync-folder/src/borrow.rs | 73 +-------- core/dr-sync-folder/src/borrow/tests.rs | 52 ++++-- core/dr-sync-folder/src/lib.rs | 10 +- core/dr-sync/src/lib.rs | 13 +- docs/storage.md | 47 ++++++ docs/traceability.md | 50 +++--- ui/dr-ui/src/library.rs | 206 +++++++++++++++++++++++- ui/dr-ui/src/library_ui.rs | 19 ++- 9 files changed, 406 insertions(+), 114 deletions(-) diff --git a/core/dr-catalog/src/cache.rs b/core/dr-catalog/src/cache.rs index c763aa4..25c8803 100644 --- a/core/dr-catalog/src/cache.rs +++ b/core/dr-catalog/src/cache.rs @@ -222,6 +222,56 @@ impl Cache { Ok(()) } + /// TRACES: FR-NC-6c | FR-NC-6a + /// Record an original this cache does **not** own the bytes of. + /// + /// The virtual-filesystem case. On a library kept by a sync client the + /// original is materialised *in the library folder itself*, so copying it + /// under `originals/` would hold two copies of every pinned photograph — + /// and the copy would be the one the budget could evict while the real + /// disk cost stayed. + /// + /// So the bytes are left where they are and only the bookkeeping is kept. + /// `path` is deliberately `NULL`, which is what makes this safe: + /// [`release`](Self::release) deletes the file a row names, and a row that + /// names none deletes nothing. **That matters more than it sounds.** + /// Deleting a materialised file inside a synced folder does not free a + /// cache — it deletes the photograph, and the client propagates that to + /// the server and to every other device. Handing the disk back is the + /// backend's job (`RemoteBackend::dematerialise`), not this one's. + /// + /// `bytes` is what the original occupies where it lies, for the budget and + /// for reporting; pass 0 where it is not known. + pub fn record_in_place( + &self, + conn: &Connection, + image: ImageId, + bytes: u64, + pinned: bool, + now: i64, + ) -> Result<(), CatalogError> { + conn.execute( + "INSERT INTO image_cache + (image_id, tier_actual, tier_desired, bytes, last_used, pinned, path) + VALUES (?1, ?2, ?2, ?3, ?4, ?5, NULL) + ON CONFLICT(image_id) DO UPDATE SET + tier_actual = ?2, + tier_desired = max(tier_desired, ?2), + bytes = ?3, + last_used = ?4, + pinned = max(pinned, ?5), + path = NULL", + rusqlite::params![ + image.0 as i64, + Tier::Original.stored(), + bytes as i64, + now, + i64::from(pinned), + ], + )?; + Ok(()) + } + /// Read a cached original back, if it is here. /// /// Touches `last_used`, which is what makes the eviction order reflect diff --git a/core/dr-sync-folder/src/borrow.rs b/core/dr-sync-folder/src/borrow.rs index b3e9006..f48526a 100644 --- a/core/dr-sync-folder/src/borrow.rs +++ b/core/dr-sync-folder/src/borrow.rs @@ -85,27 +85,6 @@ impl BorrowPool { &'p self, backend: &dyn RemoteBackend, path: &RemotePath, - ) -> Result, RemoteError> { - self.borrow_known(backend, path, None).await - } - - /// [`borrow`](Self::borrow) where the caller already knows whether the - /// content is here. - /// - /// A sweep reads availability out of the catalog on its way to building a - /// work list, so making the pool re-derive it costs a directory listing - /// per file for an answer already in hand. - /// - /// **`None` means "find out", and being wrong is not symmetric.** Claiming - /// a file was already local leaves it downloaded — disk, and nothing else. - /// Claiming it was not releases a file the user may have pinned. So an - /// uncertain caller passes `Some(true)` or `None`, never a guess at - /// `false`. - pub async fn borrow_known<'p>( - &'p self, - backend: &dyn RemoteBackend, - path: &RemotePath, - already_local: Option, ) -> Result, RemoteError> { if !backend.capabilities().materialisation.can_materialise() { return Ok(Borrowed { @@ -129,32 +108,19 @@ impl BorrowPool { } } - let id = RemoteId::Path(path.clone()); + // The backend answers whether *it* fetched the content, because it had + // to look before deciding. Determining that here instead would cost a + // directory listing per file, and getting it wrong in the wrong + // direction releases a file the user pinned. + let ours = backend.materialise(&RemoteId::Path(path.clone())).await?; - // What was here *before* we asked. The whole contract rests on this - // being read first: after `materialise` there is no way to tell what - // we brought from what was already there. - let was_local = match already_local { - Some(known) => known, - None => is_materialised(backend, path).await, - }; - - if !was_local { - backend.materialise(&id).await?; - } - - self.lock().insert( - path.clone(), - Held { - borrowers: 1, - ours: !was_local, - }, - ); + self.lock() + .insert(path.clone(), Held { borrowers: 1, ours }); Ok(Borrowed { pool: Some(self), path: path.clone(), - hydrated: !was_local, + hydrated: ours, }) } @@ -237,28 +203,5 @@ impl Drop for Borrowed<'_> { } } -/// Whether the backend currently holds this file's content. -/// -/// Asked by listing its parent, because the trait has no "stat one object" — -/// and adding one for this would put a method on every backend to serve a case -/// only one of them has. -async fn is_materialised(backend: &dyn RemoteBackend, path: &RemotePath) -> bool { - // `parent()` is `None` for a file directly under the library root, whose - // parent *is* the root — not "no parent". Treating the two alike reported - // every top-level photograph as already downloaded, so nothing was ever - // hydrated and nothing was ever released. - let parent = path.parent().unwrap_or_else(RemotePath::root); - match backend.list(&parent, None).await { - Ok(entries) => entries - .iter() - .find(|e| &e.path == path) - .map(|e| e.materialised) - // Not listed at all: nothing to hydrate, and the caller's own read - // will report the miss properly. - .unwrap_or(true), - Err(_) => true, - } -} - #[cfg(test)] mod tests; diff --git a/core/dr-sync-folder/src/borrow/tests.rs b/core/dr-sync-folder/src/borrow/tests.rs index 0baff06..9b8778b 100644 --- a/core/dr-sync-folder/src/borrow/tests.rs +++ b/core/dr-sync-folder/src/borrow/tests.rs @@ -221,22 +221,46 @@ async fn borrowing_against_a_plain_folder_does_nothing_at_all() { } #[tokio::test] -async fn an_uncertain_caller_keeps_the_file_rather_than_releasing_it() { - // The asymmetry stated on `borrow_known`: claiming "already local" costs - // disk, claiming "not local" can release a pin. - let t = Tmp::new("uncertain"); - t.real("a.CR2"); +async fn the_backend_is_what_decides_whether_a_file_was_ours() { + // Not the pool, and not the caller. The backend had to look before + // deciding whether to ask, so it can answer for the cost of that same + // `stat`; a borrower working it out separately would pay a directory + // listing per file and could get it wrong in the direction that releases + // a file the user pinned. + let t = Tmp::new("who-decides"); + t.real("had.CR2").stub("wanted.CR2"); + let b = t.backend(FakeClient::new()); + + assert!( + !b.materialise(&RemoteId::Path(RemotePath::new("had.CR2"))) + .await + .unwrap(), + "already here, so not ours to release" + ); + assert!( + b.materialise(&RemoteId::Path(RemotePath::new("wanted.CR2"))) + .await + .unwrap(), + "this call fetched it" + ); +} + +#[tokio::test] +async fn a_file_borrowed_twice_in_one_pass_is_fetched_once_and_released_once() { + // Thumbnailing and face indexing visit the same photograph. Fetching it + // per stage doubles the transfer over the whole library. + let t = Tmp::new("sequential"); + t.stub("a.CR2"); let client = FakeClient::new(); let b = t.backend(client.clone()); let pool = BorrowPool::new(); + let path = RemotePath::new("a.CR2"); - { - let _held = pool - .borrow_known(&b, &RemotePath::new("a.CR2"), Some(true)) - .await - .unwrap(); - } - pool.release_all(&b).await; - assert!(t.has("a.CR2"), "kept"); - assert_eq!(client.dehydrations.load(Ordering::SeqCst), 0); + // Sequential borrows, as two passes over one work list would make. + drop(pool.borrow(&b, &path).await.unwrap()); + drop(pool.borrow(&b, &path).await.unwrap()); + + assert_eq!(client.hydrations.load(Ordering::SeqCst), 1, "paid once"); + let stats = pool.release_all(&b).await; + assert_eq!(stats.released, 1, "given back once"); } diff --git a/core/dr-sync-folder/src/lib.rs b/core/dr-sync-folder/src/lib.rs index a55a571..f69675f 100644 --- a/core/dr-sync-folder/src/lib.rs +++ b/core/dr-sync-folder/src/lib.rs @@ -773,12 +773,12 @@ impl RemoteBackend for FolderBackend { /// Suffix-mode VFS *renames* on hydration, so completion is the /// materialised path appearing — not the stub changing size. Polling the /// original would wait forever. - async fn materialise(&self, id: &RemoteId) -> Result<(), RemoteError> { + async fn materialise(&self, id: &RemoteId) -> Result { let (local, materialised) = self.locate_id(id)?; if materialised { - // Already here. Not an error, and not a reason to ask again: the - // borrow pool relies on this being idempotent. - return Ok(()); + // Already here. Not an error, and not a reason to ask again — and + // `false` is what tells a borrower to leave it alone afterwards. + return Ok(false); } let vfs = self.vfs.clone(); let target = self.resolve_id(id)?; @@ -793,7 +793,7 @@ impl RemoteBackend for FolderBackend { let deadline = std::time::Instant::now() + MATERIALISE_TIMEOUT; while std::time::Instant::now() < deadline { if target.is_file() { - return Ok(()); + return Ok(true); } std::thread::sleep(POLL); } diff --git a/core/dr-sync/src/lib.rs b/core/dr-sync/src/lib.rs index 60dc98a..51e6bce 100644 --- a/core/dr-sync/src/lib.rs +++ b/core/dr-sync/src/lib.rs @@ -170,9 +170,16 @@ pub trait RemoteBackend: Send + Sync { /// agreed to. **Never for filling a grid**: doing so downloads the entire /// library to produce thumbnails. /// - /// Returns once the content is readable. Callers that borrowed it should - /// hand it back with [`dematerialise`](Self::dematerialise). - async fn materialise(&self, _id: &RemoteId) -> Result<(), RemoteError> { + /// Returns once the content is readable, and **whether this call is what + /// brought it here** — `false` meaning it was already local. + /// + /// That boolean is the whole basis of borrowing. A caller releasing what + /// it fetched must not release what the user already had, and after the + /// fact the two are indistinguishable; the backend knows because it had to + /// look before deciding whether to ask. Answering it here costs the `stat` + /// the implementation performs anyway, where a caller determining it + /// separately would pay a directory listing per file. + async fn materialise(&self, _id: &RemoteId) -> Result { Err(RemoteError::Unsupported( "this backend has no placeholders to materialise", )) diff --git a/docs/storage.md b/docs/storage.md index ee9decd..6d867a6 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -477,6 +477,40 @@ This is also why the originals cache (`dr_catalog::cache`) cannot simply be pointed at a VFS library: `Cache::release` deletes bytes, which is right for a copy under `originals/` and catastrophic in place. +### 6.5 Which photographs stay downloaded + +The user's half of the bargain: a pass borrows for a moment, but *some* of the +library should stay local — the trip you are about to take, the shoot you are +working on. + +That is a **pin**, and it is the pin the originals cache already had +(`dr_catalog::cache`, FR-NC-6a). Nothing parallel was built, because the model +was already the right one: + +| Cache concept | On a placeholder library | +|---|---| +| `tier_desired` | what the user asked to keep hydrated | +| `tier_actual` | what is actually materialised | +| `pending_pins()` | the work list — what to hydrate next, resumable | +| pinned rows are never evicted | a pinned collection is never dehydrated | +| passive rows, LRU under a budget | what a pass borrowed, released when it finishes | + +So "keep this collection hydrated" is `Cache::pin`, and the existing pin worker +drives it — except that on a placeholder library it calls `materialise` instead +of downloading a copy. + +**Why not a copy.** The original materialises *in the library folder*. Copying +it under `originals/` as well would hold every pinned photograph twice, and the +copy would be the half the budget could evict while the real disk cost stayed. +`Cache::record_in_place` records the bookkeeping with **`path = NULL`**, and +that null is load-bearing: `release` deletes the file a row names, and a row +that names none deletes nothing. The safety property is structural rather than +remembered. + +Unpinning therefore frees nothing by itself — the bytes are not ours to delete. +`spawn_dehydrate` asks the client to take them back, which is what actually +returns the disk. + --- ## 7. What the abstraction does not yet cover @@ -495,3 +529,16 @@ Stated so the next person does not have to rediscover it. - **A general notion of an account.** Credentials stay connector-specific on purpose (§3.5). A third connector with an OAuth flow will need a third `SignIn` variant, and that is the right place for it to appear. +- **A quoted cost before a hydrating pass.** FR-NC-6c wants the transfer size + stated before an operation that needs absent data. A placeholder reports no + size (§6.2), so the honest figure for "index this library" is a count and not + a byte total. The interface should say *n photographs, size unknown until + fetched* rather than estimate one silently — and it does not say anything yet. +- **Metadata-only placeholders.** Windows and macOS express these in filesystem + metadata rather than in the name, and carry the real size there. `Vfs` asks + its questions about a *name*, which is all the one convention this project has + met needs. Supporting them means widening the trait to take a `Metadata`, and + doing that before anyone has run this on those platforms would be guessing. +- **Hydration during browsing, deliberately.** It stays forbidden (ARCH §9.0 + finding 3). A grid cell whose content is absent shows as not-downloaded; only + a pass the user asked for may fetch. diff --git a/docs/traceability.md b/docs/traceability.md index 0f31d0a..1016208 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| | Source files scanned | 286 | -| TRACES tags found | 834 | +| TRACES tags found | 840 | | Requirements defined | 179 | | Requirements covered | 107 | | **Coverage** | **59.8%** (107/179) | @@ -35,26 +35,26 @@ _None._ |---|---| | FR-CAT-1 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:109`](../core/dr-catalog/src/walk.rs#L109), [`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-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`core/dr-types/src/lib.rs:200`](../core/dr-types/src/lib.rs#L200), [`core/dr-types/src/lib.rs:269`](../core/dr-types/src/lib.rs#L269), [`core/dr-types/src/lib.rs:302`](../core/dr-types/src/lib.rs#L302), [`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), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479), [`tools/traceability/src/lib.rs:511`](../tools/traceability/src/lib.rs#L511), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1) | | 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:116`](../core/dr-types/src/settings.rs#L116), [`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:1123`](../ui/dr-ui/src/lib.rs#L1123), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1014`](../ui/dr-ui/ui/library.slint#L1014), [`ui/dr-ui/ui/library.slint:1176`](../ui/dr-ui/ui/library.slint#L1176), [`ui/dr-ui/ui/library.slint:863`](../ui/dr-ui/ui/library.slint#L863) | -| 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:1123`](../ui/dr-ui/src/lib.rs#L1123), [`ui/dr-ui/src/library.rs:162`](../ui/dr-ui/src/library.rs#L162), [`ui/dr-ui/src/library.rs:2257`](../ui/dr-ui/src/library.rs#L2257), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | +| 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:1123`](../ui/dr-ui/src/lib.rs#L1123), [`ui/dr-ui/src/library.rs:162`](../ui/dr-ui/src/library.rs#L162), [`ui/dr-ui/src/library.rs:2375`](../ui/dr-ui/src/library.rs#L2375), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | | FR-CAT-12 | [`core/dr-pipeline/src/sidecar.rs:118`](../core/dr-pipeline/src/sidecar.rs#L118) | | FR-CAT-13 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1) | -| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:641`](../core/dr-catalog/src/schema.rs#L641), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:456`](../core/dr-sync-nextcloud/src/lib.rs#L456), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:429`](../core/dr-sync/src/scan.rs#L429), [`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:1219`](../ui/dr-ui/src/collections_ui.rs#L1219), [`ui/dr-ui/src/collections_ui.rs:1975`](../ui/dr-ui/src/collections_ui.rs#L1975), [`ui/dr-ui/src/library.rs:162`](../ui/dr-ui/src/library.rs#L162), [`ui/dr-ui/src/library.rs:179`](../ui/dr-ui/src/library.rs#L179), [`ui/dr-ui/src/library.rs:208`](../ui/dr-ui/src/library.rs#L208), [`ui/dr-ui/src/library.rs:3665`](../ui/dr-ui/src/library.rs#L3665), [`ui/dr-ui/src/library.rs:3699`](../ui/dr-ui/src/library.rs#L3699), [`ui/dr-ui/src/library_ui.rs:190`](../ui/dr-ui/src/library_ui.rs#L190), [`ui/dr-ui/src/library_ui.rs:756`](../ui/dr-ui/src/library_ui.rs#L756), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:572`](../ui/dr-ui/ui/collections.slint#L572) | +| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:641`](../core/dr-catalog/src/schema.rs#L641), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:456`](../core/dr-sync-nextcloud/src/lib.rs#L456), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:429`](../core/dr-sync/src/scan.rs#L429), [`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:1219`](../ui/dr-ui/src/collections_ui.rs#L1219), [`ui/dr-ui/src/collections_ui.rs:1975`](../ui/dr-ui/src/collections_ui.rs#L1975), [`ui/dr-ui/src/library.rs:162`](../ui/dr-ui/src/library.rs#L162), [`ui/dr-ui/src/library.rs:179`](../ui/dr-ui/src/library.rs#L179), [`ui/dr-ui/src/library.rs:208`](../ui/dr-ui/src/library.rs#L208), [`ui/dr-ui/src/library.rs:3869`](../ui/dr-ui/src/library.rs#L3869), [`ui/dr-ui/src/library.rs:3903`](../ui/dr-ui/src/library.rs#L3903), [`ui/dr-ui/src/library_ui.rs:190`](../ui/dr-ui/src/library_ui.rs#L190), [`ui/dr-ui/src/library_ui.rs:756`](../ui/dr-ui/src/library_ui.rs#L756), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:572`](../ui/dr-ui/ui/collections.slint#L572) | | FR-CAT-1a | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:53`](../core/dr-types/src/lib.rs#L53), [`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), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479) | -| 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:2730`](../ui/dr-ui/src/library.rs#L2730), [`ui/dr-ui/src/library.rs:3200`](../ui/dr-ui/src/library.rs#L3200), [`ui/dr-ui/src/library_ui.rs:172`](../ui/dr-ui/src/library_ui.rs#L172), [`ui/dr-ui/src/library_ui.rs:3610`](../ui/dr-ui/src/library_ui.rs#L3610), [`ui/dr-ui/src/library_ui.rs:4575`](../ui/dr-ui/src/library_ui.rs#L4575), [`ui/dr-ui/ui/app.slint:319`](../ui/dr-ui/ui/app.slint#L319), [`ui/dr-ui/ui/settings.slint:372`](../ui/dr-ui/ui/settings.slint#L372), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) | +| 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:2848`](../ui/dr-ui/src/library.rs#L2848), [`ui/dr-ui/src/library.rs:3356`](../ui/dr-ui/src/library.rs#L3356), [`ui/dr-ui/src/library_ui.rs:172`](../ui/dr-ui/src/library_ui.rs#L172), [`ui/dr-ui/src/library_ui.rs:3627`](../ui/dr-ui/src/library_ui.rs#L3627), [`ui/dr-ui/src/library_ui.rs:4592`](../ui/dr-ui/src/library_ui.rs#L4592), [`ui/dr-ui/ui/app.slint:319`](../ui/dr-ui/ui/app.slint#L319), [`ui/dr-ui/ui/settings.slint:372`](../ui/dr-ui/ui/settings.slint#L372), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) | | 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:318`](../core/dr-catalog/src/schema.rs#L318), [`ui/dr-ui/src/library.rs:189`](../ui/dr-ui/src/library.rs#L189), [`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) | -| 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:1059`](../core/dr-catalog/src/schema.rs#L1059), [`core/dr-catalog/src/schema.rs:556`](../core/dr-catalog/src/schema.rs#L556), [`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:135`](../core/dr-pipeline/src/sidecar.rs#L135), [`ui/dr-ui/src/collections_ui.rs:1558`](../ui/dr-ui/src/collections_ui.rs#L1558), [`ui/dr-ui/src/collections_ui.rs:1577`](../ui/dr-ui/src/collections_ui.rs#L1577), [`ui/dr-ui/src/collections_ui.rs:243`](../ui/dr-ui/src/collections_ui.rs#L243), [`ui/dr-ui/src/collections_ui.rs:340`](../ui/dr-ui/src/collections_ui.rs#L340), [`ui/dr-ui/src/collections_ui.rs:89`](../ui/dr-ui/src/collections_ui.rs#L89), [`ui/dr-ui/src/library.rs:3713`](../ui/dr-ui/src/library.rs#L3713), [`ui/dr-ui/src/library_ui.rs:631`](../ui/dr-ui/src/library_ui.rs#L631), [`ui/dr-ui/src/library_ui.rs:6372`](../ui/dr-ui/src/library_ui.rs#L6372), [`ui/dr-ui/src/library_ui.rs:6383`](../ui/dr-ui/src/library_ui.rs#L6383), [`ui/dr-ui/src/library_ui.rs:6396`](../ui/dr-ui/src/library_ui.rs#L6396), [`ui/dr-ui/src/library_ui.rs:6411`](../ui/dr-ui/src/library_ui.rs#L6411), [`ui/dr-ui/src/library_ui.rs:6420`](../ui/dr-ui/src/library_ui.rs#L6420), [`ui/dr-ui/ui/app.slint:264`](../ui/dr-ui/ui/app.slint#L264), [`ui/dr-ui/ui/app.slint:445`](../ui/dr-ui/ui/app.slint#L445), [`ui/dr-ui/ui/library.slint:1225`](../ui/dr-ui/ui/library.slint#L1225), [`ui/dr-ui/ui/library.slint:1228`](../ui/dr-ui/ui/library.slint#L1228), [`ui/dr-ui/ui/library.slint:18`](../ui/dr-ui/ui/library.slint#L18), [`ui/dr-ui/ui/library.slint:856`](../ui/dr-ui/ui/library.slint#L856), [`ui/dr-ui/ui/library.slint:908`](../ui/dr-ui/ui/library.slint#L908) | -| 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:556`](../core/dr-catalog/src/schema.rs#L556), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:63`](../core/dr-types/src/settings.rs#L63), [`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:213`](../ui/dr-ui/src/library.rs#L213), [`ui/dr-ui/src/library.rs:3959`](../ui/dr-ui/src/library.rs#L3959), [`ui/dr-ui/src/library_ui.rs:321`](../ui/dr-ui/src/library_ui.rs#L321), [`ui/dr-ui/src/library_ui.rs:393`](../ui/dr-ui/src/library_ui.rs#L393), [`ui/dr-ui/src/library_ui.rs:5186`](../ui/dr-ui/src/library_ui.rs#L5186), [`ui/dr-ui/src/library_ui.rs:5239`](../ui/dr-ui/src/library_ui.rs#L5239), [`ui/dr-ui/src/library_ui.rs:6512`](../ui/dr-ui/src/library_ui.rs#L6512), [`ui/dr-ui/ui/app.slint:267`](../ui/dr-ui/ui/app.slint#L267), [`ui/dr-ui/ui/app.slint:445`](../ui/dr-ui/ui/app.slint#L445), [`ui/dr-ui/ui/app.slint:690`](../ui/dr-ui/ui/app.slint#L690), [`ui/dr-ui/ui/library.slint:109`](../ui/dr-ui/ui/library.slint#L109), [`ui/dr-ui/ui/library.slint:1301`](../ui/dr-ui/ui/library.slint#L1301), [`ui/dr-ui/ui/library.slint:908`](../ui/dr-ui/ui/library.slint#L908), [`ui/dr-ui/ui/settings.slint:147`](../ui/dr-ui/ui/settings.slint#L147) | -| 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:1673`](../ui/dr-ui/src/collections_ui.rs#L1673), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library_ui.rs:3472`](../ui/dr-ui/src/library_ui.rs#L3472), [`ui/dr-ui/src/library_ui.rs:4165`](../ui/dr-ui/src/library_ui.rs#L4165), [`ui/dr-ui/ui/app.slint:440`](../ui/dr-ui/ui/app.slint#L440), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2564`](../ui/dr-ui/ui/library.slint#L2564), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898) | -| FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:345`](../core/dr-pipeline/src/graph.rs#L345), [`core/dr-pipeline/src/graph.rs:384`](../core/dr-pipeline/src/graph.rs#L384), [`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:1636`](../core/dr-pipeline/src/sidecar.rs#L1636), [`core/dr-pipeline/src/sidecar.rs:92`](../core/dr-pipeline/src/sidecar.rs#L92), [`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:3345`](../ui/dr-ui/src/develop.rs#L3345), [`ui/dr-ui/src/develop.rs:3374`](../ui/dr-ui/src/develop.rs#L3374), [`ui/dr-ui/src/export.rs:750`](../ui/dr-ui/src/export.rs#L750), [`ui/dr-ui/src/lib.rs:1376`](../ui/dr-ui/src/lib.rs#L1376), [`ui/dr-ui/src/lib.rs:1777`](../ui/dr-ui/src/lib.rs#L1777), [`ui/dr-ui/src/lib.rs:1912`](../ui/dr-ui/src/lib.rs#L1912), [`ui/dr-ui/src/lib.rs:501`](../ui/dr-ui/src/lib.rs#L501), [`ui/dr-ui/src/lib.rs:926`](../ui/dr-ui/src/lib.rs#L926), [`ui/dr-ui/src/library.rs:1669`](../ui/dr-ui/src/library.rs#L1669), [`ui/dr-ui/src/library.rs:459`](../ui/dr-ui/src/library.rs#L459), [`ui/dr-ui/src/library.rs:506`](../ui/dr-ui/src/library.rs#L506), [`ui/dr-ui/src/library.rs:543`](../ui/dr-ui/src/library.rs#L543), [`ui/dr-ui/src/library.rs:790`](../ui/dr-ui/src/library.rs#L790), [`ui/dr-ui/src/library_ui.rs:4867`](../ui/dr-ui/src/library_ui.rs#L4867), [`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:613`](../core/dr-catalog/src/schema.rs#L613), [`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:704`](../core/dr-catalog/src/walk.rs#L704), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`ui/dr-ui/src/develop.rs:2870`](../ui/dr-ui/src/develop.rs#L2870), [`ui/dr-ui/src/library.rs:148`](../ui/dr-ui/src/library.rs#L148), [`ui/dr-ui/src/library.rs:1629`](../ui/dr-ui/src/library.rs#L1629), [`ui/dr-ui/src/library.rs:1705`](../ui/dr-ui/src/library.rs#L1705), [`ui/dr-ui/src/library.rs:233`](../ui/dr-ui/src/library.rs#L233), [`ui/dr-ui/src/library.rs:4066`](../ui/dr-ui/src/library.rs#L4066), [`ui/dr-ui/src/library.rs:543`](../ui/dr-ui/src/library.rs#L543), [`ui/dr-ui/src/library.rs:774`](../ui/dr-ui/src/library.rs#L774), [`ui/dr-ui/src/library.rs:790`](../ui/dr-ui/src/library.rs#L790), [`ui/dr-ui/src/library.rs:844`](../ui/dr-ui/src/library.rs#L844), [`ui/dr-ui/src/library_ui.rs:1563`](../ui/dr-ui/src/library_ui.rs#L1563), [`ui/dr-ui/src/library_ui.rs:1589`](../ui/dr-ui/src/library_ui.rs#L1589), [`ui/dr-ui/src/library_ui.rs:1605`](../ui/dr-ui/src/library_ui.rs#L1605), [`ui/dr-ui/src/library_ui.rs:1699`](../ui/dr-ui/src/library_ui.rs#L1699), [`ui/dr-ui/src/library_ui.rs:2315`](../ui/dr-ui/src/library_ui.rs#L2315), [`ui/dr-ui/src/library_ui.rs:232`](../ui/dr-ui/src/library_ui.rs#L232), [`ui/dr-ui/src/library_ui.rs:265`](../ui/dr-ui/src/library_ui.rs#L265), [`ui/dr-ui/src/library_ui.rs:2753`](../ui/dr-ui/src/library_ui.rs#L2753), [`ui/dr-ui/src/library_ui.rs:2975`](../ui/dr-ui/src/library_ui.rs#L2975), [`ui/dr-ui/src/library_ui.rs:3253`](../ui/dr-ui/src/library_ui.rs#L3253), [`ui/dr-ui/src/library_ui.rs:3341`](../ui/dr-ui/src/library_ui.rs#L3341), [`ui/dr-ui/src/library_ui.rs:3523`](../ui/dr-ui/src/library_ui.rs#L3523), [`ui/dr-ui/src/library_ui.rs:3637`](../ui/dr-ui/src/library_ui.rs#L3637), [`ui/dr-ui/src/library_ui.rs:446`](../ui/dr-ui/src/library_ui.rs#L446), [`ui/dr-ui/src/library_ui.rs:504`](../ui/dr-ui/src/library_ui.rs#L504), [`ui/dr-ui/src/library_ui.rs:5284`](../ui/dr-ui/src/library_ui.rs#L5284), [`ui/dr-ui/src/library_ui.rs:5399`](../ui/dr-ui/src/library_ui.rs#L5399), [`ui/dr-ui/src/presets.rs:326`](../ui/dr-ui/src/presets.rs#L326), [`ui/dr-ui/src/presets.rs:338`](../ui/dr-ui/src/presets.rs#L338), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.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:1059`](../core/dr-catalog/src/schema.rs#L1059), [`core/dr-catalog/src/schema.rs:556`](../core/dr-catalog/src/schema.rs#L556), [`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:135`](../core/dr-pipeline/src/sidecar.rs#L135), [`ui/dr-ui/src/collections_ui.rs:1558`](../ui/dr-ui/src/collections_ui.rs#L1558), [`ui/dr-ui/src/collections_ui.rs:1577`](../ui/dr-ui/src/collections_ui.rs#L1577), [`ui/dr-ui/src/collections_ui.rs:243`](../ui/dr-ui/src/collections_ui.rs#L243), [`ui/dr-ui/src/collections_ui.rs:340`](../ui/dr-ui/src/collections_ui.rs#L340), [`ui/dr-ui/src/collections_ui.rs:89`](../ui/dr-ui/src/collections_ui.rs#L89), [`ui/dr-ui/src/library.rs:3917`](../ui/dr-ui/src/library.rs#L3917), [`ui/dr-ui/src/library_ui.rs:631`](../ui/dr-ui/src/library_ui.rs#L631), [`ui/dr-ui/src/library_ui.rs:6389`](../ui/dr-ui/src/library_ui.rs#L6389), [`ui/dr-ui/src/library_ui.rs:6400`](../ui/dr-ui/src/library_ui.rs#L6400), [`ui/dr-ui/src/library_ui.rs:6413`](../ui/dr-ui/src/library_ui.rs#L6413), [`ui/dr-ui/src/library_ui.rs:6428`](../ui/dr-ui/src/library_ui.rs#L6428), [`ui/dr-ui/src/library_ui.rs:6437`](../ui/dr-ui/src/library_ui.rs#L6437), [`ui/dr-ui/ui/app.slint:264`](../ui/dr-ui/ui/app.slint#L264), [`ui/dr-ui/ui/app.slint:445`](../ui/dr-ui/ui/app.slint#L445), [`ui/dr-ui/ui/library.slint:1225`](../ui/dr-ui/ui/library.slint#L1225), [`ui/dr-ui/ui/library.slint:1228`](../ui/dr-ui/ui/library.slint#L1228), [`ui/dr-ui/ui/library.slint:18`](../ui/dr-ui/ui/library.slint#L18), [`ui/dr-ui/ui/library.slint:856`](../ui/dr-ui/ui/library.slint#L856), [`ui/dr-ui/ui/library.slint:908`](../ui/dr-ui/ui/library.slint#L908) | +| 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:556`](../core/dr-catalog/src/schema.rs#L556), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:63`](../core/dr-types/src/settings.rs#L63), [`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:213`](../ui/dr-ui/src/library.rs#L213), [`ui/dr-ui/src/library.rs:4163`](../ui/dr-ui/src/library.rs#L4163), [`ui/dr-ui/src/library_ui.rs:321`](../ui/dr-ui/src/library_ui.rs#L321), [`ui/dr-ui/src/library_ui.rs:393`](../ui/dr-ui/src/library_ui.rs#L393), [`ui/dr-ui/src/library_ui.rs:5203`](../ui/dr-ui/src/library_ui.rs#L5203), [`ui/dr-ui/src/library_ui.rs:5256`](../ui/dr-ui/src/library_ui.rs#L5256), [`ui/dr-ui/src/library_ui.rs:6529`](../ui/dr-ui/src/library_ui.rs#L6529), [`ui/dr-ui/ui/app.slint:267`](../ui/dr-ui/ui/app.slint#L267), [`ui/dr-ui/ui/app.slint:445`](../ui/dr-ui/ui/app.slint#L445), [`ui/dr-ui/ui/app.slint:690`](../ui/dr-ui/ui/app.slint#L690), [`ui/dr-ui/ui/library.slint:109`](../ui/dr-ui/ui/library.slint#L109), [`ui/dr-ui/ui/library.slint:1301`](../ui/dr-ui/ui/library.slint#L1301), [`ui/dr-ui/ui/library.slint:908`](../ui/dr-ui/ui/library.slint#L908), [`ui/dr-ui/ui/settings.slint:147`](../ui/dr-ui/ui/settings.slint#L147) | +| 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:1673`](../ui/dr-ui/src/collections_ui.rs#L1673), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library_ui.rs:3489`](../ui/dr-ui/src/library_ui.rs#L3489), [`ui/dr-ui/src/library_ui.rs:4182`](../ui/dr-ui/src/library_ui.rs#L4182), [`ui/dr-ui/ui/app.slint:440`](../ui/dr-ui/ui/app.slint#L440), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2564`](../ui/dr-ui/ui/library.slint#L2564), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898) | +| FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:345`](../core/dr-pipeline/src/graph.rs#L345), [`core/dr-pipeline/src/graph.rs:384`](../core/dr-pipeline/src/graph.rs#L384), [`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:1636`](../core/dr-pipeline/src/sidecar.rs#L1636), [`core/dr-pipeline/src/sidecar.rs:92`](../core/dr-pipeline/src/sidecar.rs#L92), [`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:3345`](../ui/dr-ui/src/develop.rs#L3345), [`ui/dr-ui/src/develop.rs:3374`](../ui/dr-ui/src/develop.rs#L3374), [`ui/dr-ui/src/export.rs:750`](../ui/dr-ui/src/export.rs#L750), [`ui/dr-ui/src/lib.rs:1376`](../ui/dr-ui/src/lib.rs#L1376), [`ui/dr-ui/src/lib.rs:1777`](../ui/dr-ui/src/lib.rs#L1777), [`ui/dr-ui/src/lib.rs:1912`](../ui/dr-ui/src/lib.rs#L1912), [`ui/dr-ui/src/lib.rs:501`](../ui/dr-ui/src/lib.rs#L501), [`ui/dr-ui/src/lib.rs:926`](../ui/dr-ui/src/lib.rs#L926), [`ui/dr-ui/src/library.rs:1787`](../ui/dr-ui/src/library.rs#L1787), [`ui/dr-ui/src/library.rs:459`](../ui/dr-ui/src/library.rs#L459), [`ui/dr-ui/src/library.rs:506`](../ui/dr-ui/src/library.rs#L506), [`ui/dr-ui/src/library.rs:543`](../ui/dr-ui/src/library.rs#L543), [`ui/dr-ui/src/library.rs:790`](../ui/dr-ui/src/library.rs#L790), [`ui/dr-ui/src/library_ui.rs:4884`](../ui/dr-ui/src/library_ui.rs#L4884), [`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:613`](../core/dr-catalog/src/schema.rs#L613), [`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:704`](../core/dr-catalog/src/walk.rs#L704), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`ui/dr-ui/src/develop.rs:2870`](../ui/dr-ui/src/develop.rs#L2870), [`ui/dr-ui/src/library.rs:148`](../ui/dr-ui/src/library.rs#L148), [`ui/dr-ui/src/library.rs:1747`](../ui/dr-ui/src/library.rs#L1747), [`ui/dr-ui/src/library.rs:1823`](../ui/dr-ui/src/library.rs#L1823), [`ui/dr-ui/src/library.rs:233`](../ui/dr-ui/src/library.rs#L233), [`ui/dr-ui/src/library.rs:4270`](../ui/dr-ui/src/library.rs#L4270), [`ui/dr-ui/src/library.rs:543`](../ui/dr-ui/src/library.rs#L543), [`ui/dr-ui/src/library.rs:774`](../ui/dr-ui/src/library.rs#L774), [`ui/dr-ui/src/library.rs:790`](../ui/dr-ui/src/library.rs#L790), [`ui/dr-ui/src/library.rs:844`](../ui/dr-ui/src/library.rs#L844), [`ui/dr-ui/src/library_ui.rs:1580`](../ui/dr-ui/src/library_ui.rs#L1580), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:1622`](../ui/dr-ui/src/library_ui.rs#L1622), [`ui/dr-ui/src/library_ui.rs:1716`](../ui/dr-ui/src/library_ui.rs#L1716), [`ui/dr-ui/src/library_ui.rs:232`](../ui/dr-ui/src/library_ui.rs#L232), [`ui/dr-ui/src/library_ui.rs:2332`](../ui/dr-ui/src/library_ui.rs#L2332), [`ui/dr-ui/src/library_ui.rs:265`](../ui/dr-ui/src/library_ui.rs#L265), [`ui/dr-ui/src/library_ui.rs:2770`](../ui/dr-ui/src/library_ui.rs#L2770), [`ui/dr-ui/src/library_ui.rs:2992`](../ui/dr-ui/src/library_ui.rs#L2992), [`ui/dr-ui/src/library_ui.rs:3270`](../ui/dr-ui/src/library_ui.rs#L3270), [`ui/dr-ui/src/library_ui.rs:3358`](../ui/dr-ui/src/library_ui.rs#L3358), [`ui/dr-ui/src/library_ui.rs:3540`](../ui/dr-ui/src/library_ui.rs#L3540), [`ui/dr-ui/src/library_ui.rs:3654`](../ui/dr-ui/src/library_ui.rs#L3654), [`ui/dr-ui/src/library_ui.rs:446`](../ui/dr-ui/src/library_ui.rs#L446), [`ui/dr-ui/src/library_ui.rs:504`](../ui/dr-ui/src/library_ui.rs#L504), [`ui/dr-ui/src/library_ui.rs:5301`](../ui/dr-ui/src/library_ui.rs#L5301), [`ui/dr-ui/src/library_ui.rs:5416`](../ui/dr-ui/src/library_ui.rs#L5416), [`ui/dr-ui/src/presets.rs:326`](../ui/dr-ui/src/presets.rs#L326), [`ui/dr-ui/src/presets.rs:338`](../ui/dr-ui/src/presets.rs#L338), [`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:357`](../core/dr-catalog/src/schema.rs#L357), [`core/dr-catalog/src/schema.rs:442`](../core/dr-catalog/src/schema.rs#L442), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`ui/dr-ui/src/develop.rs:119`](../ui/dr-ui/src/develop.rs#L119), [`ui/dr-ui/src/develop.rs:128`](../ui/dr-ui/src/develop.rs#L128), [`ui/dr-ui/src/develop.rs:1793`](../ui/dr-ui/src/develop.rs#L1793), [`ui/dr-ui/src/develop.rs:194`](../ui/dr-ui/src/develop.rs#L194), [`ui/dr-ui/src/develop.rs:589`](../ui/dr-ui/src/develop.rs#L589), [`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:1884`](../ui/dr-ui/src/lib.rs#L1884), [`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:442`](../core/dr-catalog/src/schema.rs#L442), [`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:254`](../ui/dr-ui/src/library.rs#L254), [`ui/dr-ui/src/library.rs:284`](../ui/dr-ui/src/library.rs#L284), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-12 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:357`](../core/dr-catalog/src/schema.rs#L357), [`core/dr-catalog/src/schema.rs:442`](../core/dr-catalog/src/schema.rs#L442), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | 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-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:135`](../core/dr-pipeline/src/sidecar.rs#L135), [`ui/dr-ui/src/library.rs:213`](../ui/dr-ui/src/library.rs#L213), [`ui/dr-ui/src/library.rs:459`](../ui/dr-ui/src/library.rs#L459) | -| 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:401`](../core/dr-catalog/src/schema.rs#L401), [`core/dr-catalog/src/schema.rs:442`](../core/dr-catalog/src/schema.rs#L442), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:2731`](../ui/dr-ui/src/library.rs#L2731), [`ui/dr-ui/src/library.rs:2835`](../ui/dr-ui/src/library.rs#L2835), [`ui/dr-ui/ui/settings.slint:404`](../ui/dr-ui/ui/settings.slint#L404), [`ui/dr-ui/ui/settings.slint:81`](../ui/dr-ui/ui/settings.slint#L81) | +| 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:401`](../core/dr-catalog/src/schema.rs#L401), [`core/dr-catalog/src/schema.rs:442`](../core/dr-catalog/src/schema.rs#L442), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:2849`](../ui/dr-ui/src/library.rs#L2849), [`ui/dr-ui/src/library.rs:2953`](../ui/dr-ui/src/library.rs#L2953), [`ui/dr-ui/ui/settings.slint:404`](../ui/dr-ui/ui/settings.slint#L404), [`ui/dr-ui/ui/settings.slint:81`](../ui/dr-ui/ui/settings.slint#L81) | | FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:442`](../core/dr-catalog/src/schema.rs#L442), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`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) | | FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389) | | FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/framing.rs:191`](../core/dr-pipeline/src/framing.rs#L191), [`core/dr-pipeline/src/framing.rs:365`](../core/dr-pipeline/src/framing.rs#L365), [`core/dr-pipeline/src/framing.rs:620`](../core/dr-pipeline/src/framing.rs#L620), [`core/dr-pipeline/src/graph.rs:169`](../core/dr-pipeline/src/graph.rs#L169), [`core/dr-pipeline/src/graph.rs:577`](../core/dr-pipeline/src/graph.rs#L577), [`core/dr-pipeline/src/mask.rs:121`](../core/dr-pipeline/src/mask.rs#L121), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:516`](../core/dr-pipeline/src/operation.rs#L516), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:156`](../core/dr-pipeline/src/sidecar.rs#L156), [`core/dr-pipeline/src/sidecar.rs:1636`](../core/dr-pipeline/src/sidecar.rs#L1636), [`core/dr-pipeline/src/sidecar.rs:1696`](../core/dr-pipeline/src/sidecar.rs#L1696), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:101`](../ui/dr-ui/src/develop.rs#L101), [`ui/dr-ui/src/develop.rs:1297`](../ui/dr-ui/src/develop.rs#L1297), [`ui/dr-ui/src/develop.rs:163`](../ui/dr-ui/src/develop.rs#L163), [`ui/dr-ui/src/develop.rs:1775`](../ui/dr-ui/src/develop.rs#L1775), [`ui/dr-ui/src/develop.rs:1793`](../ui/dr-ui/src/develop.rs#L1793), [`ui/dr-ui/src/develop.rs:1807`](../ui/dr-ui/src/develop.rs#L1807), [`ui/dr-ui/src/develop.rs:1829`](../ui/dr-ui/src/develop.rs#L1829), [`ui/dr-ui/src/develop.rs:1975`](../ui/dr-ui/src/develop.rs#L1975), [`ui/dr-ui/src/develop.rs:2073`](../ui/dr-ui/src/develop.rs#L2073), [`ui/dr-ui/src/develop.rs:326`](../ui/dr-ui/src/develop.rs#L326), [`ui/dr-ui/src/develop.rs:3345`](../ui/dr-ui/src/develop.rs#L3345), [`ui/dr-ui/src/develop.rs:363`](../ui/dr-ui/src/develop.rs#L363), [`ui/dr-ui/src/develop.rs:3909`](../ui/dr-ui/src/develop.rs#L3909), [`ui/dr-ui/src/develop.rs:3963`](../ui/dr-ui/src/develop.rs#L3963), [`ui/dr-ui/src/develop.rs:4007`](../ui/dr-ui/src/develop.rs#L4007), [`ui/dr-ui/src/develop.rs:4057`](../ui/dr-ui/src/develop.rs#L4057), [`ui/dr-ui/src/develop.rs:628`](../ui/dr-ui/src/develop.rs#L628), [`ui/dr-ui/src/develop.rs:675`](../ui/dr-ui/src/develop.rs#L675), [`ui/dr-ui/src/lib.rs:1461`](../ui/dr-ui/src/lib.rs#L1461), [`ui/dr-ui/src/lib.rs:2162`](../ui/dr-ui/src/lib.rs#L2162), [`ui/dr-ui/src/lib.rs:319`](../ui/dr-ui/src/lib.rs#L319), [`ui/dr-ui/src/library.rs:506`](../ui/dr-ui/src/library.rs#L506), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:219`](../ui/dr-ui/src/segmentation.rs#L219), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322), [`ui/dr-ui/src/segmentation.rs:350`](../ui/dr-ui/src/segmentation.rs#L350), [`ui/dr-ui/ui/app.slint:1766`](../ui/dr-ui/ui/app.slint#L1766), [`ui/dr-ui/ui/app.slint:793`](../ui/dr-ui/ui/app.slint#L793), [`ui/dr-ui/ui/masks.slint:490`](../ui/dr-ui/ui/masks.slint#L490) | @@ -67,7 +67,7 @@ _None._ | FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:205`](../core/dr-pipeline/src/framing.rs#L205), [`core/dr-pipeline/src/framing.rs:365`](../core/dr-pipeline/src/framing.rs#L365), [`core/dr-pipeline/src/framing.rs:927`](../core/dr-pipeline/src/framing.rs#L927), [`core/dr-types/src/lib.rs:336`](../core/dr-types/src/lib.rs#L336), [`core/dr-types/src/lib.rs:444`](../core/dr-types/src/lib.rs#L444), [`core/dr-types/src/lib.rs:456`](../core/dr-types/src/lib.rs#L456), [`core/dr-types/src/lib.rs:472`](../core/dr-types/src/lib.rs#L472), [`ui/dr-ui/src/develop.rs:138`](../ui/dr-ui/src/develop.rs#L138), [`ui/dr-ui/src/develop.rs:1992`](../ui/dr-ui/src/develop.rs#L1992), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:217`](../core/dr-gpu/src/lib.rs#L217), [`ui/dr-ui/ui/crop.slint:1`](../ui/dr-ui/ui/crop.slint#L1) | | FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:345`](../core/dr-pipeline/src/graph.rs#L345), [`core/dr-pipeline/src/graph.rs:384`](../core/dr-pipeline/src/graph.rs#L384), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`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), [`ui/dr-ui/src/develop.rs:2914`](../ui/dr-ui/src/develop.rs#L2914), [`ui/dr-ui/src/develop.rs:3397`](../ui/dr-ui/src/develop.rs#L3397), [`ui/dr-ui/src/develop.rs:3427`](../ui/dr-ui/src/develop.rs#L3427), [`ui/dr-ui/src/develop.rs:3440`](../ui/dr-ui/src/develop.rs#L3440), [`ui/dr-ui/src/develop.rs:3452`](../ui/dr-ui/src/develop.rs#L3452), [`ui/dr-ui/src/develop.rs:3468`](../ui/dr-ui/src/develop.rs#L3468), [`ui/dr-ui/src/develop.rs:3500`](../ui/dr-ui/src/develop.rs#L3500), [`ui/dr-ui/src/develop.rs:3504`](../ui/dr-ui/src/develop.rs#L3504), [`ui/dr-ui/src/develop.rs:3523`](../ui/dr-ui/src/develop.rs#L3523), [`ui/dr-ui/src/develop.rs:3539`](../ui/dr-ui/src/develop.rs#L3539), [`ui/dr-ui/src/develop.rs:610`](../ui/dr-ui/src/develop.rs#L610), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:215`](../ui/dr-ui/src/labels.rs#L215), [`ui/dr-ui/src/lib.rs:1409`](../ui/dr-ui/src/lib.rs#L1409), [`ui/dr-ui/src/lib.rs:1439`](../ui/dr-ui/src/lib.rs#L1439), [`ui/dr-ui/src/lib.rs:1447`](../ui/dr-ui/src/lib.rs#L1447), [`ui/dr-ui/src/lib.rs:2332`](../ui/dr-ui/src/lib.rs#L2332), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | -| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-types/src/settings.rs:182`](../core/dr-types/src/settings.rs#L182), [`ui/dr-ui/src/develop.rs:3339`](../ui/dr-ui/src/develop.rs#L3339), [`ui/dr-ui/src/develop.rs:3360`](../ui/dr-ui/src/develop.rs#L3360), [`ui/dr-ui/src/lib.rs:1376`](../ui/dr-ui/src/lib.rs#L1376), [`ui/dr-ui/src/library.rs:1669`](../ui/dr-ui/src/library.rs#L1669), [`ui/dr-ui/src/library.rs:459`](../ui/dr-ui/src/library.rs#L459), [`ui/dr-ui/src/library.rs:487`](../ui/dr-ui/src/library.rs#L487), [`ui/dr-ui/src/library_ui.rs:2575`](../ui/dr-ui/src/library_ui.rs#L2575), [`ui/dr-ui/src/library_ui.rs:2975`](../ui/dr-ui/src/library_ui.rs#L2975), [`ui/dr-ui/src/library_ui.rs:472`](../ui/dr-ui/src/library_ui.rs#L472), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/settings_ui.rs:549`](../ui/dr-ui/src/settings_ui.rs#L549), [`ui/dr-ui/ui/adjust.slint:613`](../ui/dr-ui/ui/adjust.slint#L613), [`ui/dr-ui/ui/library.slint:1328`](../ui/dr-ui/ui/library.slint#L1328), [`ui/dr-ui/ui/library.slint:837`](../ui/dr-ui/ui/library.slint#L837), [`ui/dr-ui/ui/library.slint:919`](../ui/dr-ui/ui/library.slint#L919), [`ui/dr-ui/ui/settings.slint:100`](../ui/dr-ui/ui/settings.slint#L100) | +| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-types/src/settings.rs:182`](../core/dr-types/src/settings.rs#L182), [`ui/dr-ui/src/develop.rs:3339`](../ui/dr-ui/src/develop.rs#L3339), [`ui/dr-ui/src/develop.rs:3360`](../ui/dr-ui/src/develop.rs#L3360), [`ui/dr-ui/src/lib.rs:1376`](../ui/dr-ui/src/lib.rs#L1376), [`ui/dr-ui/src/library.rs:1787`](../ui/dr-ui/src/library.rs#L1787), [`ui/dr-ui/src/library.rs:459`](../ui/dr-ui/src/library.rs#L459), [`ui/dr-ui/src/library.rs:487`](../ui/dr-ui/src/library.rs#L487), [`ui/dr-ui/src/library_ui.rs:2592`](../ui/dr-ui/src/library_ui.rs#L2592), [`ui/dr-ui/src/library_ui.rs:2992`](../ui/dr-ui/src/library_ui.rs#L2992), [`ui/dr-ui/src/library_ui.rs:472`](../ui/dr-ui/src/library_ui.rs#L472), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/settings_ui.rs:549`](../ui/dr-ui/src/settings_ui.rs#L549), [`ui/dr-ui/ui/adjust.slint:613`](../ui/dr-ui/ui/adjust.slint#L613), [`ui/dr-ui/ui/library.slint:1328`](../ui/dr-ui/ui/library.slint#L1328), [`ui/dr-ui/ui/library.slint:837`](../ui/dr-ui/ui/library.slint#L837), [`ui/dr-ui/ui/library.slint:919`](../ui/dr-ui/ui/library.slint#L919), [`ui/dr-ui/ui/settings.slint:100`](../ui/dr-ui/ui/settings.slint#L100) | | FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:3468`](../ui/dr-ui/src/develop.rs#L3468), [`ui/dr-ui/src/develop.rs:3500`](../ui/dr-ui/src/develop.rs#L3500), [`ui/dr-ui/src/lib.rs:1447`](../ui/dr-ui/src/lib.rs#L1447), [`ui/dr-ui/src/lib.rs:2332`](../ui/dr-ui/src/lib.rs#L2332), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | | FR-DEV-8 | [`core/dr-gpu/src/detail.rs:252`](../core/dr-gpu/src/detail.rs#L252), [`core/dr-gpu/src/detail.rs:434`](../core/dr-gpu/src/detail.rs#L434), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:363`](../core/dr-pipeline/src/detail.rs#L363), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:422`](../core/dr-pipeline/src/detail.rs#L422), [`core/dr-pipeline/src/detail.rs:496`](../core/dr-pipeline/src/detail.rs#L496), [`core/dr-pipeline/src/graph.rs:113`](../core/dr-pipeline/src/graph.rs#L113), [`core/dr-pipeline/src/graph.rs:191`](../core/dr-pipeline/src/graph.rs#L191), [`core/dr-pipeline/src/graph.rs:685`](../core/dr-pipeline/src/graph.rs#L685), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:554`](../core/dr-pipeline/src/operation.rs#L554), [`core/dr-pipeline/src/sidecar.rs:183`](../core/dr-pipeline/src/sidecar.rs#L183), [`core/dr-pipeline/src/sidecar.rs:352`](../core/dr-pipeline/src/sidecar.rs#L352), [`core/dr-pipeline/src/sidecar.rs:672`](../core/dr-pipeline/src/sidecar.rs#L672), [`core/dr-pipeline/src/sidecar.rs:808`](../core/dr-pipeline/src/sidecar.rs#L808), [`core/dr-pipeline/src/sidecar.rs:862`](../core/dr-pipeline/src/sidecar.rs#L862), [`core/dr-pipeline/src/sidecar.rs:892`](../core/dr-pipeline/src/sidecar.rs#L892), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2144`](../ui/dr-ui/src/develop.rs#L2144), [`ui/dr-ui/src/develop.rs:2182`](../ui/dr-ui/src/develop.rs#L2182), [`ui/dr-ui/src/develop.rs:2247`](../ui/dr-ui/src/develop.rs#L2247), [`ui/dr-ui/src/develop.rs:2330`](../ui/dr-ui/src/develop.rs#L2330), [`ui/dr-ui/src/develop.rs:2344`](../ui/dr-ui/src/develop.rs#L2344), [`ui/dr-ui/src/develop.rs:658`](../ui/dr-ui/src/develop.rs#L658), [`ui/dr-ui/src/labels.rs:52`](../ui/dr-ui/src/labels.rs#L52), [`ui/dr-ui/src/lib.rs:1468`](../ui/dr-ui/src/lib.rs#L1468), [`ui/dr-ui/src/lib.rs:2429`](../ui/dr-ui/src/lib.rs#L2429), [`ui/dr-ui/src/lib.rs:324`](../ui/dr-ui/src/lib.rs#L324), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:700`](../ui/dr-ui/ui/adjust.slint#L700), [`ui/dr-ui/ui/app.slint:108`](../ui/dr-ui/ui/app.slint#L108), [`ui/dr-ui/ui/app.slint:1671`](../ui/dr-ui/ui/app.slint#L1671), [`ui/dr-ui/ui/app.slint:1844`](../ui/dr-ui/ui/app.slint#L1844), [`ui/dr-ui/ui/app.slint:2162`](../ui/dr-ui/ui/app.slint#L2162), [`ui/dr-ui/ui/spots.slint:180`](../ui/dr-ui/ui/spots.slint#L180), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | | FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2088`](../core/dr-gpu/src/adjust.rs#L2088), [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:2250`](../core/dr-gpu/src/adjust.rs#L2250), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:54`](../core/dr-gpu/src/lib.rs#L54), [`core/dr-gpu/src/lib.rs:94`](../core/dr-gpu/src/lib.rs#L94), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:263`](../core/dr-gpu/tests/local_contrast.rs#L263), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/graph.rs:547`](../core/dr-pipeline/src/graph.rs#L547), [`core/dr-pipeline/src/graph.rs:577`](../core/dr-pipeline/src/graph.rs#L577), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:657`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L657), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:672`](../core/dr-pipeline/src/ops/local_contrast.rs#L672), [`core/dr-pipeline/src/ops/noise_reduction.rs:698`](../core/dr-pipeline/src/ops/noise_reduction.rs#L698), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:2668`](../ui/dr-ui/src/develop.rs#L2668), [`ui/dr-ui/src/develop.rs:3698`](../ui/dr-ui/src/develop.rs#L3698), [`ui/dr-ui/src/develop.rs:4181`](../ui/dr-ui/src/develop.rs#L4181), [`ui/dr-ui/src/develop.rs:4215`](../ui/dr-ui/src/develop.rs#L4215), [`ui/dr-ui/src/lib.rs:72`](../ui/dr-ui/src/lib.rs#L72), [`ui/dr-ui/src/lib.rs:757`](../ui/dr-ui/src/lib.rs#L757), [`ui/dr-ui/src/lib.rs:816`](../ui/dr-ui/src/lib.rs#L816) | @@ -82,22 +82,22 @@ _None._ | FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:362`](../ui/dr-ui/src/lib.rs#L362), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:49`](../ui/dr-ui/src/settings_ui.rs#L49), [`ui/dr-ui/src/settings_ui.rs:604`](../ui/dr-ui/src/settings_ui.rs#L604) | -| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:942`](../ui/dr-ui/src/export.rs#L942), [`ui/dr-ui/src/lib.rs:204`](../ui/dr-ui/src/lib.rs#L204), [`ui/dr-ui/src/lib.rs:2078`](../ui/dr-ui/src/lib.rs#L2078), [`ui/dr-ui/src/lib.rs:362`](../ui/dr-ui/src/lib.rs#L362), [`ui/dr-ui/src/lib.rs:397`](../ui/dr-ui/src/lib.rs#L397), [`ui/dr-ui/src/lib.rs:424`](../ui/dr-ui/src/lib.rs#L424), [`ui/dr-ui/src/library_ui.rs:3358`](../ui/dr-ui/src/library_ui.rs#L3358), [`ui/dr-ui/src/library_ui.rs:563`](../ui/dr-ui/src/library_ui.rs#L563), [`ui/dr-ui/src/library_ui.rs:6354`](../ui/dr-ui/src/library_ui.rs#L6354), [`ui/dr-ui/src/library_ui.rs:6431`](../ui/dr-ui/src/library_ui.rs#L6431), [`ui/dr-ui/src/library_ui.rs:6443`](../ui/dr-ui/src/library_ui.rs#L6443), [`ui/dr-ui/src/library_ui.rs:663`](../ui/dr-ui/src/library_ui.rs#L663), [`ui/dr-ui/src/library_ui.rs:720`](../ui/dr-ui/src/library_ui.rs#L720), [`ui/dr-ui/ui/app.slint:1372`](../ui/dr-ui/ui/app.slint#L1372), [`ui/dr-ui/ui/app.slint:929`](../ui/dr-ui/ui/app.slint#L929), [`ui/dr-ui/ui/library.slint:1336`](../ui/dr-ui/ui/library.slint#L1336), [`ui/dr-ui/ui/library.slint:841`](../ui/dr-ui/ui/library.slint#L841), [`ui/dr-ui/ui/library.slint:934`](../ui/dr-ui/ui/library.slint#L934) | +| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:942`](../ui/dr-ui/src/export.rs#L942), [`ui/dr-ui/src/lib.rs:204`](../ui/dr-ui/src/lib.rs#L204), [`ui/dr-ui/src/lib.rs:2078`](../ui/dr-ui/src/lib.rs#L2078), [`ui/dr-ui/src/lib.rs:362`](../ui/dr-ui/src/lib.rs#L362), [`ui/dr-ui/src/lib.rs:397`](../ui/dr-ui/src/lib.rs#L397), [`ui/dr-ui/src/lib.rs:424`](../ui/dr-ui/src/lib.rs#L424), [`ui/dr-ui/src/library_ui.rs:3375`](../ui/dr-ui/src/library_ui.rs#L3375), [`ui/dr-ui/src/library_ui.rs:563`](../ui/dr-ui/src/library_ui.rs#L563), [`ui/dr-ui/src/library_ui.rs:6371`](../ui/dr-ui/src/library_ui.rs#L6371), [`ui/dr-ui/src/library_ui.rs:6448`](../ui/dr-ui/src/library_ui.rs#L6448), [`ui/dr-ui/src/library_ui.rs:6460`](../ui/dr-ui/src/library_ui.rs#L6460), [`ui/dr-ui/src/library_ui.rs:663`](../ui/dr-ui/src/library_ui.rs#L663), [`ui/dr-ui/src/library_ui.rs:720`](../ui/dr-ui/src/library_ui.rs#L720), [`ui/dr-ui/ui/app.slint:1372`](../ui/dr-ui/ui/app.slint#L1372), [`ui/dr-ui/ui/app.slint:929`](../ui/dr-ui/ui/app.slint#L929), [`ui/dr-ui/ui/library.slint:1336`](../ui/dr-ui/ui/library.slint#L1336), [`ui/dr-ui/ui/library.slint:841`](../ui/dr-ui/ui/library.slint#L841), [`ui/dr-ui/ui/library.slint:934`](../ui/dr-ui/ui/library.slint#L934) | | FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:136`](../core/dr-export/src/lib.rs#L136), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:652`](../core/dr-types/src/lib.rs#L652), [`core/dr-types/src/settings.rs:313`](../core/dr-types/src/settings.rs#L313), [`ui/dr-ui/src/export.rs:619`](../ui/dr-ui/src/export.rs#L619), [`ui/dr-ui/src/export.rs:647`](../ui/dr-ui/src/export.rs#L647), [`ui/dr-ui/src/export.rs:777`](../ui/dr-ui/src/export.rs#L777), [`ui/dr-ui/src/export.rs:794`](../ui/dr-ui/src/export.rs#L794), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1068`](../core/dr-gpu/src/adjust.rs#L1068), [`ui/dr-ui/src/develop.rs:2829`](../ui/dr-ui/src/develop.rs#L2829), [`ui/dr-ui/src/lib.rs:362`](../ui/dr-ui/src/lib.rs#L362) | | FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:355`](../core/dr-sync/src/account.rs#L355), [`ui/dr-ui/src/launch.rs:277`](../ui/dr-ui/src/launch.rs#L277), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61), [`ui/dr-ui/src/launch_ui.rs:417`](../ui/dr-ui/src/launch_ui.rs#L417) | -| FR-NC-10 | [`core/dr-sync/src/account.rs:220`](../core/dr-sync/src/account.rs#L220), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:424`](../ui/dr-ui/src/lib.rs#L424), [`ui/dr-ui/src/library.rs:1068`](../ui/dr-ui/src/library.rs#L1068), [`ui/dr-ui/src/library.rs:1705`](../ui/dr-ui/src/library.rs#L1705), [`ui/dr-ui/src/library.rs:543`](../ui/dr-ui/src/library.rs#L543), [`ui/dr-ui/src/library.rs:844`](../ui/dr-ui/src/library.rs#L844), [`ui/dr-ui/src/library_ui.rs:1605`](../ui/dr-ui/src/library_ui.rs#L1605), [`ui/dr-ui/src/library_ui.rs:3358`](../ui/dr-ui/src/library_ui.rs#L3358), [`ui/dr-ui/src/library_ui.rs:504`](../ui/dr-ui/src/library_ui.rs#L504), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | -| 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:39`](../core/dr-sync-nextcloud/src/lib.rs#L39), [`core/dr-sync-nextcloud/src/lib.rs:913`](../core/dr-sync-nextcloud/src/lib.rs#L913), [`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:211`](../core/dr-sync/src/lib.rs#L211), [`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-10 | [`core/dr-sync/src/account.rs:220`](../core/dr-sync/src/account.rs#L220), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:424`](../ui/dr-ui/src/lib.rs#L424), [`ui/dr-ui/src/library.rs:1068`](../ui/dr-ui/src/library.rs#L1068), [`ui/dr-ui/src/library.rs:1823`](../ui/dr-ui/src/library.rs#L1823), [`ui/dr-ui/src/library.rs:543`](../ui/dr-ui/src/library.rs#L543), [`ui/dr-ui/src/library.rs:844`](../ui/dr-ui/src/library.rs#L844), [`ui/dr-ui/src/library_ui.rs:1622`](../ui/dr-ui/src/library_ui.rs#L1622), [`ui/dr-ui/src/library_ui.rs:3375`](../ui/dr-ui/src/library_ui.rs#L3375), [`ui/dr-ui/src/library_ui.rs:504`](../ui/dr-ui/src/library_ui.rs#L504), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| 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:39`](../core/dr-sync-nextcloud/src/lib.rs#L39), [`core/dr-sync-nextcloud/src/lib.rs:913`](../core/dr-sync-nextcloud/src/lib.rs#L913), [`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:2730`](../ui/dr-ui/src/library.rs#L2730), [`ui/dr-ui/src/library.rs:3200`](../ui/dr-ui/src/library.rs#L3200), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:3610`](../ui/dr-ui/src/library_ui.rs#L3610), [`ui/dr-ui/src/library_ui.rs:4575`](../ui/dr-ui/src/library_ui.rs#L4575), [`ui/dr-ui/ui/app.slint:319`](../ui/dr-ui/ui/app.slint#L319), [`ui/dr-ui/ui/settings.slint:372`](../ui/dr-ui/ui/settings.slint#L372), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) | -| 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:211`](../core/dr-sync/src/lib.rs#L211), [`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-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:2848`](../ui/dr-ui/src/library.rs#L2848), [`ui/dr-ui/src/library.rs:3356`](../ui/dr-ui/src/library.rs#L3356), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:3627`](../ui/dr-ui/src/library_ui.rs#L3627), [`ui/dr-ui/src/library_ui.rs:4592`](../ui/dr-ui/src/library_ui.rs#L4592), [`ui/dr-ui/ui/app.slint:319`](../ui/dr-ui/ui/app.slint#L319), [`ui/dr-ui/ui/settings.slint:372`](../ui/dr-ui/ui/settings.slint#L372), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) | +| 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/schema.rs:1014`](../core/dr-catalog/src/schema.rs#L1014), [`core/dr-catalog/src/schema.rs:613`](../core/dr-catalog/src/schema.rs#L613), [`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:3174`](../ui/dr-ui/src/collections_ui.rs#L3174), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/lib.rs:1807`](../ui/dr-ui/src/lib.rs#L1807), [`ui/dr-ui/src/lib.rs:2698`](../ui/dr-ui/src/lib.rs#L2698), [`ui/dr-ui/src/library.rs:1449`](../ui/dr-ui/src/library.rs#L1449), [`ui/dr-ui/src/library.rs:1472`](../ui/dr-ui/src/library.rs#L1472), [`ui/dr-ui/src/library.rs:1629`](../ui/dr-ui/src/library.rs#L1629), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/src/library_ui.rs:1239`](../ui/dr-ui/src/library_ui.rs#L1239), [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294), [`ui/dr-ui/src/library_ui.rs:1412`](../ui/dr-ui/src/library_ui.rs#L1412), [`ui/dr-ui/src/library_ui.rs:1530`](../ui/dr-ui/src/library_ui.rs#L1530), [`ui/dr-ui/src/library_ui.rs:2057`](../ui/dr-ui/src/library_ui.rs#L2057), [`ui/dr-ui/src/library_ui.rs:273`](../ui/dr-ui/src/library_ui.rs#L273), [`ui/dr-ui/src/library_ui.rs:284`](../ui/dr-ui/src/library_ui.rs#L284), [`ui/dr-ui/src/library_ui.rs:292`](../ui/dr-ui/src/library_ui.rs#L292), [`ui/dr-ui/src/library_ui.rs:304`](../ui/dr-ui/src/library_ui.rs#L304), [`ui/dr-ui/src/library_ui.rs:313`](../ui/dr-ui/src/library_ui.rs#L313), [`ui/dr-ui/src/library_ui.rs:405`](../ui/dr-ui/src/library_ui.rs#L405), [`ui/dr-ui/src/library_ui.rs:415`](../ui/dr-ui/src/library_ui.rs#L415), [`ui/dr-ui/src/library_ui.rs:457`](../ui/dr-ui/src/library_ui.rs#L457), [`ui/dr-ui/src/library_ui.rs:520`](../ui/dr-ui/src/library_ui.rs#L520), [`ui/dr-ui/src/library_ui.rs:5301`](../ui/dr-ui/src/library_ui.rs#L5301), [`ui/dr-ui/src/library_ui.rs:5319`](../ui/dr-ui/src/library_ui.rs#L5319), [`ui/dr-ui/src/library_ui.rs:5331`](../ui/dr-ui/src/library_ui.rs#L5331), [`ui/dr-ui/src/library_ui.rs:551`](../ui/dr-ui/src/library_ui.rs#L551), [`ui/dr-ui/src/library_ui.rs:563`](../ui/dr-ui/src/library_ui.rs#L563), [`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:2335`](../ui/dr-ui/ui/app.slint#L2335), [`ui/dr-ui/ui/app.slint:434`](../ui/dr-ui/ui/app.slint#L434), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260), [`ui/dr-ui/ui/library.slint:980`](../ui/dr-ui/ui/library.slint#L980) | +| 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:1014`](../core/dr-catalog/src/schema.rs#L1014), [`core/dr-catalog/src/schema.rs:613`](../core/dr-catalog/src/schema.rs#L613), [`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:3174`](../ui/dr-ui/src/collections_ui.rs#L3174), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/lib.rs:1807`](../ui/dr-ui/src/lib.rs#L1807), [`ui/dr-ui/src/lib.rs:2698`](../ui/dr-ui/src/lib.rs#L2698), [`ui/dr-ui/src/library.rs:1449`](../ui/dr-ui/src/library.rs#L1449), [`ui/dr-ui/src/library.rs:1472`](../ui/dr-ui/src/library.rs#L1472), [`ui/dr-ui/src/library.rs:1747`](../ui/dr-ui/src/library.rs#L1747), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/src/library_ui.rs:1239`](../ui/dr-ui/src/library_ui.rs#L1239), [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294), [`ui/dr-ui/src/library_ui.rs:1429`](../ui/dr-ui/src/library_ui.rs#L1429), [`ui/dr-ui/src/library_ui.rs:1547`](../ui/dr-ui/src/library_ui.rs#L1547), [`ui/dr-ui/src/library_ui.rs:2074`](../ui/dr-ui/src/library_ui.rs#L2074), [`ui/dr-ui/src/library_ui.rs:273`](../ui/dr-ui/src/library_ui.rs#L273), [`ui/dr-ui/src/library_ui.rs:284`](../ui/dr-ui/src/library_ui.rs#L284), [`ui/dr-ui/src/library_ui.rs:292`](../ui/dr-ui/src/library_ui.rs#L292), [`ui/dr-ui/src/library_ui.rs:304`](../ui/dr-ui/src/library_ui.rs#L304), [`ui/dr-ui/src/library_ui.rs:313`](../ui/dr-ui/src/library_ui.rs#L313), [`ui/dr-ui/src/library_ui.rs:405`](../ui/dr-ui/src/library_ui.rs#L405), [`ui/dr-ui/src/library_ui.rs:415`](../ui/dr-ui/src/library_ui.rs#L415), [`ui/dr-ui/src/library_ui.rs:457`](../ui/dr-ui/src/library_ui.rs#L457), [`ui/dr-ui/src/library_ui.rs:520`](../ui/dr-ui/src/library_ui.rs#L520), [`ui/dr-ui/src/library_ui.rs:5318`](../ui/dr-ui/src/library_ui.rs#L5318), [`ui/dr-ui/src/library_ui.rs:5336`](../ui/dr-ui/src/library_ui.rs#L5336), [`ui/dr-ui/src/library_ui.rs:5348`](../ui/dr-ui/src/library_ui.rs#L5348), [`ui/dr-ui/src/library_ui.rs:551`](../ui/dr-ui/src/library_ui.rs#L551), [`ui/dr-ui/src/library_ui.rs:563`](../ui/dr-ui/src/library_ui.rs#L563), [`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:2335`](../ui/dr-ui/ui/app.slint#L2335), [`ui/dr-ui/ui/app.slint:434`](../ui/dr-ui/ui/app.slint#L434), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260), [`ui/dr-ui/ui/library.slint:980`](../ui/dr-ui/ui/library.slint#L980) | | FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294) | -| FR-NC-6c | [`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:770`](../core/dr-sync-folder/src/lib.rs#L770), [`core/dr-sync-folder/src/lib.rs:809`](../core/dr-sync-folder/src/lib.rs#L809), [`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:119`](../core/dr-types/src/lib.rs#L119), [`core/dr-types/src/lib.rs:201`](../core/dr-types/src/lib.rs#L201), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3174`](../ui/dr-ui/src/collections_ui.rs#L3174), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/library.rs:948`](../ui/dr-ui/src/library.rs#L948), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`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:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260) | -| 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:104`](../core/dr-sync-nextcloud/src/lib.rs#L104), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:3200`](../ui/dr-ui/src/library.rs#L3200), [`ui/dr-ui/src/library_ui.rs:3610`](../ui/dr-ui/src/library_ui.rs#L3610), [`ui/dr-ui/ui/settings.slint:372`](../ui/dr-ui/ui/settings.slint#L372) | +| 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:770`](../core/dr-sync-folder/src/lib.rs#L770), [`core/dr-sync-folder/src/lib.rs:809`](../core/dr-sync-folder/src/lib.rs#L809), [`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:119`](../core/dr-types/src/lib.rs#L119), [`core/dr-types/src/lib.rs:201`](../core/dr-types/src/lib.rs#L201), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3174`](../ui/dr-ui/src/collections_ui.rs#L3174), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/library.rs:1569`](../ui/dr-ui/src/library.rs#L1569), [`ui/dr-ui/src/library.rs:1659`](../ui/dr-ui/src/library.rs#L1659), [`ui/dr-ui/src/library.rs:3138`](../ui/dr-ui/src/library.rs#L3138), [`ui/dr-ui/src/library.rs:3476`](../ui/dr-ui/src/library.rs#L3476), [`ui/dr-ui/src/library.rs:948`](../ui/dr-ui/src/library.rs#L948), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/src/library_ui.rs:1337`](../ui/dr-ui/src/library_ui.rs#L1337), [`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:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260) | +| 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:104`](../core/dr-sync-nextcloud/src/lib.rs#L104), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:3356`](../ui/dr-ui/src/library.rs#L3356), [`ui/dr-ui/src/library_ui.rs:3627`](../ui/dr-ui/src/library_ui.rs#L3627), [`ui/dr-ui/ui/settings.slint:372`](../ui/dr-ui/ui/settings.slint#L372) | | 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:116`](../core/dr-types/src/settings.rs#L116), [`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:1123`](../ui/dr-ui/src/lib.rs#L1123), [`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:1123`](../ui/dr-ui/src/lib.rs#L1123) | | FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:118`](../core/dr-pipeline/src/sidecar.rs#L118), [`core/dr-pipeline/src/sidecar.rs:92`](../core/dr-pipeline/src/sidecar.rs#L92), [`ui/dr-ui/src/lib.rs:1777`](../ui/dr-ui/src/lib.rs#L1777), [`ui/dr-ui/src/library.rs:459`](../ui/dr-ui/src/library.rs#L459), [`ui/dr-ui/src/library_ui.rs:472`](../ui/dr-ui/src/library_ui.rs#L472) | @@ -113,18 +113,18 @@ _None._ | FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1), [`ui/dr-ui/src/lib.rs:204`](../ui/dr-ui/src/lib.rs#L204) | | FR-RAW-5 | [`core/dr-decode/src/lib.rs:167`](../core/dr-decode/src/lib.rs#L167), [`core/dr-gpu/src/demosaic.rs:34`](../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:602`](../core/dr-gpu/src/demosaic.rs#L602), [`core/dr-gpu/src/demosaic.rs:681`](../core/dr-gpu/src/demosaic.rs#L681), [`core/dr-gpu/src/demosaic.rs:805`](../core/dr-gpu/src/demosaic.rs#L805) | | FR-UI-1 | [`ui/dr-ui/src/lib.rs:2780`](../ui/dr-ui/src/lib.rs#L2780), [`ui/dr-ui/src/lib.rs:80`](../ui/dr-ui/src/lib.rs#L80), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/identity.slint:165`](../ui/dr-ui/ui/identity.slint#L165), [`ui/dr-ui/ui/library.slint:1046`](../ui/dr-ui/ui/library.slint#L1046) | -| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:1488`](../ui/dr-ui/src/collections_ui.rs#L1488), [`ui/dr-ui/src/collections_ui.rs:1502`](../ui/dr-ui/src/collections_ui.rs#L1502), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1646`](../ui/dr-ui/src/collections_ui.rs#L1646), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/lib.rs:80`](../ui/dr-ui/src/lib.rs#L80), [`ui/dr-ui/src/lib.rs:87`](../ui/dr-ui/src/lib.rs#L87), [`ui/dr-ui/src/library_ui.rs:292`](../ui/dr-ui/src/library_ui.rs#L292), [`ui/dr-ui/src/library_ui.rs:5186`](../ui/dr-ui/src/library_ui.rs#L5186), [`ui/dr-ui/src/library_ui.rs:5331`](../ui/dr-ui/src/library_ui.rs#L5331), [`ui/dr-ui/src/library_ui.rs:6462`](../ui/dr-ui/src/library_ui.rs#L6462), [`ui/dr-ui/ui/adjust.slint:506`](../ui/dr-ui/ui/adjust.slint#L506), [`ui/dr-ui/ui/adjust.slint:641`](../ui/dr-ui/ui/adjust.slint#L641), [`ui/dr-ui/ui/adjust.slint:962`](../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1950`](../ui/dr-ui/ui/app.slint#L1950), [`ui/dr-ui/ui/app.slint:464`](../ui/dr-ui/ui/app.slint#L464), [`ui/dr-ui/ui/app.slint:471`](../ui/dr-ui/ui/app.slint#L471), [`ui/dr-ui/ui/app.slint:54`](../ui/dr-ui/ui/app.slint#L54), [`ui/dr-ui/ui/app.slint:764`](../ui/dr-ui/ui/app.slint#L764), [`ui/dr-ui/ui/develop.slint:223`](../ui/dr-ui/ui/develop.slint#L223), [`ui/dr-ui/ui/histogram.slint:127`](../ui/dr-ui/ui/histogram.slint#L127), [`ui/dr-ui/ui/history.slint:118`](../ui/dr-ui/ui/history.slint#L118), [`ui/dr-ui/ui/library.slint:1202`](../ui/dr-ui/ui/library.slint#L1202), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1215`](../ui/dr-ui/ui/library.slint#L1215), [`ui/dr-ui/ui/library.slint:2314`](../ui/dr-ui/ui/library.slint#L2314), [`ui/dr-ui/ui/library.slint:829`](../ui/dr-ui/ui/library.slint#L829), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879), [`ui/dr-ui/ui/masks.slint:304`](../ui/dr-ui/ui/masks.slint#L304), [`ui/dr-ui/ui/settings.slint:106`](../ui/dr-ui/ui/settings.slint#L106), [`ui/dr-ui/ui/spots.slint:88`](../ui/dr-ui/ui/spots.slint#L88) | -| FR-UI-3 | [`ui/dr-ui/src/develop.rs:2073`](../ui/dr-ui/src/develop.rs#L2073), [`ui/dr-ui/src/develop.rs:2182`](../ui/dr-ui/src/develop.rs#L2182), [`ui/dr-ui/src/library_ui.rs:4370`](../ui/dr-ui/src/library_ui.rs#L4370), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1766`](../ui/dr-ui/ui/app.slint#L1766), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/masks.slint:490`](../ui/dr-ui/ui/masks.slint#L490) | -| FR-UI-4 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:131`](../ui/dr-ui/src/collections_ui.rs#L131), [`ui/dr-ui/src/collections_ui.rs:1488`](../ui/dr-ui/src/collections_ui.rs#L1488), [`ui/dr-ui/src/collections_ui.rs:1502`](../ui/dr-ui/src/collections_ui.rs#L1502), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1646`](../ui/dr-ui/src/collections_ui.rs#L1646), [`ui/dr-ui/src/collections_ui.rs:1673`](../ui/dr-ui/src/collections_ui.rs#L1673), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:582`](../ui/dr-ui/src/collections_ui.rs#L582), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/library_ui.rs:4370`](../ui/dr-ui/src/library_ui.rs#L4370), [`ui/dr-ui/src/library_ui.rs:4415`](../ui/dr-ui/src/library_ui.rs#L4415), [`ui/dr-ui/src/library_ui.rs:4518`](../ui/dr-ui/src/library_ui.rs#L4518), [`ui/dr-ui/src/library_ui.rs:4546`](../ui/dr-ui/src/library_ui.rs#L4546), [`ui/dr-ui/src/library_ui.rs:5319`](../ui/dr-ui/src/library_ui.rs#L5319), [`ui/dr-ui/src/library_ui.rs:5331`](../ui/dr-ui/src/library_ui.rs#L5331), [`ui/dr-ui/ui/app.slint:1608`](../ui/dr-ui/ui/app.slint#L1608), [`ui/dr-ui/ui/app.slint:440`](../ui/dr-ui/ui/app.slint#L440), [`ui/dr-ui/ui/app.slint:471`](../ui/dr-ui/ui/app.slint#L471), [`ui/dr-ui/ui/library.slint:1202`](../ui/dr-ui/ui/library.slint#L1202), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1215`](../ui/dr-ui/ui/library.slint#L1215), [`ui/dr-ui/ui/library.slint:2314`](../ui/dr-ui/ui/library.slint#L2314), [`ui/dr-ui/ui/library.slint:829`](../ui/dr-ui/ui/library.slint#L829), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898) | +| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:1488`](../ui/dr-ui/src/collections_ui.rs#L1488), [`ui/dr-ui/src/collections_ui.rs:1502`](../ui/dr-ui/src/collections_ui.rs#L1502), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1646`](../ui/dr-ui/src/collections_ui.rs#L1646), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/lib.rs:80`](../ui/dr-ui/src/lib.rs#L80), [`ui/dr-ui/src/lib.rs:87`](../ui/dr-ui/src/lib.rs#L87), [`ui/dr-ui/src/library_ui.rs:292`](../ui/dr-ui/src/library_ui.rs#L292), [`ui/dr-ui/src/library_ui.rs:5203`](../ui/dr-ui/src/library_ui.rs#L5203), [`ui/dr-ui/src/library_ui.rs:5348`](../ui/dr-ui/src/library_ui.rs#L5348), [`ui/dr-ui/src/library_ui.rs:6479`](../ui/dr-ui/src/library_ui.rs#L6479), [`ui/dr-ui/ui/adjust.slint:506`](../ui/dr-ui/ui/adjust.slint#L506), [`ui/dr-ui/ui/adjust.slint:641`](../ui/dr-ui/ui/adjust.slint#L641), [`ui/dr-ui/ui/adjust.slint:962`](../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1950`](../ui/dr-ui/ui/app.slint#L1950), [`ui/dr-ui/ui/app.slint:464`](../ui/dr-ui/ui/app.slint#L464), [`ui/dr-ui/ui/app.slint:471`](../ui/dr-ui/ui/app.slint#L471), [`ui/dr-ui/ui/app.slint:54`](../ui/dr-ui/ui/app.slint#L54), [`ui/dr-ui/ui/app.slint:764`](../ui/dr-ui/ui/app.slint#L764), [`ui/dr-ui/ui/develop.slint:223`](../ui/dr-ui/ui/develop.slint#L223), [`ui/dr-ui/ui/histogram.slint:127`](../ui/dr-ui/ui/histogram.slint#L127), [`ui/dr-ui/ui/history.slint:118`](../ui/dr-ui/ui/history.slint#L118), [`ui/dr-ui/ui/library.slint:1202`](../ui/dr-ui/ui/library.slint#L1202), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1215`](../ui/dr-ui/ui/library.slint#L1215), [`ui/dr-ui/ui/library.slint:2314`](../ui/dr-ui/ui/library.slint#L2314), [`ui/dr-ui/ui/library.slint:829`](../ui/dr-ui/ui/library.slint#L829), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879), [`ui/dr-ui/ui/masks.slint:304`](../ui/dr-ui/ui/masks.slint#L304), [`ui/dr-ui/ui/settings.slint:106`](../ui/dr-ui/ui/settings.slint#L106), [`ui/dr-ui/ui/spots.slint:88`](../ui/dr-ui/ui/spots.slint#L88) | +| FR-UI-3 | [`ui/dr-ui/src/develop.rs:2073`](../ui/dr-ui/src/develop.rs#L2073), [`ui/dr-ui/src/develop.rs:2182`](../ui/dr-ui/src/develop.rs#L2182), [`ui/dr-ui/src/library_ui.rs:4387`](../ui/dr-ui/src/library_ui.rs#L4387), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1766`](../ui/dr-ui/ui/app.slint#L1766), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/masks.slint:490`](../ui/dr-ui/ui/masks.slint#L490) | +| FR-UI-4 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:131`](../ui/dr-ui/src/collections_ui.rs#L131), [`ui/dr-ui/src/collections_ui.rs:1488`](../ui/dr-ui/src/collections_ui.rs#L1488), [`ui/dr-ui/src/collections_ui.rs:1502`](../ui/dr-ui/src/collections_ui.rs#L1502), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1646`](../ui/dr-ui/src/collections_ui.rs#L1646), [`ui/dr-ui/src/collections_ui.rs:1673`](../ui/dr-ui/src/collections_ui.rs#L1673), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:582`](../ui/dr-ui/src/collections_ui.rs#L582), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/library_ui.rs:4387`](../ui/dr-ui/src/library_ui.rs#L4387), [`ui/dr-ui/src/library_ui.rs:4432`](../ui/dr-ui/src/library_ui.rs#L4432), [`ui/dr-ui/src/library_ui.rs:4535`](../ui/dr-ui/src/library_ui.rs#L4535), [`ui/dr-ui/src/library_ui.rs:4563`](../ui/dr-ui/src/library_ui.rs#L4563), [`ui/dr-ui/src/library_ui.rs:5336`](../ui/dr-ui/src/library_ui.rs#L5336), [`ui/dr-ui/src/library_ui.rs:5348`](../ui/dr-ui/src/library_ui.rs#L5348), [`ui/dr-ui/ui/app.slint:1608`](../ui/dr-ui/ui/app.slint#L1608), [`ui/dr-ui/ui/app.slint:440`](../ui/dr-ui/ui/app.slint#L440), [`ui/dr-ui/ui/app.slint:471`](../ui/dr-ui/ui/app.slint#L471), [`ui/dr-ui/ui/library.slint:1202`](../ui/dr-ui/ui/library.slint#L1202), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1215`](../ui/dr-ui/ui/library.slint#L1215), [`ui/dr-ui/ui/library.slint:2314`](../ui/dr-ui/ui/library.slint#L2314), [`ui/dr-ui/ui/library.slint:829`](../ui/dr-ui/ui/library.slint#L829), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898) | | FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2394`](../ui/dr-ui/src/lib.rs#L2394), [`ui/dr-ui/src/lib.rs:2819`](../ui/dr-ui/src/lib.rs#L2819), [`ui/dr-ui/src/lib.rs:3004`](../ui/dr-ui/src/lib.rs#L3004), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:89`](../ui/dr-ui/ui/app.slint#L89), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) | | FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:262`](../core/dr-pipeline/src/framing.rs#L262) | -| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:2835`](../ui/dr-ui/src/library.rs#L2835) | +| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:2953`](../ui/dr-ui/src/library.rs#L2953) | | NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1553`](../ui/dr-ui/src/export.rs#L1553), [`ui/dr-ui/src/export.rs:1579`](../ui/dr-ui/src/export.rs#L1579), [`ui/dr-ui/src/export.rs:409`](../ui/dr-ui/src/export.rs#L409), [`ui/dr-ui/src/export.rs:435`](../ui/dr-ui/src/export.rs#L435), [`ui/dr-ui/src/lib.rs:2112`](../ui/dr-ui/src/lib.rs#L2112), [`ui/dr-ui/ui/app.slint:940`](../ui/dr-ui/ui/app.slint#L940), [`ui/dr-ui/ui/library.slint:934`](../ui/dr-ui/ui/library.slint#L934) | | NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-export/src/error.rs:1`](../core/dr-export/src/error.rs#L1), [`core/dr-thumbs/src/error.rs:1`](../core/dr-thumbs/src/error.rs#L1), [`platform/dr-plat/src/storage.rs:148`](../platform/dr-plat/src/storage.rs#L148), [`ui/dr-ui/src/export.rs:499`](../ui/dr-ui/src/export.rs#L499) | | NFR-OPS-1 | [`tools/traceability/src/lib.rs:266`](../tools/traceability/src/lib.rs#L266) | | NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`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), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479) | | NFR-P13 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | -| NFR-P5 | [`core/dr-catalog/src/schema.rs:318`](../core/dr-catalog/src/schema.rs#L318), [`ui/dr-ui/src/library.rs:3799`](../ui/dr-ui/src/library.rs#L3799), [`ui/dr-ui/src/library.rs:4591`](../ui/dr-ui/src/library.rs#L4591), [`ui/dr-ui/src/library_ui.rs:4055`](../ui/dr-ui/src/library_ui.rs#L4055), [`ui/dr-ui/src/library_ui.rs:64`](../ui/dr-ui/src/library_ui.rs#L64) | +| NFR-P5 | [`core/dr-catalog/src/schema.rs:318`](../core/dr-catalog/src/schema.rs#L318), [`ui/dr-ui/src/library.rs:4003`](../ui/dr-ui/src/library.rs#L4003), [`ui/dr-ui/src/library.rs:4795`](../ui/dr-ui/src/library.rs#L4795), [`ui/dr-ui/src/library_ui.rs:4072`](../ui/dr-ui/src/library_ui.rs#L4072), [`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:269`](../core/dr-types/src/lib.rs#L269), [`core/dr-types/src/lib.rs:302`](../core/dr-types/src/lib.rs#L302), [`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-3 | [`platform/dr-plat/src/storage.rs:1`](../platform/dr-plat/src/storage.rs#L1) | @@ -134,7 +134,7 @@ _None._ | 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-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/lib.rs:72`](../ui/dr-ui/src/lib.rs#L72) | -| 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:613`](../core/dr-catalog/src/schema.rs#L613), [`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:2730`](../ui/dr-ui/src/library.rs#L2730) | +| 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:613`](../core/dr-catalog/src/schema.rs#L613), [`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:2848`](../ui/dr-ui/src/library.rs#L2848) | | 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/secrets.rs:82`](../platform/dr-plat/src/secrets.rs#L82) | | NFR-SEC-5 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:442`](../core/dr-catalog/src/schema.rs#L442), [`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/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | diff --git a/ui/dr-ui/src/library.rs b/ui/dr-ui/src/library.rs index 344f32c..8bc230c 100644 --- a/ui/dr-ui/src/library.rs +++ b/ui/dr-ui/src/library.rs @@ -1565,6 +1565,48 @@ pub fn spawn_pin_fetch( }; let id = RemoteId::Path(RemotePath::new(&source_ref)); + + // TRACES: FR-NC-6c + // On a placeholder library "pin" means *keep it downloaded*, + // not "make a second copy". The original materialises in the + // library folder itself, so copying it under `originals/` + // would hold every pinned photograph twice — and the copy + // would be the half the budget could evict while the real disk + // cost stayed. Only the bookkeeping is recorded, with no path, + // so nothing here can ever delete a file inside a synced tree + // (see `Cache::record_in_place`). + if backend.capabilities().materialisation.can_materialise() { + match backend.materialise(&id).await { + Ok(_) => { + let bytes = size_of(&catalog, image).unwrap_or(0); + if let Err(e) = store.record_in_place( + catalog.connection(), + image, + bytes, + true, + now_secs(), + ) { + log::warn!("recording pinned {source_ref}: {e}"); + continue; + } + stored += 1; + bytes_total += bytes; + if tx.send(PinMessage::Stored { done: stored }).is_err() { + return; + } + } + Err(e) if e.indicates_offline() => { + let _ = tx.send(PinMessage::Failed { + message: e.to_string(), + offline: true, + }); + return; + } + Err(e) => log::warn!("pinning {source_ref}: {e}"), + } + continue; + } + match backend.get(&id, None).await { Ok(bytes) => { // `pinned: true` — this is the population the budget @@ -1614,6 +1656,82 @@ pub fn spawn_pin_fetch( rx } +/// TRACES: FR-NC-6c +/// Hand a set of photographs back to the sync client, freeing their disk. +/// +/// The other half of pinning on a placeholder library. `Cache::release` drops +/// the bookkeeping and — correctly — deletes nothing, because the rows it +/// holds for a library like this name no file of ours (`record_in_place`). +/// The bytes are in the library folder, and only the client may take them +/// back. +/// +/// **This is a dehydration, not a deletion, and the distinction is the whole +/// safety of the feature.** Removing a materialised file inside a synced tree +/// propagates to the server and deletes the photograph everywhere. +/// +/// Best effort per image: a file the client refuses to release simply stays, +/// which costs disk and loses nothing. +pub fn spawn_dehydrate( + conn: Connection, + catalog_path: PathBuf, + images: Vec, +) -> Receiver { + let (tx, rx) = std::sync::mpsc::channel(); + + std::thread::spawn(move || { + let Ok(catalog) = Catalog::open(&catalog_path) else { + return; + }; + let Ok(rt) = crate::net_runtime::build() else { + return; + }; + rt.block_on(async { + let Ok(backend) = crate::remote::connect(&conn) else { + return; + }; + // Nothing to do where content is not a thing that can be given + // back — a server library, or a plain folder. + if !backend.capabilities().materialisation.can_materialise() { + return; + } + + let mut released = 0usize; + for image in images { + let Some(source_ref) = source_ref_of(&catalog, image) else { + continue; + }; + let id = RemoteId::Path(RemotePath::new(&source_ref)); + match backend.dematerialise(&id).await { + Ok(()) => released += 1, + Err(e) => log::debug!("releasing {source_ref}: {e}"), + } + } + log::info!("released {released} photograph(s) back to the sync client"); + let _ = tx.send(released); + }); + }); + + rx +} + +/// What an image occupies, as the catalog recorded it. +/// +/// Zero where the scan could not tell — a placeholder reports no size, because +/// a one-byte stub says nothing about what it stands for (ARCH §9.0a). A pin +/// that cannot state its cost is better than one that states a wrong one. +fn size_of(catalog: &Catalog, image: dr_types::ImageId) -> Option { + catalog + .connection() + .query_row( + "SELECT file_size FROM images WHERE id = ?1", + rusqlite::params![image.0 as i64], + |r| r.get::<_, Option>(0), + ) + .ok() + .flatten() + .map(|v| v.max(0) as u64) +} + /// The remote path for a catalogued image. fn source_ref_of(catalog: &Catalog, image: dr_types::ImageId) -> Option { catalog @@ -3017,6 +3135,13 @@ pub fn spawn_face_sweep( let (mut done, mut images, mut found, mut failed) = (0usize, 0usize, 0usize, 0usize); let mut offline = false; + // TRACES: FR-NC-6c + // The same borrow the thumbnail sweep makes, and deliberately its + // own pool: the two passes run at different times, so sharing one + // would keep every file the earlier pass touched hydrated until + // the later one finished. Each gives its own back (ARCH §9.0a). + let pool = dr_sync_folder::BorrowPool::new(); + for chunk in wanted.chunks(SWEEP_CHUNK) { let lanes: Vec> = (0..SWEEP_LANES) .map(|lane| chunk.iter().skip(lane).step_by(SWEEP_LANES).collect()) @@ -3026,6 +3151,7 @@ pub fn spawn_face_sweep( let backend = &*backend; let models = ⊧ let options = &options; + let pool = &pool; async move { let mut indexed: Vec = Vec::new(); let mut discard = Vec::new(); @@ -3034,6 +3160,23 @@ pub fn spawn_face_sweep( let mut offline = false; for req in lane { attempted += 1; + + let _held = + match pool.borrow(backend, &RemotePath::new(&req.path)).await { + Ok(h) => h, + Err(e) if e.indicates_offline() => { + log::info!("face sweep: {e}"); + attempted -= 1; + offline = true; + break; + } + Err(e) => { + log::debug!("face sweep: {}: {e}", req.path); + failed += 1; + continue; + } + }; + match fetch_preview(backend, req, &mut discard).await { PreviewOutcome::Ready(mut preview) => { // No await inside this borrow — see the @@ -3138,8 +3281,13 @@ pub fn spawn_face_sweep( { // Receiver dropped: the screen closed, or // the user pressed Stop. Everything written - // so far stays written. + // so far stays written — and everything + // borrowed is given back. A cancelled pass + // that kept the library hydrated would be + // the worst of both: the disk spent and + // the work abandoned. log::info!("face sweep: cancelled after {images} image(s)"); + pool.release_all(&*backend).await; return; } } @@ -3157,6 +3305,14 @@ pub fn spawn_face_sweep( } } + let returned = pool.release_all(&*backend).await; + if returned.released > 0 { + log::info!( + "face sweep: released {} borrowed file(s)", + returned.released + ); + } + log::info!( "face sweep: {found} face(s) across {images} image(s), {failed} failed{}", if offline { ", server went away" } else { "" } @@ -3317,6 +3473,15 @@ pub fn spawn_thumbnail_sweep( let mut offline = false; let mut found = Vec::new(); + // TRACES: FR-NC-6c + // On a placeholder library the bytes may not be here at all, and + // this is a pass the user asked for — so it may fetch them, which + // browsing may not (ARCH §9.0a). Every file is *borrowed*: what + // this pass downloads it gives back, and what the user already had + // it leaves alone. Against a server or a plain folder every borrow + // is a no-op, so there is one code path rather than two. + let pool = dr_sync_folder::BorrowPool::new(); + for chunk in wanted.chunks(SWEEP_CHUNK) { // Each lane owns a disjoint slice and its own output, so // nothing is shared and no lock is needed. The store is not @@ -3327,6 +3492,7 @@ pub fn spawn_thumbnail_sweep( let results = futures_join_all(lanes.into_iter().map(|lane| { let backend: &dyn RemoteBackend = &*backend; + let pool = &pool; async move { let mut made: Vec<(u64, dr_thumbs::Thumbnail)> = Vec::new(); let mut found = Vec::new(); @@ -3339,6 +3505,27 @@ pub fn spawn_thumbnail_sweep( // store on and is not a candidate. let Some(file_id) = req.file_id else { continue }; attempted += 1; + + // Held for this image only. A failure to fetch is + // this image's verdict, not the batch's: a client + // that cannot reach the server reports it as + // offline through the usual path below. + let _held = + match pool.borrow(backend, &RemotePath::new(&req.path)).await { + Ok(h) => h, + Err(e) if e.indicates_offline() => { + log::info!("thumbnail sweep: {e}"); + attempted -= 1; + offline = true; + break; + } + Err(e) => { + log::debug!("thumbnail sweep: {}: {e}", req.path); + failed += 1; + continue; + } + }; + match fetch_preview(backend, req, &mut found).await { PreviewOutcome::Ready(preview) => { match encode_preview(file_id, &preview) { @@ -3389,6 +3576,10 @@ pub fn spawn_thumbnail_sweep( .send(ThumbSweepMessage::Progress { done, stored }) .is_err() { + // Cancelled. Hand back what was borrowed before leaving, + // or a stopped pass costs the disk of everything it had + // reached and delivers nothing for it. + pool.release_all(&*backend).await; return; } if offline { @@ -3397,6 +3588,19 @@ pub fn spawn_thumbnail_sweep( } flush_sweep(&catalog, &mut found); + + // Give back everything this pass fetched, before reporting done — + // a user watching the disk should see it return, and a pass that + // reported success while still holding the library would be + // lying about what it cost. + let returned = pool.release_all(&*backend).await; + if returned.released > 0 { + log::info!( + "thumbnail sweep: released {} borrowed file(s)", + returned.released + ); + } + log::info!("thumbnail sweep: {stored} stored, {failed} without a usable preview"); let _ = tx.send(ThumbSweepMessage::Finished { stored, diff --git a/ui/dr-ui/src/library_ui.rs b/ui/dr-ui/src/library_ui.rs index 51ca77b..ae6844d 100644 --- a/ui/dr-ui/src/library_ui.rs +++ b/ui/dr-ui/src/library_ui.rs @@ -1326,13 +1326,30 @@ fn release_collection_offline( } }; let images = collection_images(catalog, &ids); - match cache.release(catalog.connection(), &images) { + let outcome = match cache.release(catalog.connection(), &images) { Ok(r) => r, Err(e) => { window.set_library_error(format!("removing local copies: {e}").into()); return; } + }; + + // TRACES: FR-NC-6c + // On a placeholder library the bookkeeping above owns no files, so it + // freed nothing — the originals are materialised in the library folder + // and only the sync client may take them back. Asking it to is what + // makes unpinning actually return the disk, and it must be a + // dehydration rather than a delete: removing a file inside a synced + // tree propagates to the server (ARCH §9.0a). + // + // Fire and forget: it is per-file work over a socket, the user has + // already been told the pin is withdrawn, and a client that refuses + // leaves the content where it is at no cost but disk. + if let Some(conn) = ctl.session() { + let path = library::catalog_path(&conn.account); + std::mem::drop(library::spawn_dehydrate(conn, path, images)); } + outcome }; let (count, freed) = released;