From cb7b9d71c47b0f369e3c776b0185ab4d394630e9 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 20 Sep 2026 19:17:22 +0200 Subject: [PATCH] Split lib.rs::run into named construction and wiring functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run() was 2,724 lines that built every controller, owned the develop session and its render loop, and registered every develop-screen callback inline — the state CH-1 in docs/dev/code-health.md describes. This is the mechanical split CH-1 calls for, done in one pass rather than section by section since develop_ui.rs only compiles once lib.rs stops registering those callbacks itself. develop_ui.rs is new: a DevelopWiring struct holding the session, the rows model, the redraw/render closures and the other controllers' handles, and one wire_* function per section run() used to contain — presets, export, the adjustment panel, film, undo/redo, zoom/pan/crop, rotation/flips/ straightening, navigation and peaking — called in the order run() registered them. window travels as its own parameter throughout rather than living on the struct, because the generated AppWindow type is not Clone; every other field is an owned clone so each function's body could be pasted from run() unchanged. lib.rs::run is now under 300 lines: construction and startup only, calling named functions for the window and its diagnostics/inference wiring, the launch/library/collections/identity/settings screens, import and merge, the render loop (render_now/redraw/show), the remote open path, the window chrome (resize, layout class, panel toggles, back gesture), and develop_ui::wire for the rest. Every TRACES and GESTURE comment moved with the code it annotates. Two latent type errors surfaced while restructuring rather than being introduced by it: activity and display were being passed around as bare ActivityLog/DisplayWatch instead of the Rc<...> their constructors actually return, which only worked before because nothing needed to name the type explicitly. --- docs/dev/traceability.md | 82 +- ui/dr-ui/src/develop_ui.rs | 1272 +++++++++++++++++++ ui/dr-ui/src/lib.rs | 2398 ++++++++++++------------------------ 3 files changed, 2088 insertions(+), 1664 deletions(-) create mode 100644 ui/dr-ui/src/develop_ui.rs diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md index 7bb6bc7..13d9648 100644 --- a/docs/dev/traceability.md +++ b/docs/dev/traceability.md @@ -9,7 +9,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 414 | +| Source files scanned | 415 | | TRACES tags found | 1571 | | Requirements defined | 184 | | Requirements deferred (post-v1) | 24 | @@ -35,8 +35,8 @@ A tag naming an ID `requirements.md` does not define — what renumbering produc | ID | Tagged in | |---|---| | 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:129`](../../core/dr-sync/src/scan.rs#L129), [`core/dr-types/src/lib.rs:203`](../../core/dr-types/src/lib.rs#L203), [`core/dr-types/src/lib.rs:272`](../../core/dr-types/src/lib.rs#L272), [`core/dr-types/src/lib.rs:305`](../../core/dr-types/src/lib.rs#L305), [`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), [`ui/dr-ui/src/activity.rs:1`](../../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.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:377`](../../core/dr-types/src/settings.rs#L377), [`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:1559`](../../ui/dr-ui/src/lib.rs#L1559), [`ui/dr-ui/ui/import.slint:5`](../../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1033`](../../ui/dr-ui/ui/library.slint#L1033), [`ui/dr-ui/ui/library.slint:1254`](../../ui/dr-ui/ui/library.slint#L1254), [`ui/dr-ui/ui/library.slint:979`](../../ui/dr-ui/ui/library.slint#L979) | -| 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:1559`](../../ui/dr-ui/src/lib.rs#L1559), [`ui/dr-ui/src/library/filters.rs:12`](../../ui/dr-ui/src/library/filters.rs#L12), [`ui/dr-ui/src/library/thumbnails_gen.rs:518`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L518), [`ui/dr-ui/ui/import.slint:5`](../../ui/dr-ui/ui/import.slint#L5) | +| 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:377`](../../core/dr-types/src/settings.rs#L377), [`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:1784`](../../ui/dr-ui/src/lib.rs#L1784), [`ui/dr-ui/ui/import.slint:5`](../../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1033`](../../ui/dr-ui/ui/library.slint#L1033), [`ui/dr-ui/ui/library.slint:1254`](../../ui/dr-ui/ui/library.slint#L1254), [`ui/dr-ui/ui/library.slint:979`](../../ui/dr-ui/ui/library.slint#L979) | +| 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:1784`](../../ui/dr-ui/src/lib.rs#L1784), [`ui/dr-ui/src/library/filters.rs:12`](../../ui/dr-ui/src/library/filters.rs#L12), [`ui/dr-ui/src/library/thumbnails_gen.rs:518`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L518), [`ui/dr-ui/ui/import.slint:5`](../../ui/dr-ui/ui/import.slint#L5) | | FR-CAT-12 | [`core/dr-pipeline/src/sidecar.rs:137`](../../core/dr-pipeline/src/sidecar.rs#L137) | | FR-CAT-13 | [`core/dr-catalog/src/rating.rs:278`](../../core/dr-catalog/src/rating.rs#L278), [`core/dr-catalog/src/schema.rs:836`](../../core/dr-catalog/src/schema.rs#L836), [`core/dr-sync/src/scan.rs:40`](../../core/dr-sync/src/scan.rs#L40), [`core/dr-sync/src/scan.rs:944`](../../core/dr-sync/src/scan.rs#L944), [`core/dr-types/src/settings.rs:92`](../../core/dr-types/src/settings.rs#L92), [`core/dr-xmp/src/lib.rs:1`](../../core/dr-xmp/src/lib.rs#L1), [`core/dr-xmp/src/lib.rs:331`](../../core/dr-xmp/src/lib.rs#L331), [`core/dr-xmp/src/lib.rs:442`](../../core/dr-xmp/src/lib.rs#L442), [`core/dr-xmp/src/lib.rs:535`](../../core/dr-xmp/src/lib.rs#L535), [`core/dr-xmp/src/lib.rs:733`](../../core/dr-xmp/src/lib.rs#L733), [`core/dr-xmp/src/lib.rs:746`](../../core/dr-xmp/src/lib.rs#L746), [`core/dr-xmp/src/read.rs:1`](../../core/dr-xmp/src/read.rs#L1), [`core/dr-xmp/src/write.rs:1`](../../core/dr-xmp/src/write.rs#L1), [`core/dr-xmp/tests/round_trip.rs:1`](../../core/dr-xmp/tests/round_trip.rs#L1), [`ui/dr-ui/src/library/scan.rs:335`](../../ui/dr-ui/src/library/scan.rs#L335), [`ui/dr-ui/src/library/xmp.rs:132`](../../ui/dr-ui/src/library/xmp.rs#L132), [`ui/dr-ui/src/library/xmp.rs:25`](../../ui/dr-ui/src/library/xmp.rs#L25), [`ui/dr-ui/src/library/xmp.rs:9`](../../ui/dr-ui/src/library/xmp.rs#L9), [`ui/dr-ui/src/library_ui.rs:1322`](../../ui/dr-ui/src/library_ui.rs#L1322), [`ui/dr-ui/src/library_ui.rs:301`](../../ui/dr-ui/src/library_ui.rs#L301), [`ui/dr-ui/src/library_ui.rs:3427`](../../ui/dr-ui/src/library_ui.rs#L3427), [`ui/dr-ui/src/library_ui.rs:3483`](../../ui/dr-ui/src/library_ui.rs#L3483), [`ui/dr-ui/src/library_ui.rs:3581`](../../ui/dr-ui/src/library_ui.rs#L3581), [`ui/dr-ui/src/library_ui.rs:392`](../../ui/dr-ui/src/library_ui.rs#L392), [`ui/dr-ui/src/library_ui.rs:537`](../../ui/dr-ui/src/library_ui.rs#L537), [`ui/dr-ui/src/library_ui.rs:5766`](../../ui/dr-ui/src/library_ui.rs#L5766), [`ui/dr-ui/src/settings_ui.rs:558`](../../ui/dr-ui/src/settings_ui.rs#L558), [`ui/dr-ui/src/xmp_sync.rs:1`](../../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/src/xmp_sync.rs:250`](../../ui/dr-ui/src/xmp_sync.rs#L250), [`ui/dr-ui/src/xmp_sync.rs:287`](../../ui/dr-ui/src/xmp_sync.rs#L287), [`ui/dr-ui/src/xmp_sync.rs:324`](../../ui/dr-ui/src/xmp_sync.rs#L324), [`ui/dr-ui/ui/app.slint:900`](../../ui/dr-ui/ui/app.slint#L900), [`ui/dr-ui/ui/settings.slint:1093`](../../ui/dr-ui/ui/settings.slint#L1093), [`ui/dr-ui/ui/settings.slint:199`](../../ui/dr-ui/ui/settings.slint#L199) | | FR-CAT-15 | [`core/dr-catalog/src/schema.rs:1210`](../../core/dr-catalog/src/schema.rs#L1210), [`core/dr-catalog/src/trash.rs:1`](../../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:522`](../../core/dr-sync-nextcloud/src/lib.rs#L522), [`core/dr-sync/src/lib.rs:135`](../../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:618`](../../core/dr-sync/src/scan.rs#L618), [`core/dr-sync/src/scan.rs:93`](../../core/dr-sync/src/scan.rs#L93), [`core/dr-thumbs/src/lib.rs:393`](../../core/dr-thumbs/src/lib.rs#L393), [`core/dr-types/src/place.rs:91`](../../core/dr-types/src/place.rs#L91), [`ui/dr-ui/src/collections_ui.rs:1435`](../../ui/dr-ui/src/collections_ui.rs#L1435), [`ui/dr-ui/src/collections_ui.rs:2341`](../../ui/dr-ui/src/collections_ui.rs#L2341), [`ui/dr-ui/src/library/cells.rs:158`](../../ui/dr-ui/src/library/cells.rs#L158), [`ui/dr-ui/src/library/cells.rs:192`](../../ui/dr-ui/src/library/cells.rs#L192), [`ui/dr-ui/src/library/filters.rs:12`](../../ui/dr-ui/src/library/filters.rs#L12), [`ui/dr-ui/src/library/filters.rs:29`](../../ui/dr-ui/src/library/filters.rs#L29), [`ui/dr-ui/src/library/filters.rs:59`](../../ui/dr-ui/src/library/filters.rs#L59), [`ui/dr-ui/src/library_ui.rs:238`](../../ui/dr-ui/src/library_ui.rs#L238), [`ui/dr-ui/src/library_ui.rs:914`](../../ui/dr-ui/src/library_ui.rs#L914), [`ui/dr-ui/src/trash.rs:1`](../../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:745`](../../ui/dr-ui/ui/collections.slint#L745) | @@ -47,60 +47,60 @@ A tag naming an ID `requirements.md` does not define — what renumbering produc | 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:1125`](../../core/dr-catalog/src/schema.rs#L1125), [`core/dr-catalog/src/schema.rs:1682`](../../core/dr-catalog/src/schema.rs#L1682), [`core/dr-decode/src/lib.rs:301`](../../core/dr-decode/src/lib.rs#L301), [`core/dr-decode/src/lib.rs:307`](../../core/dr-decode/src/lib.rs#L307), [`core/dr-decode/src/lib.rs:317`](../../core/dr-decode/src/lib.rs#L317), [`core/dr-decode/src/lib.rs:444`](../../core/dr-decode/src/lib.rs#L444), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:171`](../../core/dr-pipeline/src/sidecar.rs#L171), [`ui/dr-ui/src/collections_ui.rs:1731`](../../ui/dr-ui/src/collections_ui.rs#L1731), [`ui/dr-ui/src/collections_ui.rs:1837`](../../ui/dr-ui/src/collections_ui.rs#L1837), [`ui/dr-ui/src/collections_ui.rs:1886`](../../ui/dr-ui/src/collections_ui.rs#L1886), [`ui/dr-ui/src/collections_ui.rs:275`](../../ui/dr-ui/src/collections_ui.rs#L275), [`ui/dr-ui/src/collections_ui.rs:484`](../../ui/dr-ui/src/collections_ui.rs#L484), [`ui/dr-ui/src/collections_ui.rs:87`](../../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library/cells.rs:206`](../../ui/dr-ui/src/library/cells.rs#L206), [`ui/dr-ui/src/library_ui.rs:789`](../../ui/dr-ui/src/library_ui.rs#L789), [`ui/dr-ui/src/library_ui.rs:8226`](../../ui/dr-ui/src/library_ui.rs#L8226), [`ui/dr-ui/src/library_ui.rs:8237`](../../ui/dr-ui/src/library_ui.rs#L8237), [`ui/dr-ui/src/library_ui.rs:8250`](../../ui/dr-ui/src/library_ui.rs#L8250), [`ui/dr-ui/src/library_ui.rs:8265`](../../ui/dr-ui/src/library_ui.rs#L8265), [`ui/dr-ui/src/library_ui.rs:8274`](../../ui/dr-ui/src/library_ui.rs#L8274), [`ui/dr-ui/ui/app.slint:308`](../../ui/dr-ui/ui/app.slint#L308), [`ui/dr-ui/ui/app.slint:575`](../../ui/dr-ui/ui/app.slint#L575), [`ui/dr-ui/ui/library.slint:1360`](../../ui/dr-ui/ui/library.slint#L1360), [`ui/dr-ui/ui/library.slint:1384`](../../ui/dr-ui/ui/library.slint#L1384), [`ui/dr-ui/ui/library.slint:1437`](../../ui/dr-ui/ui/library.slint#L1437), [`ui/dr-ui/ui/library.slint:19`](../../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:4246`](../../ui/dr-ui/ui/library.slint#L4246) | | 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:1125`](../../core/dr-catalog/src/schema.rs#L1125), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/spans.rs:253`](../../ui/dr-ui/src/library/spans.rs#L253), [`ui/dr-ui/src/library_ui.rs:396`](../../ui/dr-ui/src/library_ui.rs#L396), [`ui/dr-ui/src/library_ui.rs:505`](../../ui/dr-ui/src/library_ui.rs#L505), [`ui/dr-ui/src/library_ui.rs:6822`](../../ui/dr-ui/src/library_ui.rs#L6822), [`ui/dr-ui/src/library_ui.rs:6875`](../../ui/dr-ui/src/library_ui.rs#L6875), [`ui/dr-ui/src/library_ui.rs:8366`](../../ui/dr-ui/src/library_ui.rs#L8366), [`ui/dr-ui/ui/app.slint:320`](../../ui/dr-ui/ui/app.slint#L320), [`ui/dr-ui/ui/app.slint:575`](../../ui/dr-ui/ui/app.slint#L575), [`ui/dr-ui/ui/app.slint:896`](../../ui/dr-ui/ui/app.slint#L896), [`ui/dr-ui/ui/library.slint:111`](../../ui/dr-ui/ui/library.slint#L111), [`ui/dr-ui/ui/library.slint:1552`](../../ui/dr-ui/ui/library.slint#L1552), [`ui/dr-ui/ui/library.slint:4246`](../../ui/dr-ui/ui/library.slint#L4246), [`ui/dr-ui/ui/settings.slint:191`](../../ui/dr-ui/ui/settings.slint#L191) | | 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:1117`](../../ui/dr-ui/src/collections_ui.rs#L1117), [`ui/dr-ui/src/collections_ui.rs:117`](../../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1225`](../../ui/dr-ui/src/collections_ui.rs#L1225), [`ui/dr-ui/src/collections_ui.rs:1704`](../../ui/dr-ui/src/collections_ui.rs#L1704), [`ui/dr-ui/src/collections_ui.rs:1750`](../../ui/dr-ui/src/collections_ui.rs#L1750), [`ui/dr-ui/src/collections_ui.rs:1`](../../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2000`](../../ui/dr-ui/src/collections_ui.rs#L2000), [`ui/dr-ui/src/collections_ui.rs:2065`](../../ui/dr-ui/src/collections_ui.rs#L2065), [`ui/dr-ui/src/collections_ui.rs:2125`](../../ui/dr-ui/src/collections_ui.rs#L2125), [`ui/dr-ui/src/collections_ui.rs:2701`](../../ui/dr-ui/src/collections_ui.rs#L2701), [`ui/dr-ui/src/collections_ui.rs:2755`](../../ui/dr-ui/src/collections_ui.rs#L2755), [`ui/dr-ui/src/collections_ui.rs:2801`](../../ui/dr-ui/src/collections_ui.rs#L2801), [`ui/dr-ui/src/collections_ui.rs:2903`](../../ui/dr-ui/src/collections_ui.rs#L2903), [`ui/dr-ui/src/collections_ui.rs:2959`](../../ui/dr-ui/src/collections_ui.rs#L2959), [`ui/dr-ui/src/collections_ui.rs:3120`](../../ui/dr-ui/src/collections_ui.rs#L3120), [`ui/dr-ui/src/collections_ui.rs:3142`](../../ui/dr-ui/src/collections_ui.rs#L3142), [`ui/dr-ui/src/collections_ui.rs:3208`](../../ui/dr-ui/src/collections_ui.rs#L3208), [`ui/dr-ui/src/collections_ui.rs:329`](../../ui/dr-ui/src/collections_ui.rs#L329), [`ui/dr-ui/src/collections_ui.rs:3328`](../../ui/dr-ui/src/collections_ui.rs#L3328), [`ui/dr-ui/src/collections_ui.rs:339`](../../ui/dr-ui/src/collections_ui.rs#L339), [`ui/dr-ui/src/collections_ui.rs:362`](../../ui/dr-ui/src/collections_ui.rs#L362), [`ui/dr-ui/src/collections_ui.rs:3633`](../../ui/dr-ui/src/collections_ui.rs#L3633), [`ui/dr-ui/src/collections_ui.rs:383`](../../ui/dr-ui/src/collections_ui.rs#L383), [`ui/dr-ui/src/collections_ui.rs:514`](../../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:718`](../../ui/dr-ui/src/collections_ui.rs#L718), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/spans.rs:139`](../../ui/dr-ui/src/library/spans.rs#L139), [`ui/dr-ui/src/library/spans.rs:58`](../../ui/dr-ui/src/library/spans.rs#L58), [`ui/dr-ui/src/library/spans.rs:84`](../../ui/dr-ui/src/library/spans.rs#L84), [`ui/dr-ui/src/library_ui.rs:2774`](../../ui/dr-ui/src/library_ui.rs#L2774), [`ui/dr-ui/src/library_ui.rs:4369`](../../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/library_ui.rs:5128`](../../ui/dr-ui/src/library_ui.rs#L5128), [`ui/dr-ui/ui/app.slint:311`](../../ui/dr-ui/ui/app.slint#L311), [`ui/dr-ui/ui/app.slint:3213`](../../ui/dr-ui/ui/app.slint#L3213), [`ui/dr-ui/ui/app.slint:570`](../../ui/dr-ui/ui/app.slint#L570), [`ui/dr-ui/ui/app.slint:599`](../../ui/dr-ui/ui/app.slint#L599), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:587`](../../ui/dr-ui/ui/collections.slint#L587), [`ui/dr-ui/ui/library.slint:1327`](../../ui/dr-ui/ui/library.slint#L1327), [`ui/dr-ui/ui/library.slint:2942`](../../ui/dr-ui/ui/library.slint#L2942), [`ui/dr-ui/ui/library.slint:4224`](../../ui/dr-ui/ui/library.slint#L4224), [`ui/dr-ui/ui/library.slint:4311`](../../ui/dr-ui/ui/library.slint#L4311), [`ui/dr-ui/ui/library.slint:998`](../../ui/dr-ui/ui/library.slint#L998) | -| FR-CAT-8 | [`core/dr-catalog/src/schema.rs:740`](../../core/dr-catalog/src/schema.rs#L740), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:640`](../../core/dr-pipeline/src/graph.rs#L640), [`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:1400`](../../core/dr-pipeline/src/sidecar.rs#L1400), [`core/dr-pipeline/src/sidecar.rs:2232`](../../core/dr-pipeline/src/sidecar.rs#L2232), [`core/dr-pipeline/src/sidecar.rs:96`](../../core/dr-pipeline/src/sidecar.rs#L96), [`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), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/masks.rs:740`](../../ui/dr-ui/src/develop/masks.rs#L740), [`ui/dr-ui/src/develop/session.rs:561`](../../ui/dr-ui/src/develop/session.rs#L561), [`ui/dr-ui/src/develop/session.rs:590`](../../ui/dr-ui/src/develop/session.rs#L590), [`ui/dr-ui/src/export.rs:812`](../../ui/dr-ui/src/export.rs#L812), [`ui/dr-ui/src/lib.rs:1330`](../../ui/dr-ui/src/lib.rs#L1330), [`ui/dr-ui/src/lib.rs:1888`](../../ui/dr-ui/src/lib.rs#L1888), [`ui/dr-ui/src/lib.rs:2457`](../../ui/dr-ui/src/lib.rs#L2457), [`ui/dr-ui/src/lib.rs:2604`](../../ui/dr-ui/src/lib.rs#L2604), [`ui/dr-ui/src/lib.rs:757`](../../ui/dr-ui/src/lib.rs#L757), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:373`](../../ui/dr-ui/src/library/sidecar.rs#L373), [`ui/dr-ui/src/library/sidecar.rs:63`](../../ui/dr-ui/src/library/sidecar.rs#L63), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui.rs:1339`](../../ui/dr-ui/src/library_ui.rs#L1339), [`ui/dr-ui/src/library_ui.rs:6389`](../../ui/dr-ui/src/library_ui.rs#L6389), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-CAT-8 | [`core/dr-catalog/src/schema.rs:740`](../../core/dr-catalog/src/schema.rs#L740), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:640`](../../core/dr-pipeline/src/graph.rs#L640), [`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:1400`](../../core/dr-pipeline/src/sidecar.rs#L1400), [`core/dr-pipeline/src/sidecar.rs:2232`](../../core/dr-pipeline/src/sidecar.rs#L2232), [`core/dr-pipeline/src/sidecar.rs:96`](../../core/dr-pipeline/src/sidecar.rs#L96), [`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), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/masks.rs:740`](../../ui/dr-ui/src/develop/masks.rs#L740), [`ui/dr-ui/src/develop/session.rs:561`](../../ui/dr-ui/src/develop/session.rs#L561), [`ui/dr-ui/src/develop/session.rs:590`](../../ui/dr-ui/src/develop/session.rs#L590), [`ui/dr-ui/src/export.rs:812`](../../ui/dr-ui/src/export.rs#L812), [`ui/dr-ui/src/lib.rs:1183`](../../ui/dr-ui/src/lib.rs#L1183), [`ui/dr-ui/src/lib.rs:1243`](../../ui/dr-ui/src/lib.rs#L1243), [`ui/dr-ui/src/lib.rs:2685`](../../ui/dr-ui/src/lib.rs#L2685), [`ui/dr-ui/src/lib.rs:2832`](../../ui/dr-ui/src/lib.rs#L2832), [`ui/dr-ui/src/lib.rs:758`](../../ui/dr-ui/src/lib.rs#L758), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:373`](../../ui/dr-ui/src/library/sidecar.rs#L373), [`ui/dr-ui/src/library/sidecar.rs:63`](../../ui/dr-ui/src/library/sidecar.rs#L63), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui.rs:1339`](../../ui/dr-ui/src/library_ui.rs#L1339), [`ui/dr-ui/src/library_ui.rs:6389`](../../ui/dr-ui/src/library_ui.rs#L6389), [`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:1182`](../../core/dr-catalog/src/schema.rs#L1182), [`core/dr-catalog/src/walk.rs:1022`](../../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:35`](../../core/dr-sync-nextcloud/src/desktop_client.rs#L35), [`core/dr-sync/src/error.rs:113`](../../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:211`](../../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../../core/dr-sync/src/scan.rs#L519), [`core/dr-types/src/lib.rs:122`](../../core/dr-types/src/lib.rs#L122), [`ui/dr-ui/src/develop/render.rs:584`](../../ui/dr-ui/src/develop/render.rs#L584), [`ui/dr-ui/src/develop/render.rs:627`](../../ui/dr-ui/src/develop/render.rs#L627), [`ui/dr-ui/src/library/filters.rs:84`](../../ui/dr-ui/src/library/filters.rs#L84), [`ui/dr-ui/src/library/scan.rs:126`](../../ui/dr-ui/src/library/scan.rs#L126), [`ui/dr-ui/src/library/scan.rs:179`](../../ui/dr-ui/src/library/scan.rs#L179), [`ui/dr-ui/src/library/scan.rs:210`](../../ui/dr-ui/src/library/scan.rs#L210), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:605`](../../ui/dr-ui/src/library/scan.rs#L605), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:357`](../../ui/dr-ui/src/library/sidecar.rs#L357), [`ui/dr-ui/src/library/sidecar.rs:373`](../../ui/dr-ui/src/library/sidecar.rs#L373), [`ui/dr-ui/src/library/sidecar.rs:427`](../../ui/dr-ui/src/library/sidecar.rs#L427), [`ui/dr-ui/src/library/spans.rs:361`](../../ui/dr-ui/src/library/spans.rs#L361), [`ui/dr-ui/src/library/thumbnails_fetch.rs:393`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L393), [`ui/dr-ui/src/library/thumbnails_fetch.rs:449`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L449), [`ui/dr-ui/src/library/thumbnails_gen.rs:74`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L74), [`ui/dr-ui/src/library_ui.rs:1406`](../../ui/dr-ui/src/library_ui.rs#L1406), [`ui/dr-ui/src/library_ui.rs:1997`](../../ui/dr-ui/src/library_ui.rs#L1997), [`ui/dr-ui/src/library_ui.rs:2041`](../../ui/dr-ui/src/library_ui.rs#L2041), [`ui/dr-ui/src/library_ui.rs:2057`](../../ui/dr-ui/src/library_ui.rs#L2057), [`ui/dr-ui/src/library_ui.rs:2151`](../../ui/dr-ui/src/library_ui.rs#L2151), [`ui/dr-ui/src/library_ui.rs:284`](../../ui/dr-ui/src/library_ui.rs#L284), [`ui/dr-ui/src/library_ui.rs:2993`](../../ui/dr-ui/src/library_ui.rs#L2993), [`ui/dr-ui/src/library_ui.rs:320`](../../ui/dr-ui/src/library_ui.rs#L320), [`ui/dr-ui/src/library_ui.rs:328`](../../ui/dr-ui/src/library_ui.rs#L328), [`ui/dr-ui/src/library_ui.rs:3616`](../../ui/dr-ui/src/library_ui.rs#L3616), [`ui/dr-ui/src/library_ui.rs:3838`](../../ui/dr-ui/src/library_ui.rs#L3838), [`ui/dr-ui/src/library_ui.rs:4116`](../../ui/dr-ui/src/library_ui.rs#L4116), [`ui/dr-ui/src/library_ui.rs:4232`](../../ui/dr-ui/src/library_ui.rs#L4232), [`ui/dr-ui/src/library_ui.rs:4420`](../../ui/dr-ui/src/library_ui.rs#L4420), [`ui/dr-ui/src/library_ui.rs:4542`](../../ui/dr-ui/src/library_ui.rs#L4542), [`ui/dr-ui/src/library_ui.rs:573`](../../ui/dr-ui/src/library_ui.rs#L573), [`ui/dr-ui/src/library_ui.rs:662`](../../ui/dr-ui/src/library_ui.rs#L662), [`ui/dr-ui/src/library_ui.rs:6928`](../../ui/dr-ui/src/library_ui.rs#L6928), [`ui/dr-ui/src/library_ui.rs:7010`](../../ui/dr-ui/src/library_ui.rs#L7010), [`ui/dr-ui/src/presets.rs:486`](../../ui/dr-ui/src/presets.rs#L486), [`ui/dr-ui/src/presets.rs:498`](../../ui/dr-ui/src/presets.rs#L498), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.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:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`core/dr-catalog/src/schema.rs:603`](../../core/dr-catalog/src/schema.rs#L603), [`core/dr-catalog/src/schema.rs:781`](../../core/dr-catalog/src/schema.rs#L781), [`core/dr-face/src/assign.rs:1`](../../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../../core/dr-face/src/neighbours.rs#L1), [`core/dr-face/src/references.rs:1`](../../core/dr-face/src/references.rs#L1), [`core/dr-types/src/settings.rs:129`](../../core/dr-types/src/settings.rs#L129), [`ui/dr-ui/src/develop/segmentation.rs:104`](../../ui/dr-ui/src/develop/segmentation.rs#L104), [`ui/dr-ui/src/develop/segmentation.rs:170`](../../ui/dr-ui/src/develop/segmentation.rs#L170), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:95`](../../ui/dr-ui/src/develop/segmentation.rs#L95), [`ui/dr-ui/src/develop/session.rs:23`](../../ui/dr-ui/src/develop/session.rs#L23), [`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:1325`](../../ui/dr-ui/src/identity_ui.rs#L1325), [`ui/dr-ui/src/identity_ui.rs:1`](../../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2576`](../../ui/dr-ui/src/lib.rs#L2576), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../../ui/dr-ui/ui/identity.slint#L1), [`ui/dr-ui/ui/settings.slint:529`](../../ui/dr-ui/ui/settings.slint#L529), [`ui/dr-ui/ui/settings.slint:94`](../../ui/dr-ui/ui/settings.slint#L94) | +| FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`core/dr-catalog/src/schema.rs:603`](../../core/dr-catalog/src/schema.rs#L603), [`core/dr-catalog/src/schema.rs:781`](../../core/dr-catalog/src/schema.rs#L781), [`core/dr-face/src/assign.rs:1`](../../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../../core/dr-face/src/neighbours.rs#L1), [`core/dr-face/src/references.rs:1`](../../core/dr-face/src/references.rs#L1), [`core/dr-types/src/settings.rs:129`](../../core/dr-types/src/settings.rs#L129), [`ui/dr-ui/src/develop/segmentation.rs:104`](../../ui/dr-ui/src/develop/segmentation.rs#L104), [`ui/dr-ui/src/develop/segmentation.rs:170`](../../ui/dr-ui/src/develop/segmentation.rs#L170), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:95`](../../ui/dr-ui/src/develop/segmentation.rs#L95), [`ui/dr-ui/src/develop/session.rs:23`](../../ui/dr-ui/src/develop/session.rs#L23), [`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:1325`](../../ui/dr-ui/src/identity_ui.rs#L1325), [`ui/dr-ui/src/identity_ui.rs:1`](../../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2804`](../../ui/dr-ui/src/lib.rs#L2804), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../../ui/dr-ui/ui/identity.slint#L1), [`ui/dr-ui/ui/settings.slint:529`](../../ui/dr-ui/ui/settings.slint#L529), [`ui/dr-ui/ui/settings.slint:94`](../../ui/dr-ui/ui/settings.slint#L94) | | FR-CULL-11 | [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`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/filters.rs:105`](../../ui/dr-ui/src/library/filters.rs#L105), [`ui/dr-ui/src/library/filters.rs:155`](../../ui/dr-ui/src/library/filters.rs#L155), [`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:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`core/dr-catalog/src/schema.rs:603`](../../core/dr-catalog/src/schema.rs#L603), [`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-13 | [`core/dr-catalog/src/schema.rs:917`](../../core/dr-catalog/src/schema.rs#L917), [`core/dr-types/src/place.rs:129`](../../core/dr-types/src/place.rs#L129), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:131`](../../ui/dr-ui/src/library/filters.rs#L131), [`ui/dr-ui/src/library/filters.rs:683`](../../ui/dr-ui/src/library/filters.rs#L683), [`ui/dr-ui/src/library/filters.rs:726`](../../ui/dr-ui/src/library/filters.rs#L726), [`ui/dr-ui/src/library/filters.rs:764`](../../ui/dr-ui/src/library/filters.rs#L764), [`ui/dr-ui/ui/app.slint:707`](../../ui/dr-ui/ui/app.slint#L707), [`ui/dr-ui/ui/identity.slint:63`](../../ui/dr-ui/ui/identity.slint#L63), [`ui/dr-ui/ui/library.slint:1512`](../../ui/dr-ui/ui/library.slint#L1512) | | FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../../core/dr-decode/src/preview.rs#L148), [`ui/dr-ui/src/import.rs:463`](../../ui/dr-ui/src/import.rs#L463) | -| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1203`](../../ui/dr-ui/src/develop/render.rs#L1203), [`ui/dr-ui/src/develop/render.rs:1267`](../../ui/dr-ui/src/develop/render.rs#L1267), [`ui/dr-ui/src/develop/render.rs:1290`](../../ui/dr-ui/src/develop/render.rs#L1290), [`ui/dr-ui/src/develop/render.rs:256`](../../ui/dr-ui/src/develop/render.rs#L256), [`ui/dr-ui/src/develop/render.rs:269`](../../ui/dr-ui/src/develop/render.rs#L269), [`ui/dr-ui/src/develop/render.rs:314`](../../ui/dr-ui/src/develop/render.rs#L314), [`ui/dr-ui/src/develop/render.rs:325`](../../ui/dr-ui/src/develop/render.rs#L325), [`ui/dr-ui/src/develop/render.rs:331`](../../ui/dr-ui/src/develop/render.rs#L331), [`ui/dr-ui/src/develop/render.rs:348`](../../ui/dr-ui/src/develop/render.rs#L348), [`ui/dr-ui/src/develop/session.rs:118`](../../ui/dr-ui/src/develop/session.rs#L118), [`ui/dr-ui/src/develop/session.rs:138`](../../ui/dr-ui/src/develop/session.rs#L138), [`ui/dr-ui/src/develop/session.rs:144`](../../ui/dr-ui/src/develop/session.rs#L144), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1932`](../../ui/dr-ui/src/lib.rs#L1932), [`ui/dr-ui/src/lib.rs:2040`](../../ui/dr-ui/src/lib.rs#L2040), [`ui/dr-ui/src/lib.rs:2153`](../../ui/dr-ui/src/lib.rs#L2153), [`ui/dr-ui/src/lib.rs:2194`](../../ui/dr-ui/src/lib.rs#L2194), [`ui/dr-ui/src/lib.rs:3685`](../../ui/dr-ui/src/lib.rs#L3685), [`ui/dr-ui/src/lib.rs:442`](../../ui/dr-ui/src/lib.rs#L442), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:2028`](../../ui/dr-ui/ui/app.slint#L2028), [`ui/dr-ui/ui/app.slint:3035`](../../ui/dr-ui/ui/app.slint#L3035), [`ui/dr-ui/ui/app.slint:90`](../../ui/dr-ui/ui/app.slint#L90), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../../ui/dr-ui/ui/peaking.slint#L56), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) | +| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1203`](../../ui/dr-ui/src/develop/render.rs#L1203), [`ui/dr-ui/src/develop/render.rs:1267`](../../ui/dr-ui/src/develop/render.rs#L1267), [`ui/dr-ui/src/develop/render.rs:1290`](../../ui/dr-ui/src/develop/render.rs#L1290), [`ui/dr-ui/src/develop/render.rs:256`](../../ui/dr-ui/src/develop/render.rs#L256), [`ui/dr-ui/src/develop/render.rs:269`](../../ui/dr-ui/src/develop/render.rs#L269), [`ui/dr-ui/src/develop/render.rs:314`](../../ui/dr-ui/src/develop/render.rs#L314), [`ui/dr-ui/src/develop/render.rs:325`](../../ui/dr-ui/src/develop/render.rs#L325), [`ui/dr-ui/src/develop/render.rs:331`](../../ui/dr-ui/src/develop/render.rs#L331), [`ui/dr-ui/src/develop/render.rs:348`](../../ui/dr-ui/src/develop/render.rs#L348), [`ui/dr-ui/src/develop/session.rs:118`](../../ui/dr-ui/src/develop/session.rs#L118), [`ui/dr-ui/src/develop/session.rs:138`](../../ui/dr-ui/src/develop/session.rs#L138), [`ui/dr-ui/src/develop/session.rs:144`](../../ui/dr-ui/src/develop/session.rs#L144), [`ui/dr-ui/src/develop_ui.rs:1199`](../../ui/dr-ui/src/develop_ui.rs#L1199), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1272`](../../ui/dr-ui/src/lib.rs#L1272), [`ui/dr-ui/src/lib.rs:2205`](../../ui/dr-ui/src/lib.rs#L2205), [`ui/dr-ui/src/lib.rs:2318`](../../ui/dr-ui/src/lib.rs#L2318), [`ui/dr-ui/src/lib.rs:2359`](../../ui/dr-ui/src/lib.rs#L2359), [`ui/dr-ui/src/lib.rs:443`](../../ui/dr-ui/src/lib.rs#L443), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:2028`](../../ui/dr-ui/ui/app.slint#L2028), [`ui/dr-ui/ui/app.slint:3035`](../../ui/dr-ui/ui/app.slint#L3035), [`ui/dr-ui/ui/app.slint:90`](../../ui/dr-ui/ui/app.slint#L90), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../../ui/dr-ui/ui/peaking.slint#L56), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) | | FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:171`](../../core/dr-pipeline/src/sidecar.rs#L171), [`core/dr-xmp/src/lib.rs:442`](../../core/dr-xmp/src/lib.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11) | | FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:646`](../../core/dr-catalog/src/schema.rs#L646), [`ui/dr-ui/src/bursts.rs:1`](../../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library/cells.rs:19`](../../ui/dr-ui/src/library/cells.rs#L19), [`ui/dr-ui/src/library/cells.rs:769`](../../ui/dr-ui/src/library/cells.rs#L769) | -| 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:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`core/dr-catalog/src/schema.rs:714`](../../core/dr-catalog/src/schema.rs#L714), [`core/dr-catalog/src/schema.rs:970`](../../core/dr-catalog/src/schema.rs#L970), [`core/dr-face/src/align.rs:296`](../../core/dr-face/src/align.rs#L296), [`core/dr-types/src/settings.rs:176`](../../core/dr-types/src/settings.rs#L176), [`ui/dr-ui/examples/face_detectors.rs:1`](../../ui/dr-ui/examples/face_detectors.rs#L1), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/derived_sync.rs:124`](../../ui/dr-ui/src/derived_sync.rs#L124), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:446`](../../ui/dr-ui/src/faces.rs#L446), [`ui/dr-ui/src/faces.rs:462`](../../ui/dr-ui/src/faces.rs#L462), [`ui/dr-ui/src/faces.rs:584`](../../ui/dr-ui/src/faces.rs#L584), [`ui/dr-ui/src/identity_ui.rs:1325`](../../ui/dr-ui/src/identity_ui.rs#L1325), [`ui/dr-ui/src/identity_ui.rs:603`](../../ui/dr-ui/src/identity_ui.rs#L603), [`ui/dr-ui/src/lib.rs:1511`](../../ui/dr-ui/src/lib.rs#L1511), [`ui/dr-ui/src/lib.rs:3940`](../../ui/dr-ui/src/lib.rs#L3940), [`ui/dr-ui/src/library/sweep.rs:241`](../../ui/dr-ui/src/library/sweep.rs#L241), [`ui/dr-ui/src/library/sweep.rs:506`](../../ui/dr-ui/src/library/sweep.rs#L506), [`ui/dr-ui/src/library_ui.rs:406`](../../ui/dr-ui/src/library_ui.rs#L406), [`ui/dr-ui/src/library_ui.rs:493`](../../ui/dr-ui/src/library_ui.rs#L493), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/settings_ui.rs:210`](../../ui/dr-ui/src/settings_ui.rs#L210), [`ui/dr-ui/src/settings_ui.rs:571`](../../ui/dr-ui/src/settings_ui.rs#L571), [`ui/dr-ui/ui/app.slint:946`](../../ui/dr-ui/ui/app.slint#L946), [`ui/dr-ui/ui/settings.slint:100`](../../ui/dr-ui/ui/settings.slint#L100), [`ui/dr-ui/ui/settings.slint:467`](../../ui/dr-ui/ui/settings.slint#L467), [`ui/dr-ui/ui/settings.slint:529`](../../ui/dr-ui/ui/settings.slint#L529), [`ui/dr-ui/ui/settings.slint:82`](../../ui/dr-ui/ui/settings.slint#L82), [`ui/dr-ui/ui/settings.slint:94`](../../ui/dr-ui/ui/settings.slint#L94) | +| 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:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`core/dr-catalog/src/schema.rs:714`](../../core/dr-catalog/src/schema.rs#L714), [`core/dr-catalog/src/schema.rs:970`](../../core/dr-catalog/src/schema.rs#L970), [`core/dr-face/src/align.rs:296`](../../core/dr-face/src/align.rs#L296), [`core/dr-types/src/settings.rs:176`](../../core/dr-types/src/settings.rs#L176), [`ui/dr-ui/examples/face_detectors.rs:1`](../../ui/dr-ui/examples/face_detectors.rs#L1), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/derived_sync.rs:124`](../../ui/dr-ui/src/derived_sync.rs#L124), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:446`](../../ui/dr-ui/src/faces.rs#L446), [`ui/dr-ui/src/faces.rs:462`](../../ui/dr-ui/src/faces.rs#L462), [`ui/dr-ui/src/faces.rs:584`](../../ui/dr-ui/src/faces.rs#L584), [`ui/dr-ui/src/identity_ui.rs:1325`](../../ui/dr-ui/src/identity_ui.rs#L1325), [`ui/dr-ui/src/identity_ui.rs:603`](../../ui/dr-ui/src/identity_ui.rs#L603), [`ui/dr-ui/src/lib.rs:1733`](../../ui/dr-ui/src/lib.rs#L1733), [`ui/dr-ui/src/lib.rs:3092`](../../ui/dr-ui/src/lib.rs#L3092), [`ui/dr-ui/src/library/sweep.rs:241`](../../ui/dr-ui/src/library/sweep.rs#L241), [`ui/dr-ui/src/library/sweep.rs:506`](../../ui/dr-ui/src/library/sweep.rs#L506), [`ui/dr-ui/src/library_ui.rs:406`](../../ui/dr-ui/src/library_ui.rs#L406), [`ui/dr-ui/src/library_ui.rs:493`](../../ui/dr-ui/src/library_ui.rs#L493), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/settings_ui.rs:210`](../../ui/dr-ui/src/settings_ui.rs#L210), [`ui/dr-ui/src/settings_ui.rs:571`](../../ui/dr-ui/src/settings_ui.rs#L571), [`ui/dr-ui/ui/app.slint:946`](../../ui/dr-ui/ui/app.slint#L946), [`ui/dr-ui/ui/settings.slint:100`](../../ui/dr-ui/ui/settings.slint#L100), [`ui/dr-ui/ui/settings.slint:467`](../../ui/dr-ui/ui/settings.slint#L467), [`ui/dr-ui/ui/settings.slint:529`](../../ui/dr-ui/ui/settings.slint#L529), [`ui/dr-ui/ui/settings.slint:82`](../../ui/dr-ui/ui/settings.slint#L82), [`ui/dr-ui/ui/settings.slint:94`](../../ui/dr-ui/ui/settings.slint#L94) | | FR-CULL-8a | [`core/dr-catalog/src/faces.rs:124`](../../core/dr-catalog/src/faces.rs#L124), [`core/dr-catalog/src/schema.rs:917`](../../core/dr-catalog/src/schema.rs#L917), [`core/dr-face/src/classify.rs:1`](../../core/dr-face/src/classify.rs#L1), [`core/dr-face/src/eyes.rs:1`](../../core/dr-face/src/eyes.rs#L1), [`core/dr-face/src/landmarks.rs:1`](../../core/dr-face/src/landmarks.rs#L1), [`core/dr-types/src/place.rs:129`](../../core/dr-types/src/place.rs#L129), [`ui/dr-ui/src/faces.rs:166`](../../ui/dr-ui/src/faces.rs#L166), [`ui/dr-ui/src/faces.rs:1821`](../../ui/dr-ui/src/faces.rs#L1821), [`ui/dr-ui/src/faces.rs:308`](../../ui/dr-ui/src/faces.rs#L308), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:131`](../../ui/dr-ui/src/library/filters.rs#L131), [`ui/dr-ui/src/library/filters.rs:683`](../../ui/dr-ui/src/library/filters.rs#L683), [`ui/dr-ui/src/library/filters.rs:726`](../../ui/dr-ui/src/library/filters.rs#L726), [`ui/dr-ui/src/library/filters.rs:764`](../../ui/dr-ui/src/library/filters.rs#L764), [`ui/dr-ui/src/repairs.rs:571`](../../ui/dr-ui/src/repairs.rs#L571), [`ui/dr-ui/ui/app.slint:707`](../../ui/dr-ui/ui/app.slint#L707), [`ui/dr-ui/ui/identity.slint:273`](../../ui/dr-ui/ui/identity.slint#L273), [`ui/dr-ui/ui/identity.slint:63`](../../ui/dr-ui/ui/identity.slint#L63), [`ui/dr-ui/ui/library.slint:1512`](../../ui/dr-ui/ui/library.slint#L1512) | | FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1011`](../../core/dr-catalog/src/schema.rs#L1011), [`core/dr-catalog/src/schema.rs:781`](../../core/dr-catalog/src/schema.rs#L781), [`core/dr-face/src/assign.rs:1`](../../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:129`](../../core/dr-types/src/settings.rs#L129), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:584`](../../ui/dr-ui/src/faces.rs#L584), [`ui/dr-ui/src/identity_ui.rs:1`](../../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/repairs.rs:517`](../../ui/dr-ui/src/repairs.rs#L517), [`ui/dr-ui/ui/identity.slint:347`](../../ui/dr-ui/ui/identity.slint#L347) | | FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../../core/dr-pipeline/src/sidecar.rs#L1) | | FR-DEV-10 | [`core/dr-gpu/src/mask.rs:1027`](../../core/dr-gpu/src/mask.rs#L1027), [`core/dr-gpu/src/mask.rs:1251`](../../core/dr-gpu/src/mask.rs#L1251), [`core/dr-gpu/src/mask.rs:1509`](../../core/dr-gpu/src/mask.rs#L1509), [`core/dr-gpu/src/mask.rs:421`](../../core/dr-gpu/src/mask.rs#L421), [`core/dr-gpu/src/mask.rs:463`](../../core/dr-gpu/src/mask.rs#L463), [`core/dr-gpu/src/mask.rs:58`](../../core/dr-gpu/src/mask.rs#L58), [`core/dr-gpu/src/mask.rs:60`](../../core/dr-gpu/src/mask.rs#L60), [`core/dr-gpu/src/mask.rs:834`](../../core/dr-gpu/src/mask.rs#L834), [`core/dr-gpu/src/mask.rs:85`](../../core/dr-gpu/src/mask.rs#L85), [`core/dr-gpu/src/mask.rs:913`](../../core/dr-gpu/src/mask.rs#L913), [`core/dr-gpu/src/shaders/mask.wgsl:117`](../../core/dr-gpu/src/shaders/mask.wgsl#L117), [`core/dr-gpu/src/shaders/mask.wgsl:59`](../../core/dr-gpu/src/shaders/mask.wgsl#L59), [`core/dr-gpu/tests/range_masks.rs:119`](../../core/dr-gpu/tests/range_masks.rs#L119), [`core/dr-gpu/tests/range_masks.rs:147`](../../core/dr-gpu/tests/range_masks.rs#L147), [`core/dr-gpu/tests/range_masks.rs:175`](../../core/dr-gpu/tests/range_masks.rs#L175), [`core/dr-gpu/tests/range_masks.rs:1`](../../core/dr-gpu/tests/range_masks.rs#L1), [`core/dr-gpu/tests/range_masks.rs:94`](../../core/dr-gpu/tests/range_masks.rs#L94), [`core/dr-pipeline/src/mask.rs:125`](../../core/dr-pipeline/src/mask.rs#L125), [`core/dr-pipeline/src/mask.rs:2845`](../../core/dr-pipeline/src/mask.rs#L2845), [`core/dr-pipeline/src/mask.rs:2858`](../../core/dr-pipeline/src/mask.rs#L2858), [`core/dr-pipeline/src/mask.rs:2877`](../../core/dr-pipeline/src/mask.rs#L2877), [`core/dr-pipeline/src/mask.rs:2891`](../../core/dr-pipeline/src/mask.rs#L2891), [`core/dr-pipeline/src/mask.rs:2901`](../../core/dr-pipeline/src/mask.rs#L2901), [`core/dr-pipeline/src/mask.rs:710`](../../core/dr-pipeline/src/mask.rs#L710), [`core/dr-pipeline/src/mask.rs:749`](../../core/dr-pipeline/src/mask.rs#L749), [`core/dr-pipeline/src/mask.rs:809`](../../core/dr-pipeline/src/mask.rs#L809), [`core/dr-pipeline/src/mask.rs:828`](../../core/dr-pipeline/src/mask.rs#L828), [`core/dr-pipeline/src/mask.rs:852`](../../core/dr-pipeline/src/mask.rs#L852), [`core/dr-pipeline/src/mask.rs:864`](../../core/dr-pipeline/src/mask.rs#L864), [`core/dr-pipeline/src/mask.rs:876`](../../core/dr-pipeline/src/mask.rs#L876), [`core/dr-pipeline/src/sidecar.rs:1336`](../../core/dr-pipeline/src/sidecar.rs#L1336), [`core/dr-pipeline/src/sidecar.rs:1515`](../../core/dr-pipeline/src/sidecar.rs#L1515), [`core/dr-pipeline/src/sidecar.rs:1604`](../../core/dr-pipeline/src/sidecar.rs#L1604), [`core/dr-pipeline/src/sidecar.rs:1708`](../../core/dr-pipeline/src/sidecar.rs#L1708), [`core/dr-pipeline/src/sidecar.rs:1916`](../../core/dr-pipeline/src/sidecar.rs#L1916), [`core/dr-pipeline/tests/mask_sidecar.rs:1157`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1157), [`core/dr-pipeline/tests/mask_sidecar.rs:1186`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1186), [`core/dr-pipeline/tests/mask_sidecar.rs:1204`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1204), [`core/dr-pipeline/tests/mask_sidecar.rs:1234`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1234), [`ui/dr-ui/src/develop/mask_ops.rs:341`](../../ui/dr-ui/src/develop/mask_ops.rs#L341), [`ui/dr-ui/src/develop/mask_ops.rs:638`](../../ui/dr-ui/src/develop/mask_ops.rs#L638), [`ui/dr-ui/src/develop/mask_ops.rs:665`](../../ui/dr-ui/src/develop/mask_ops.rs#L665), [`ui/dr-ui/src/develop/mask_ops.rs:697`](../../ui/dr-ui/src/develop/mask_ops.rs#L697), [`ui/dr-ui/src/develop/mask_ops.rs:721`](../../ui/dr-ui/src/develop/mask_ops.rs#L721), [`ui/dr-ui/src/develop/mask_ops.rs:727`](../../ui/dr-ui/src/develop/mask_ops.rs#L727), [`ui/dr-ui/src/develop/mask_ops.rs:734`](../../ui/dr-ui/src/develop/mask_ops.rs#L734), [`ui/dr-ui/src/develop/mask_ops.rs:754`](../../ui/dr-ui/src/develop/mask_ops.rs#L754), [`ui/dr-ui/src/labels.rs:63`](../../ui/dr-ui/src/labels.rs#L63), [`ui/dr-ui/src/masks_ui.rs:1216`](../../ui/dr-ui/src/masks_ui.rs#L1216), [`ui/dr-ui/src/masks_ui.rs:797`](../../ui/dr-ui/src/masks_ui.rs#L797) | | FR-DEV-12 | [`core/dr-pipeline/ops/colour_grading.yaml:1`](../../core/dr-pipeline/ops/colour_grading.yaml#L1) | -| FR-DEV-16 | [`ui/dr-ui/src/lib.rs:424`](../../ui/dr-ui/src/lib.rs#L424), [`ui/dr-ui/src/masks_ui.rs:713`](../../ui/dr-ui/src/masks_ui.rs#L713), [`ui/dr-ui/ui/adjust.slint:998`](../../ui/dr-ui/ui/adjust.slint#L998), [`ui/dr-ui/ui/app.slint:2620`](../../ui/dr-ui/ui/app.slint#L2620), [`ui/dr-ui/ui/app.slint:2633`](../../ui/dr-ui/ui/app.slint#L2633), [`ui/dr-ui/ui/masks.slint:832`](../../ui/dr-ui/ui/masks.slint#L832) | +| FR-DEV-16 | [`ui/dr-ui/src/lib.rs:425`](../../ui/dr-ui/src/lib.rs#L425), [`ui/dr-ui/src/masks_ui.rs:713`](../../ui/dr-ui/src/masks_ui.rs#L713), [`ui/dr-ui/ui/adjust.slint:998`](../../ui/dr-ui/ui/adjust.slint#L998), [`ui/dr-ui/ui/app.slint:2620`](../../ui/dr-ui/ui/app.slint#L2620), [`ui/dr-ui/ui/app.slint:2633`](../../ui/dr-ui/ui/app.slint#L2633), [`ui/dr-ui/ui/masks.slint:832`](../../ui/dr-ui/ui/masks.slint#L832) | | FR-DEV-18 | [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:221`](../../core/dr-pipeline/src/ops/dehaze.rs#L221) | | FR-DEV-19 | [`core/dr-pipeline/src/mask.rs:1307`](../../core/dr-pipeline/src/mask.rs#L1307) | | FR-DEV-19a | [`core/dr-gpu/src/mask.rs:761`](../../core/dr-gpu/src/mask.rs#L761), [`core/dr-pipeline/src/mask.rs:1588`](../../core/dr-pipeline/src/mask.rs#L1588), [`core/dr-pipeline/src/mask.rs:2919`](../../core/dr-pipeline/src/mask.rs#L2919), [`core/dr-pipeline/src/mask.rs:988`](../../core/dr-pipeline/src/mask.rs#L988), [`core/dr-pipeline/src/sidecar.rs:1226`](../../core/dr-pipeline/src/sidecar.rs#L1226), [`core/dr-pipeline/tests/mask_sidecar.rs:1347`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1347), [`core/dr-pipeline/tests/mask_sidecar.rs:1373`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1373), [`ui/dr-ui/src/develop/masks.rs:431`](../../ui/dr-ui/src/develop/masks.rs#L431), [`ui/dr-ui/src/develop/masks.rs:492`](../../ui/dr-ui/src/develop/masks.rs#L492), [`ui/dr-ui/src/develop/masks.rs:511`](../../ui/dr-ui/src/develop/masks.rs#L511), [`ui/dr-ui/src/develop/session.rs:188`](../../ui/dr-ui/src/develop/session.rs#L188), [`ui/dr-ui/src/labels.rs:73`](../../ui/dr-ui/src/labels.rs#L73), [`ui/dr-ui/src/masks_ui.rs:1083`](../../ui/dr-ui/src/masks_ui.rs#L1083), [`ui/dr-ui/src/masks_ui.rs:1155`](../../ui/dr-ui/src/masks_ui.rs#L1155), [`ui/dr-ui/src/masks_ui.rs:1514`](../../ui/dr-ui/src/masks_ui.rs#L1514), [`ui/dr-ui/src/masks_ui.rs:270`](../../ui/dr-ui/src/masks_ui.rs#L270), [`ui/dr-ui/ui/masks.slint:150`](../../ui/dr-ui/ui/masks.slint#L150), [`ui/dr-ui/ui/masks.slint:175`](../../ui/dr-ui/ui/masks.slint#L175), [`ui/dr-ui/ui/masks.slint:337`](../../ui/dr-ui/ui/masks.slint#L337), [`ui/dr-ui/ui/masks.slint:406`](../../ui/dr-ui/ui/masks.slint#L406), [`ui/dr-ui/ui/masks.slint:770`](../../ui/dr-ui/ui/masks.slint#L770), [`ui/dr-ui/ui/masks.slint:855`](../../ui/dr-ui/ui/masks.slint#L855) | | FR-DEV-19b | [`ui/dr-ui/src/develop/mask_ops.rs:606`](../../ui/dr-ui/src/develop/mask_ops.rs#L606), [`ui/dr-ui/src/develop/masks.rs:592`](../../ui/dr-ui/src/develop/masks.rs#L592), [`ui/dr-ui/src/develop/session.rs:197`](../../ui/dr-ui/src/develop/session.rs#L197), [`ui/dr-ui/src/develop/session.rs:204`](../../ui/dr-ui/src/develop/session.rs#L204), [`ui/dr-ui/src/labels.rs:67`](../../ui/dr-ui/src/labels.rs#L67), [`ui/dr-ui/src/masks_ui.rs:1188`](../../ui/dr-ui/src/masks_ui.rs#L1188), [`ui/dr-ui/src/masks_ui.rs:1407`](../../ui/dr-ui/src/masks_ui.rs#L1407), [`ui/dr-ui/src/masks_ui.rs:1435`](../../ui/dr-ui/src/masks_ui.rs#L1435), [`ui/dr-ui/src/masks_ui.rs:1778`](../../ui/dr-ui/src/masks_ui.rs#L1778), [`ui/dr-ui/src/masks_ui.rs:908`](../../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/ui/app.slint:1056`](../../ui/dr-ui/ui/app.slint#L1056), [`ui/dr-ui/ui/app.slint:2260`](../../ui/dr-ui/ui/app.slint#L2260), [`ui/dr-ui/ui/masks.slint:1126`](../../ui/dr-ui/ui/masks.slint#L1126), [`ui/dr-ui/ui/masks.slint:453`](../../ui/dr-ui/ui/masks.slint#L453), [`ui/dr-ui/ui/masks.slint:774`](../../ui/dr-ui/ui/masks.slint#L774), [`ui/dr-ui/ui/masks.slint:848`](../../ui/dr-ui/ui/masks.slint#L848), [`ui/dr-ui/ui/masks.slint:864`](../../ui/dr-ui/ui/masks.slint#L864) | | FR-DEV-19c | [`core/dr-gpu/src/mask.rs:715`](../../core/dr-gpu/src/mask.rs#L715), [`core/dr-gpu/tests/local_adjustments.rs:830`](../../core/dr-gpu/tests/local_adjustments.rs#L830), [`core/dr-gpu/tests/local_adjustments.rs:887`](../../core/dr-gpu/tests/local_adjustments.rs#L887), [`core/dr-gpu/tests/local_adjustments.rs:922`](../../core/dr-gpu/tests/local_adjustments.rs#L922), [`core/dr-gpu/tests/local_adjustments.rs:967`](../../core/dr-gpu/tests/local_adjustments.rs#L967), [`core/dr-pipeline/src/graph.rs:893`](../../core/dr-pipeline/src/graph.rs#L893), [`core/dr-pipeline/src/mask.rs:1796`](../../core/dr-pipeline/src/mask.rs#L1796), [`core/dr-pipeline/src/mask.rs:1822`](../../core/dr-pipeline/src/mask.rs#L1822), [`core/dr-pipeline/src/mask.rs:1837`](../../core/dr-pipeline/src/mask.rs#L1837), [`core/dr-pipeline/src/mask.rs:1957`](../../core/dr-pipeline/src/mask.rs#L1957), [`core/dr-pipeline/src/mask.rs:2005`](../../core/dr-pipeline/src/mask.rs#L2005), [`core/dr-pipeline/src/mask.rs:2149`](../../core/dr-pipeline/src/mask.rs#L2149), [`core/dr-pipeline/src/mask.rs:2386`](../../core/dr-pipeline/src/mask.rs#L2386), [`core/dr-pipeline/src/operation.rs:1748`](../../core/dr-pipeline/src/operation.rs#L1748), [`core/dr-pipeline/src/operation.rs:588`](../../core/dr-pipeline/src/operation.rs#L588), [`core/dr-pipeline/src/operation.rs:863`](../../core/dr-pipeline/src/operation.rs#L863), [`ui/dr-ui/src/develop/mask_ops.rs:1199`](../../ui/dr-ui/src/develop/mask_ops.rs#L1199), [`ui/dr-ui/src/develop/mask_ops.rs:1235`](../../ui/dr-ui/src/develop/mask_ops.rs#L1235), [`ui/dr-ui/src/develop/mask_ops.rs:318`](../../ui/dr-ui/src/develop/mask_ops.rs#L318), [`ui/dr-ui/src/develop/mask_ops.rs:58`](../../ui/dr-ui/src/develop/mask_ops.rs#L58), [`ui/dr-ui/src/develop/masks.rs:12`](../../ui/dr-ui/src/develop/masks.rs#L12), [`ui/dr-ui/src/develop/masks.rs:176`](../../ui/dr-ui/src/develop/masks.rs#L176), [`ui/dr-ui/src/develop/masks.rs:21`](../../ui/dr-ui/src/develop/masks.rs#L21), [`ui/dr-ui/src/develop/masks.rs:43`](../../ui/dr-ui/src/develop/masks.rs#L43), [`ui/dr-ui/src/develop/render.rs:178`](../../ui/dr-ui/src/develop/render.rs#L178), [`ui/dr-ui/src/develop/session.rs:223`](../../ui/dr-ui/src/develop/session.rs#L223), [`ui/dr-ui/src/develop/session.rs:232`](../../ui/dr-ui/src/develop/session.rs#L232), [`ui/dr-ui/src/masks_ui.rs:1024`](../../ui/dr-ui/src/masks_ui.rs#L1024), [`ui/dr-ui/src/masks_ui.rs:1407`](../../ui/dr-ui/src/masks_ui.rs#L1407), [`ui/dr-ui/src/masks_ui.rs:152`](../../ui/dr-ui/src/masks_ui.rs#L152), [`ui/dr-ui/src/masks_ui.rs:1778`](../../ui/dr-ui/src/masks_ui.rs#L1778), [`ui/dr-ui/src/masks_ui.rs:291`](../../ui/dr-ui/src/masks_ui.rs#L291), [`ui/dr-ui/src/masks_ui.rs:317`](../../ui/dr-ui/src/masks_ui.rs#L317), [`ui/dr-ui/src/masks_ui.rs:677`](../../ui/dr-ui/src/masks_ui.rs#L677), [`ui/dr-ui/src/masks_ui.rs:996`](../../ui/dr-ui/src/masks_ui.rs#L996), [`ui/dr-ui/ui/icons.slint:339`](../../ui/dr-ui/ui/icons.slint#L339), [`ui/dr-ui/ui/masks.slint:1091`](../../ui/dr-ui/ui/masks.slint#L1091), [`ui/dr-ui/ui/masks.slint:183`](../../ui/dr-ui/ui/masks.slint#L183), [`ui/dr-ui/ui/masks.slint:292`](../../ui/dr-ui/ui/masks.slint#L292), [`ui/dr-ui/ui/masks.slint:517`](../../ui/dr-ui/ui/masks.slint#L517), [`ui/dr-ui/ui/masks.slint:70`](../../ui/dr-ui/ui/masks.slint#L70), [`ui/dr-ui/ui/masks.slint:782`](../../ui/dr-ui/ui/masks.slint#L782), [`ui/dr-ui/ui/masks.slint:850`](../../ui/dr-ui/ui/masks.slint#L850), [`ui/dr-ui/ui/masks.slint:938`](../../ui/dr-ui/ui/masks.slint#L938) | | FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:403`](../../core/dr-pipeline/src/operation.rs#L403) | -| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2443`](../../core/dr-gpu/src/adjust.rs#L2443), [`core/dr-gpu/src/adjust.rs:679`](../../core/dr-gpu/src/adjust.rs#L679), [`core/dr-gpu/src/adjust.rs:798`](../../core/dr-gpu/src/adjust.rs#L798), [`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/coverage.rs:122`](../../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:1348`](../../core/dr-pipeline/src/graph.rs#L1348), [`core/dr-pipeline/src/graph.rs:1379`](../../core/dr-pipeline/src/graph.rs#L1379), [`core/dr-pipeline/src/graph.rs:1402`](../../core/dr-pipeline/src/graph.rs#L1402), [`core/dr-pipeline/src/graph.rs:1440`](../../core/dr-pipeline/src/graph.rs#L1440), [`core/dr-pipeline/src/graph.rs:1471`](../../core/dr-pipeline/src/graph.rs#L1471), [`core/dr-pipeline/src/graph.rs:225`](../../core/dr-pipeline/src/graph.rs#L225), [`core/dr-pipeline/src/graph.rs:306`](../../core/dr-pipeline/src/graph.rs#L306), [`core/dr-pipeline/src/graph.rs:344`](../../core/dr-pipeline/src/graph.rs#L344), [`core/dr-pipeline/src/graph.rs:350`](../../core/dr-pipeline/src/graph.rs#L350), [`core/dr-pipeline/src/graph.rs:860`](../../core/dr-pipeline/src/graph.rs#L860), [`core/dr-pipeline/src/graph.rs:952`](../../core/dr-pipeline/src/graph.rs#L952), [`core/dr-pipeline/src/lens.rs:93`](../../core/dr-pipeline/src/lens.rs#L93), [`core/dr-pipeline/src/mask.rs:205`](../../core/dr-pipeline/src/mask.rs#L205), [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:306`](../../core/dr-pipeline/src/neutral.rs#L306), [`core/dr-pipeline/src/neutral.rs:322`](../../core/dr-pipeline/src/neutral.rs#L322), [`core/dr-pipeline/src/neutral.rs:338`](../../core/dr-pipeline/src/neutral.rs#L338), [`core/dr-pipeline/src/neutral.rs:360`](../../core/dr-pipeline/src/neutral.rs#L360), [`core/dr-pipeline/src/neutral.rs:81`](../../core/dr-pipeline/src/neutral.rs#L81), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:565`](../../core/dr-pipeline/src/operation.rs#L565), [`core/dr-pipeline/src/operation.rs:637`](../../core/dr-pipeline/src/operation.rs#L637), [`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/preset.rs:751`](../../core/dr-pipeline/src/preset.rs#L751), [`core/dr-pipeline/src/sidecar.rs:1400`](../../core/dr-pipeline/src/sidecar.rs#L1400), [`core/dr-pipeline/src/sidecar.rs:192`](../../core/dr-pipeline/src/sidecar.rs#L192), [`core/dr-pipeline/src/sidecar.rs:2232`](../../core/dr-pipeline/src/sidecar.rs#L2232), [`core/dr-pipeline/src/sidecar.rs:2312`](../../core/dr-pipeline/src/sidecar.rs#L2312), [`core/dr-pipeline/tests/tone_curve.rs:1`](../../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:937`](../../core/dr-segment/src/refine.rs#L937), [`ui/dr-ui/src/develop/curves.rs:163`](../../ui/dr-ui/src/develop/curves.rs#L163), [`ui/dr-ui/src/develop/framing.rs:10`](../../ui/dr-ui/src/develop/framing.rs#L10), [`ui/dr-ui/src/develop/framing.rs:182`](../../ui/dr-ui/src/develop/framing.rs#L182), [`ui/dr-ui/src/develop/framing.rs:212`](../../ui/dr-ui/src/develop/framing.rs#L212), [`ui/dr-ui/src/develop/framing.rs:278`](../../ui/dr-ui/src/develop/framing.rs#L278), [`ui/dr-ui/src/develop/framing.rs:292`](../../ui/dr-ui/src/develop/framing.rs#L292), [`ui/dr-ui/src/develop/framing.rs:494`](../../ui/dr-ui/src/develop/framing.rs#L494), [`ui/dr-ui/src/develop/framing.rs:71`](../../ui/dr-ui/src/develop/framing.rs#L71), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1340`](../../ui/dr-ui/src/develop/mask_ops.rs#L1340), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/mask_ops.rs:969`](../../ui/dr-ui/src/develop/mask_ops.rs#L969), [`ui/dr-ui/src/develop/masks.rs:262`](../../ui/dr-ui/src/develop/masks.rs#L262), [`ui/dr-ui/src/develop/masks.rs:360`](../../ui/dr-ui/src/develop/masks.rs#L360), [`ui/dr-ui/src/develop/masks.rs:740`](../../ui/dr-ui/src/develop/masks.rs#L740), [`ui/dr-ui/src/develop/masks.rs:770`](../../ui/dr-ui/src/develop/masks.rs#L770), [`ui/dr-ui/src/develop/render.rs:1039`](../../ui/dr-ui/src/develop/render.rs#L1039), [`ui/dr-ui/src/develop/rows.rs:106`](../../ui/dr-ui/src/develop/rows.rs#L106), [`ui/dr-ui/src/develop/rows.rs:195`](../../ui/dr-ui/src/develop/rows.rs#L195), [`ui/dr-ui/src/develop/rows.rs:231`](../../ui/dr-ui/src/develop/rows.rs#L231), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/segmentation.rs:139`](../../ui/dr-ui/src/develop/segmentation.rs#L139), [`ui/dr-ui/src/develop/segmentation.rs:302`](../../ui/dr-ui/src/develop/segmentation.rs#L302), [`ui/dr-ui/src/develop/segmentation.rs:339`](../../ui/dr-ui/src/develop/segmentation.rs#L339), [`ui/dr-ui/src/develop/segmentation.rs:572`](../../ui/dr-ui/src/develop/segmentation.rs#L572), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:604`](../../ui/dr-ui/src/develop/segmentation.rs#L604), [`ui/dr-ui/src/develop/segmentation.rs:626`](../../ui/dr-ui/src/develop/segmentation.rs#L626), [`ui/dr-ui/src/develop/segmentation.rs:77`](../../ui/dr-ui/src/develop/segmentation.rs#L77), [`ui/dr-ui/src/develop/segmentation.rs:806`](../../ui/dr-ui/src/develop/segmentation.rs#L806), [`ui/dr-ui/src/develop/session.rs:158`](../../ui/dr-ui/src/develop/session.rs#L158), [`ui/dr-ui/src/develop/session.rs:251`](../../ui/dr-ui/src/develop/session.rs#L251), [`ui/dr-ui/src/develop/session.rs:280`](../../ui/dr-ui/src/develop/session.rs#L280), [`ui/dr-ui/src/develop/session.rs:429`](../../ui/dr-ui/src/develop/session.rs#L429), [`ui/dr-ui/src/develop/session.rs:489`](../../ui/dr-ui/src/develop/session.rs#L489), [`ui/dr-ui/src/develop/session.rs:561`](../../ui/dr-ui/src/develop/session.rs#L561), [`ui/dr-ui/src/develop/session.rs:711`](../../ui/dr-ui/src/develop/session.rs#L711), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop/white_balance.rs:193`](../../ui/dr-ui/src/develop/white_balance.rs#L193), [`ui/dr-ui/src/develop/white_balance.rs:276`](../../ui/dr-ui/src/develop/white_balance.rs#L276), [`ui/dr-ui/src/develop/white_balance.rs:338`](../../ui/dr-ui/src/develop/white_balance.rs#L338), [`ui/dr-ui/src/labels.rs:35`](../../ui/dr-ui/src/labels.rs#L35), [`ui/dr-ui/src/lib.rs:2021`](../../ui/dr-ui/src/lib.rs#L2021), [`ui/dr-ui/src/lib.rs:2901`](../../ui/dr-ui/src/lib.rs#L2901), [`ui/dr-ui/src/lib.rs:3108`](../../ui/dr-ui/src/lib.rs#L3108), [`ui/dr-ui/src/lib.rs:3239`](../../ui/dr-ui/src/lib.rs#L3239), [`ui/dr-ui/src/lib.rs:3444`](../../ui/dr-ui/src/lib.rs#L3444), [`ui/dr-ui/src/lib.rs:3508`](../../ui/dr-ui/src/lib.rs#L3508), [`ui/dr-ui/src/lib.rs:3562`](../../ui/dr-ui/src/lib.rs#L3562), [`ui/dr-ui/src/lib.rs:419`](../../ui/dr-ui/src/lib.rs#L419), [`ui/dr-ui/src/lib.rs:448`](../../ui/dr-ui/src/lib.rs#L448), [`ui/dr-ui/src/lib.rs:554`](../../ui/dr-ui/src/lib.rs#L554), [`ui/dr-ui/src/lib.rs:578`](../../ui/dr-ui/src/lib.rs#L578), [`ui/dr-ui/src/library/sidecar.rs:63`](../../ui/dr-ui/src/library/sidecar.rs#L63), [`ui/dr-ui/src/masks_ui.rs:1537`](../../ui/dr-ui/src/masks_ui.rs#L1537), [`ui/dr-ui/src/masks_ui.rs:1651`](../../ui/dr-ui/src/masks_ui.rs#L1651), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/masks_ui.rs:44`](../../ui/dr-ui/src/masks_ui.rs#L44), [`ui/dr-ui/src/presets.rs:414`](../../ui/dr-ui/src/presets.rs#L414), [`ui/dr-ui/src/segmentation.rs:117`](../../ui/dr-ui/src/segmentation.rs#L117), [`ui/dr-ui/src/segmentation.rs:353`](../../ui/dr-ui/src/segmentation.rs#L353), [`ui/dr-ui/src/segmentation.rs:369`](../../ui/dr-ui/src/segmentation.rs#L369), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499), [`ui/dr-ui/src/segmentation.rs:527`](../../ui/dr-ui/src/segmentation.rs#L527), [`ui/dr-ui/ui/adjust.slint:1019`](../../ui/dr-ui/ui/adjust.slint#L1019), [`ui/dr-ui/ui/adjust.slint:126`](../../ui/dr-ui/ui/adjust.slint#L126), [`ui/dr-ui/ui/adjust.slint:1496`](../../ui/dr-ui/ui/adjust.slint#L1496), [`ui/dr-ui/ui/adjust.slint:171`](../../ui/dr-ui/ui/adjust.slint#L171), [`ui/dr-ui/ui/adjust.slint:46`](../../ui/dr-ui/ui/adjust.slint#L46), [`ui/dr-ui/ui/adjust.slint:624`](../../ui/dr-ui/ui/adjust.slint#L624), [`ui/dr-ui/ui/adjust.slint:650`](../../ui/dr-ui/ui/adjust.slint#L650), [`ui/dr-ui/ui/adjust.slint:669`](../../ui/dr-ui/ui/adjust.slint#L669), [`ui/dr-ui/ui/adjust.slint:790`](../../ui/dr-ui/ui/adjust.slint#L790), [`ui/dr-ui/ui/adjust.slint:947`](../../ui/dr-ui/ui/adjust.slint#L947), [`ui/dr-ui/ui/app.slint:149`](../../ui/dr-ui/ui/app.slint#L149), [`ui/dr-ui/ui/app.slint:2325`](../../ui/dr-ui/ui/app.slint#L2325), [`ui/dr-ui/ui/app.slint:2385`](../../ui/dr-ui/ui/app.slint#L2385), [`ui/dr-ui/ui/develop.slint:223`](../../ui/dr-ui/ui/develop.slint#L223), [`ui/dr-ui/ui/masks.slint:1216`](../../ui/dr-ui/ui/masks.slint#L1216), [`ui/dr-ui/ui/masks.slint:753`](../../ui/dr-ui/ui/masks.slint#L753), [`ui/dr-ui/ui/masks.slint:812`](../../ui/dr-ui/ui/masks.slint#L812) | -| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:212`](../../core/dr-pipeline/src/descriptor.rs#L212), [`core/dr-pipeline/src/descriptor.rs:252`](../../core/dr-pipeline/src/descriptor.rs#L252), [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/descriptor.rs:331`](../../core/dr-pipeline/src/descriptor.rs#L331), [`core/dr-pipeline/src/framing.rs:395`](../../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:25`](../../core/dr-pipeline/src/graph.rs#L25), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/graph.rs:534`](../../core/dr-pipeline/src/graph.rs#L534), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/mask.rs:1699`](../../core/dr-pipeline/src/mask.rs#L1699), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:378`](../../core/dr-pipeline/src/neutral.rs#L378), [`core/dr-pipeline/src/operation.rs:232`](../../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop/curves.rs:36`](../../ui/dr-ui/src/develop/curves.rs#L36), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/rows.rs:877`](../../ui/dr-ui/src/develop/rows.rs#L877), [`ui/dr-ui/src/lib.rs:879`](../../ui/dr-ui/src/lib.rs#L879), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../../ui/dr-ui/tests/ui_names_no_operation.rs#L1), [`ui/dr-ui/ui/app.slint:2569`](../../ui/dr-ui/ui/app.slint#L2569) | +| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2443`](../../core/dr-gpu/src/adjust.rs#L2443), [`core/dr-gpu/src/adjust.rs:679`](../../core/dr-gpu/src/adjust.rs#L679), [`core/dr-gpu/src/adjust.rs:798`](../../core/dr-gpu/src/adjust.rs#L798), [`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/coverage.rs:122`](../../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:1348`](../../core/dr-pipeline/src/graph.rs#L1348), [`core/dr-pipeline/src/graph.rs:1379`](../../core/dr-pipeline/src/graph.rs#L1379), [`core/dr-pipeline/src/graph.rs:1402`](../../core/dr-pipeline/src/graph.rs#L1402), [`core/dr-pipeline/src/graph.rs:1440`](../../core/dr-pipeline/src/graph.rs#L1440), [`core/dr-pipeline/src/graph.rs:1471`](../../core/dr-pipeline/src/graph.rs#L1471), [`core/dr-pipeline/src/graph.rs:225`](../../core/dr-pipeline/src/graph.rs#L225), [`core/dr-pipeline/src/graph.rs:306`](../../core/dr-pipeline/src/graph.rs#L306), [`core/dr-pipeline/src/graph.rs:344`](../../core/dr-pipeline/src/graph.rs#L344), [`core/dr-pipeline/src/graph.rs:350`](../../core/dr-pipeline/src/graph.rs#L350), [`core/dr-pipeline/src/graph.rs:860`](../../core/dr-pipeline/src/graph.rs#L860), [`core/dr-pipeline/src/graph.rs:952`](../../core/dr-pipeline/src/graph.rs#L952), [`core/dr-pipeline/src/lens.rs:93`](../../core/dr-pipeline/src/lens.rs#L93), [`core/dr-pipeline/src/mask.rs:205`](../../core/dr-pipeline/src/mask.rs#L205), [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:306`](../../core/dr-pipeline/src/neutral.rs#L306), [`core/dr-pipeline/src/neutral.rs:322`](../../core/dr-pipeline/src/neutral.rs#L322), [`core/dr-pipeline/src/neutral.rs:338`](../../core/dr-pipeline/src/neutral.rs#L338), [`core/dr-pipeline/src/neutral.rs:360`](../../core/dr-pipeline/src/neutral.rs#L360), [`core/dr-pipeline/src/neutral.rs:81`](../../core/dr-pipeline/src/neutral.rs#L81), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:565`](../../core/dr-pipeline/src/operation.rs#L565), [`core/dr-pipeline/src/operation.rs:637`](../../core/dr-pipeline/src/operation.rs#L637), [`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/preset.rs:751`](../../core/dr-pipeline/src/preset.rs#L751), [`core/dr-pipeline/src/sidecar.rs:1400`](../../core/dr-pipeline/src/sidecar.rs#L1400), [`core/dr-pipeline/src/sidecar.rs:192`](../../core/dr-pipeline/src/sidecar.rs#L192), [`core/dr-pipeline/src/sidecar.rs:2232`](../../core/dr-pipeline/src/sidecar.rs#L2232), [`core/dr-pipeline/src/sidecar.rs:2312`](../../core/dr-pipeline/src/sidecar.rs#L2312), [`core/dr-pipeline/tests/tone_curve.rs:1`](../../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:937`](../../core/dr-segment/src/refine.rs#L937), [`ui/dr-ui/src/develop/curves.rs:163`](../../ui/dr-ui/src/develop/curves.rs#L163), [`ui/dr-ui/src/develop/framing.rs:10`](../../ui/dr-ui/src/develop/framing.rs#L10), [`ui/dr-ui/src/develop/framing.rs:182`](../../ui/dr-ui/src/develop/framing.rs#L182), [`ui/dr-ui/src/develop/framing.rs:212`](../../ui/dr-ui/src/develop/framing.rs#L212), [`ui/dr-ui/src/develop/framing.rs:278`](../../ui/dr-ui/src/develop/framing.rs#L278), [`ui/dr-ui/src/develop/framing.rs:292`](../../ui/dr-ui/src/develop/framing.rs#L292), [`ui/dr-ui/src/develop/framing.rs:494`](../../ui/dr-ui/src/develop/framing.rs#L494), [`ui/dr-ui/src/develop/framing.rs:71`](../../ui/dr-ui/src/develop/framing.rs#L71), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1340`](../../ui/dr-ui/src/develop/mask_ops.rs#L1340), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/mask_ops.rs:969`](../../ui/dr-ui/src/develop/mask_ops.rs#L969), [`ui/dr-ui/src/develop/masks.rs:262`](../../ui/dr-ui/src/develop/masks.rs#L262), [`ui/dr-ui/src/develop/masks.rs:360`](../../ui/dr-ui/src/develop/masks.rs#L360), [`ui/dr-ui/src/develop/masks.rs:740`](../../ui/dr-ui/src/develop/masks.rs#L740), [`ui/dr-ui/src/develop/masks.rs:770`](../../ui/dr-ui/src/develop/masks.rs#L770), [`ui/dr-ui/src/develop/render.rs:1039`](../../ui/dr-ui/src/develop/render.rs#L1039), [`ui/dr-ui/src/develop/rows.rs:106`](../../ui/dr-ui/src/develop/rows.rs#L106), [`ui/dr-ui/src/develop/rows.rs:195`](../../ui/dr-ui/src/develop/rows.rs#L195), [`ui/dr-ui/src/develop/rows.rs:231`](../../ui/dr-ui/src/develop/rows.rs#L231), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/segmentation.rs:139`](../../ui/dr-ui/src/develop/segmentation.rs#L139), [`ui/dr-ui/src/develop/segmentation.rs:302`](../../ui/dr-ui/src/develop/segmentation.rs#L302), [`ui/dr-ui/src/develop/segmentation.rs:339`](../../ui/dr-ui/src/develop/segmentation.rs#L339), [`ui/dr-ui/src/develop/segmentation.rs:572`](../../ui/dr-ui/src/develop/segmentation.rs#L572), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:604`](../../ui/dr-ui/src/develop/segmentation.rs#L604), [`ui/dr-ui/src/develop/segmentation.rs:626`](../../ui/dr-ui/src/develop/segmentation.rs#L626), [`ui/dr-ui/src/develop/segmentation.rs:77`](../../ui/dr-ui/src/develop/segmentation.rs#L77), [`ui/dr-ui/src/develop/segmentation.rs:806`](../../ui/dr-ui/src/develop/segmentation.rs#L806), [`ui/dr-ui/src/develop/session.rs:158`](../../ui/dr-ui/src/develop/session.rs#L158), [`ui/dr-ui/src/develop/session.rs:251`](../../ui/dr-ui/src/develop/session.rs#L251), [`ui/dr-ui/src/develop/session.rs:280`](../../ui/dr-ui/src/develop/session.rs#L280), [`ui/dr-ui/src/develop/session.rs:429`](../../ui/dr-ui/src/develop/session.rs#L429), [`ui/dr-ui/src/develop/session.rs:489`](../../ui/dr-ui/src/develop/session.rs#L489), [`ui/dr-ui/src/develop/session.rs:561`](../../ui/dr-ui/src/develop/session.rs#L561), [`ui/dr-ui/src/develop/session.rs:711`](../../ui/dr-ui/src/develop/session.rs#L711), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop/white_balance.rs:193`](../../ui/dr-ui/src/develop/white_balance.rs#L193), [`ui/dr-ui/src/develop/white_balance.rs:276`](../../ui/dr-ui/src/develop/white_balance.rs#L276), [`ui/dr-ui/src/develop/white_balance.rs:338`](../../ui/dr-ui/src/develop/white_balance.rs#L338), [`ui/dr-ui/src/develop_ui.rs:1040`](../../ui/dr-ui/src/develop_ui.rs#L1040), [`ui/dr-ui/src/develop_ui.rs:1094`](../../ui/dr-ui/src/develop_ui.rs#L1094), [`ui/dr-ui/src/develop_ui.rs:117`](../../ui/dr-ui/src/develop_ui.rs#L117), [`ui/dr-ui/src/develop_ui.rs:406`](../../ui/dr-ui/src/develop_ui.rs#L406), [`ui/dr-ui/src/develop_ui.rs:626`](../../ui/dr-ui/src/develop_ui.rs#L626), [`ui/dr-ui/src/develop_ui.rs:964`](../../ui/dr-ui/src/develop_ui.rs#L964), [`ui/dr-ui/src/labels.rs:35`](../../ui/dr-ui/src/labels.rs#L35), [`ui/dr-ui/src/lib.rs:2186`](../../ui/dr-ui/src/lib.rs#L2186), [`ui/dr-ui/src/lib.rs:420`](../../ui/dr-ui/src/lib.rs#L420), [`ui/dr-ui/src/lib.rs:449`](../../ui/dr-ui/src/lib.rs#L449), [`ui/dr-ui/src/lib.rs:555`](../../ui/dr-ui/src/lib.rs#L555), [`ui/dr-ui/src/lib.rs:579`](../../ui/dr-ui/src/lib.rs#L579), [`ui/dr-ui/src/library/sidecar.rs:63`](../../ui/dr-ui/src/library/sidecar.rs#L63), [`ui/dr-ui/src/masks_ui.rs:1537`](../../ui/dr-ui/src/masks_ui.rs#L1537), [`ui/dr-ui/src/masks_ui.rs:1651`](../../ui/dr-ui/src/masks_ui.rs#L1651), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/masks_ui.rs:44`](../../ui/dr-ui/src/masks_ui.rs#L44), [`ui/dr-ui/src/presets.rs:414`](../../ui/dr-ui/src/presets.rs#L414), [`ui/dr-ui/src/segmentation.rs:117`](../../ui/dr-ui/src/segmentation.rs#L117), [`ui/dr-ui/src/segmentation.rs:353`](../../ui/dr-ui/src/segmentation.rs#L353), [`ui/dr-ui/src/segmentation.rs:369`](../../ui/dr-ui/src/segmentation.rs#L369), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499), [`ui/dr-ui/src/segmentation.rs:527`](../../ui/dr-ui/src/segmentation.rs#L527), [`ui/dr-ui/ui/adjust.slint:1019`](../../ui/dr-ui/ui/adjust.slint#L1019), [`ui/dr-ui/ui/adjust.slint:126`](../../ui/dr-ui/ui/adjust.slint#L126), [`ui/dr-ui/ui/adjust.slint:1496`](../../ui/dr-ui/ui/adjust.slint#L1496), [`ui/dr-ui/ui/adjust.slint:171`](../../ui/dr-ui/ui/adjust.slint#L171), [`ui/dr-ui/ui/adjust.slint:46`](../../ui/dr-ui/ui/adjust.slint#L46), [`ui/dr-ui/ui/adjust.slint:624`](../../ui/dr-ui/ui/adjust.slint#L624), [`ui/dr-ui/ui/adjust.slint:650`](../../ui/dr-ui/ui/adjust.slint#L650), [`ui/dr-ui/ui/adjust.slint:669`](../../ui/dr-ui/ui/adjust.slint#L669), [`ui/dr-ui/ui/adjust.slint:790`](../../ui/dr-ui/ui/adjust.slint#L790), [`ui/dr-ui/ui/adjust.slint:947`](../../ui/dr-ui/ui/adjust.slint#L947), [`ui/dr-ui/ui/app.slint:149`](../../ui/dr-ui/ui/app.slint#L149), [`ui/dr-ui/ui/app.slint:2325`](../../ui/dr-ui/ui/app.slint#L2325), [`ui/dr-ui/ui/app.slint:2385`](../../ui/dr-ui/ui/app.slint#L2385), [`ui/dr-ui/ui/develop.slint:223`](../../ui/dr-ui/ui/develop.slint#L223), [`ui/dr-ui/ui/masks.slint:1216`](../../ui/dr-ui/ui/masks.slint#L1216), [`ui/dr-ui/ui/masks.slint:753`](../../ui/dr-ui/ui/masks.slint#L753), [`ui/dr-ui/ui/masks.slint:812`](../../ui/dr-ui/ui/masks.slint#L812) | +| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:212`](../../core/dr-pipeline/src/descriptor.rs#L212), [`core/dr-pipeline/src/descriptor.rs:252`](../../core/dr-pipeline/src/descriptor.rs#L252), [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/descriptor.rs:331`](../../core/dr-pipeline/src/descriptor.rs#L331), [`core/dr-pipeline/src/framing.rs:395`](../../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:25`](../../core/dr-pipeline/src/graph.rs#L25), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/graph.rs:534`](../../core/dr-pipeline/src/graph.rs#L534), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/mask.rs:1699`](../../core/dr-pipeline/src/mask.rs#L1699), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:378`](../../core/dr-pipeline/src/neutral.rs#L378), [`core/dr-pipeline/src/operation.rs:232`](../../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop/curves.rs:36`](../../ui/dr-ui/src/develop/curves.rs#L36), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/rows.rs:877`](../../ui/dr-ui/src/develop/rows.rs#L877), [`ui/dr-ui/src/lib.rs:880`](../../ui/dr-ui/src/lib.rs#L880), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../../ui/dr-ui/tests/ui_names_no_operation.rs#L1), [`ui/dr-ui/ui/app.slint:2569`](../../ui/dr-ui/ui/app.slint#L2569) | | FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/framing.rs:395`](../../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`ui/dr-ui/ui/app.slint:2780`](../../ui/dr-ui/ui/app.slint#L2780) | | FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/mask.rs:1699`](../../core/dr-pipeline/src/mask.rs#L1699), [`core/dr-pipeline/src/preset.rs:751`](../../core/dr-pipeline/src/preset.rs#L751), [`ui/dr-ui/src/develop/rows.rs:928`](../../ui/dr-ui/src/develop/rows.rs#L928) | | FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1014`](../../core/dr-gpu/src/adjust.rs#L1014), [`core/dr-gpu/src/adjust.rs:1107`](../../core/dr-gpu/src/adjust.rs#L1107), [`core/dr-gpu/src/adjust.rs:114`](../../core/dr-gpu/src/adjust.rs#L114), [`core/dr-gpu/src/adjust.rs:798`](../../core/dr-gpu/src/adjust.rs#L798), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:991`](../../core/dr-pipeline/src/graph.rs#L991), [`core/dr-pipeline/src/operation.rs:32`](../../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:403`](../../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:53`](../../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../../core/dr-pipeline/src/operation.rs#L71) | | FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:771`](../../core/dr-decode/src/lib.rs#L771), [`core/dr-decode/src/lib.rs:811`](../../core/dr-decode/src/lib.rs#L811), [`core/dr-decode/src/profile.rs:102`](../../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:473`](../../core/dr-decode/src/profile.rs#L473), [`core/dr-decode/src/profile.rs:507`](../../core/dr-decode/src/profile.rs#L507), [`core/dr-decode/src/profile.rs:678`](../../core/dr-decode/src/profile.rs#L678), [`core/dr-gpu/src/adjust.rs:37`](../../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:995`](../../core/dr-gpu/src/adjust.rs#L995), [`core/dr-gpu/src/demosaic.rs:121`](../../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1985`](../../core/dr-pipeline/src/operation.rs#L1985), [`core/dr-pipeline/src/operation.rs:2066`](../../core/dr-pipeline/src/operation.rs#L2066), [`core/dr-pipeline/src/operation.rs:2091`](../../core/dr-pipeline/src/operation.rs#L2091), [`core/dr-pipeline/src/operation.rs:2106`](../../core/dr-pipeline/src/operation.rs#L2106), [`core/dr-pipeline/src/operation.rs:2130`](../../core/dr-pipeline/src/operation.rs#L2130), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:471`](../../core/dr-pipeline/src/operation.rs#L471), [`core/dr-pipeline/src/operation.rs:481`](../../core/dr-pipeline/src/operation.rs#L481), [`core/dr-pipeline/src/operation.rs:769`](../../core/dr-pipeline/src/operation.rs#L769) | -| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:149`](../../core/dr-gpu/src/adjust.rs#L149), [`core/dr-gpu/src/adjust.rs:206`](../../core/dr-gpu/src/adjust.rs#L206), [`core/dr-gpu/src/adjust.rs:381`](../../core/dr-gpu/src/adjust.rs#L381), [`core/dr-gpu/src/adjust.rs:510`](../../core/dr-gpu/src/adjust.rs#L510), [`core/dr-gpu/src/adjust.rs:77`](../../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:195`](../../core/dr-gpu/tests/film_sim.rs#L195), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:103`](../../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:168`](../../core/dr-pipeline/src/graph.rs#L168), [`core/dr-pipeline/src/graph.rs:566`](../../core/dr-pipeline/src/graph.rs#L566), [`core/dr-pipeline/src/operation.rs:1313`](../../core/dr-pipeline/src/operation.rs#L1313), [`core/dr-pipeline/src/operation.rs:1341`](../../core/dr-pipeline/src/operation.rs#L1341), [`core/dr-pipeline/src/operation.rs:1387`](../../core/dr-pipeline/src/operation.rs#L1387), [`core/dr-pipeline/src/operation.rs:1985`](../../core/dr-pipeline/src/operation.rs#L1985), [`core/dr-pipeline/src/operation.rs:296`](../../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:102`](../../core/dr-pipeline/src/ops/film_sim.rs#L102), [`core/dr-pipeline/src/ops/film_sim.rs:139`](../../core/dr-pipeline/src/ops/film_sim.rs#L139), [`core/dr-pipeline/src/ops/film_sim.rs:163`](../../core/dr-pipeline/src/ops/film_sim.rs#L163), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:341`](../../core/dr-pipeline/src/ops/film_sim.rs#L341), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:97`](../../core/dr-pipeline/src/ops/film_sim.rs#L97), [`core/dr-pipeline/src/sidecar.rs:1055`](../../core/dr-pipeline/src/sidecar.rs#L1055), [`core/dr-pipeline/src/sidecar.rs:130`](../../core/dr-pipeline/src/sidecar.rs#L130), [`core/dr-pipeline/src/sidecar.rs:203`](../../core/dr-pipeline/src/sidecar.rs#L203), [`core/dr-pipeline/src/sidecar.rs:2681`](../../core/dr-pipeline/src/sidecar.rs#L2681), [`core/dr-pipeline/src/sidecar.rs:2757`](../../core/dr-pipeline/src/sidecar.rs#L2757), [`core/dr-pipeline/src/sidecar.rs:576`](../../core/dr-pipeline/src/sidecar.rs#L576), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`core/dr-pipeline/src/state.rs:100`](../../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop/render.rs:1051`](../../ui/dr-ui/src/develop/render.rs#L1051), [`ui/dr-ui/src/develop/render.rs:642`](../../ui/dr-ui/src/develop/render.rs#L642), [`ui/dr-ui/src/develop/render.rs:659`](../../ui/dr-ui/src/develop/render.rs#L659), [`ui/dr-ui/src/develop/render.rs:671`](../../ui/dr-ui/src/develop/render.rs#L671), [`ui/dr-ui/src/develop/render.rs:709`](../../ui/dr-ui/src/develop/render.rs#L709), [`ui/dr-ui/src/develop/render.rs:718`](../../ui/dr-ui/src/develop/render.rs#L718), [`ui/dr-ui/src/develop/render.rs:821`](../../ui/dr-ui/src/develop/render.rs#L821), [`ui/dr-ui/src/develop/render.rs:847`](../../ui/dr-ui/src/develop/render.rs#L847), [`ui/dr-ui/src/develop/session.rs:598`](../../ui/dr-ui/src/develop/session.rs#L598), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/lib.rs:2873`](../../ui/dr-ui/src/lib.rs#L2873), [`ui/dr-ui/src/lib.rs:805`](../../ui/dr-ui/src/lib.rs#L805), [`ui/dr-ui/src/lib.rs:870`](../../ui/dr-ui/src/lib.rs#L870), [`ui/dr-ui/src/library/sidecar.rs:317`](../../ui/dr-ui/src/library/sidecar.rs#L317), [`ui/dr-ui/src/library/sidecar.rs:49`](../../ui/dr-ui/src/library/sidecar.rs#L49), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/ui/adjust.slint:1040`](../../ui/dr-ui/ui/adjust.slint#L1040), [`ui/dr-ui/ui/adjust.slint:1050`](../../ui/dr-ui/ui/adjust.slint#L1050), [`ui/dr-ui/ui/adjust.slint:1268`](../../ui/dr-ui/ui/adjust.slint#L1268) | +| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:149`](../../core/dr-gpu/src/adjust.rs#L149), [`core/dr-gpu/src/adjust.rs:206`](../../core/dr-gpu/src/adjust.rs#L206), [`core/dr-gpu/src/adjust.rs:381`](../../core/dr-gpu/src/adjust.rs#L381), [`core/dr-gpu/src/adjust.rs:510`](../../core/dr-gpu/src/adjust.rs#L510), [`core/dr-gpu/src/adjust.rs:77`](../../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:195`](../../core/dr-gpu/tests/film_sim.rs#L195), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:103`](../../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:168`](../../core/dr-pipeline/src/graph.rs#L168), [`core/dr-pipeline/src/graph.rs:566`](../../core/dr-pipeline/src/graph.rs#L566), [`core/dr-pipeline/src/operation.rs:1313`](../../core/dr-pipeline/src/operation.rs#L1313), [`core/dr-pipeline/src/operation.rs:1341`](../../core/dr-pipeline/src/operation.rs#L1341), [`core/dr-pipeline/src/operation.rs:1387`](../../core/dr-pipeline/src/operation.rs#L1387), [`core/dr-pipeline/src/operation.rs:1985`](../../core/dr-pipeline/src/operation.rs#L1985), [`core/dr-pipeline/src/operation.rs:296`](../../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:102`](../../core/dr-pipeline/src/ops/film_sim.rs#L102), [`core/dr-pipeline/src/ops/film_sim.rs:139`](../../core/dr-pipeline/src/ops/film_sim.rs#L139), [`core/dr-pipeline/src/ops/film_sim.rs:163`](../../core/dr-pipeline/src/ops/film_sim.rs#L163), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:341`](../../core/dr-pipeline/src/ops/film_sim.rs#L341), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:97`](../../core/dr-pipeline/src/ops/film_sim.rs#L97), [`core/dr-pipeline/src/sidecar.rs:1055`](../../core/dr-pipeline/src/sidecar.rs#L1055), [`core/dr-pipeline/src/sidecar.rs:130`](../../core/dr-pipeline/src/sidecar.rs#L130), [`core/dr-pipeline/src/sidecar.rs:203`](../../core/dr-pipeline/src/sidecar.rs#L203), [`core/dr-pipeline/src/sidecar.rs:2681`](../../core/dr-pipeline/src/sidecar.rs#L2681), [`core/dr-pipeline/src/sidecar.rs:2757`](../../core/dr-pipeline/src/sidecar.rs#L2757), [`core/dr-pipeline/src/sidecar.rs:576`](../../core/dr-pipeline/src/sidecar.rs#L576), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`core/dr-pipeline/src/state.rs:100`](../../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop/render.rs:1051`](../../ui/dr-ui/src/develop/render.rs#L1051), [`ui/dr-ui/src/develop/render.rs:642`](../../ui/dr-ui/src/develop/render.rs#L642), [`ui/dr-ui/src/develop/render.rs:659`](../../ui/dr-ui/src/develop/render.rs#L659), [`ui/dr-ui/src/develop/render.rs:671`](../../ui/dr-ui/src/develop/render.rs#L671), [`ui/dr-ui/src/develop/render.rs:709`](../../ui/dr-ui/src/develop/render.rs#L709), [`ui/dr-ui/src/develop/render.rs:718`](../../ui/dr-ui/src/develop/render.rs#L718), [`ui/dr-ui/src/develop/render.rs:821`](../../ui/dr-ui/src/develop/render.rs#L821), [`ui/dr-ui/src/develop/render.rs:847`](../../ui/dr-ui/src/develop/render.rs#L847), [`ui/dr-ui/src/develop/session.rs:598`](../../ui/dr-ui/src/develop/session.rs#L598), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:378`](../../ui/dr-ui/src/develop_ui.rs#L378), [`ui/dr-ui/src/lib.rs:806`](../../ui/dr-ui/src/lib.rs#L806), [`ui/dr-ui/src/lib.rs:871`](../../ui/dr-ui/src/lib.rs#L871), [`ui/dr-ui/src/library/sidecar.rs:317`](../../ui/dr-ui/src/library/sidecar.rs#L317), [`ui/dr-ui/src/library/sidecar.rs:49`](../../ui/dr-ui/src/library/sidecar.rs#L49), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/ui/adjust.slint:1040`](../../ui/dr-ui/ui/adjust.slint#L1040), [`ui/dr-ui/ui/adjust.slint:1050`](../../ui/dr-ui/ui/adjust.slint#L1050), [`ui/dr-ui/ui/adjust.slint:1268`](../../ui/dr-ui/ui/adjust.slint#L1268) | | FR-DEV-3h | [`core/dr-decode/src/lib.rs:444`](../../core/dr-decode/src/lib.rs#L444), [`core/dr-decode/src/preview.rs:29`](../../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1067`](../../core/dr-pipeline/src/framing.rs#L1067), [`core/dr-pipeline/src/framing.rs:328`](../../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:498`](../../core/dr-pipeline/src/framing.rs#L498), [`core/dr-types/src/lib.rs:339`](../../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop/masks.rs:279`](../../ui/dr-ui/src/develop/masks.rs#L279), [`ui/dr-ui/src/develop/segmentation.rs:114`](../../ui/dr-ui/src/develop/segmentation.rs#L114), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499) | | FR-DEV-3i | [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-segment/src/lib.rs:1`](../../core/dr-segment/src/lib.rs#L1), [`ui/dr-ui/src/segmentation.rs:1`](../../ui/dr-ui/src/segmentation.rs#L1) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:798`](../../core/dr-gpu/src/adjust.rs#L798), [`core/dr-gpu/src/lib.rs:368`](../../core/dr-gpu/src/lib.rs#L368), [`ui/dr-ui/ui/crop.slint:1`](../../ui/dr-ui/ui/crop.slint#L1) | -| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1440`](../../core/dr-pipeline/src/graph.rs#L1440), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:640`](../../core/dr-pipeline/src/graph.rs#L640), [`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/sidecar.rs:1045`](../../core/dr-pipeline/src/sidecar.rs#L1045), [`core/dr-pipeline/src/sidecar.rs:144`](../../core/dr-pipeline/src/sidecar.rs#L144), [`core/dr-pipeline/src/sidecar.rs:2472`](../../core/dr-pipeline/src/sidecar.rs#L2472), [`core/dr-pipeline/src/sidecar.rs:2508`](../../core/dr-pipeline/src/sidecar.rs#L2508), [`core/dr-pipeline/src/sidecar.rs:2520`](../../core/dr-pipeline/src/sidecar.rs#L2520), [`core/dr-pipeline/src/sidecar.rs:303`](../../core/dr-pipeline/src/sidecar.rs#L303), [`core/dr-pipeline/src/sidecar.rs:694`](../../core/dr-pipeline/src/sidecar.rs#L694), [`core/dr-pipeline/src/sidecar.rs:710`](../../core/dr-pipeline/src/sidecar.rs#L710), [`core/dr-pipeline/src/sidecar.rs:857`](../../core/dr-pipeline/src/sidecar.rs#L857), [`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/framing.rs:720`](../../ui/dr-ui/src/develop/framing.rs#L720), [`ui/dr-ui/src/develop/history.rs:11`](../../ui/dr-ui/src/develop/history.rs#L11), [`ui/dr-ui/src/develop/history.rs:152`](../../ui/dr-ui/src/develop/history.rs#L152), [`ui/dr-ui/src/develop/history.rs:165`](../../ui/dr-ui/src/develop/history.rs#L165), [`ui/dr-ui/src/develop/history.rs:178`](../../ui/dr-ui/src/develop/history.rs#L178), [`ui/dr-ui/src/develop/history.rs:190`](../../ui/dr-ui/src/develop/history.rs#L190), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:242`](../../ui/dr-ui/src/develop/history.rs#L242), [`ui/dr-ui/src/develop/history.rs:261`](../../ui/dr-ui/src/develop/history.rs#L261), [`ui/dr-ui/src/develop/history.rs:277`](../../ui/dr-ui/src/develop/history.rs#L277), [`ui/dr-ui/src/develop/history.rs:299`](../../ui/dr-ui/src/develop/history.rs#L299), [`ui/dr-ui/src/develop/history.rs:29`](../../ui/dr-ui/src/develop/history.rs#L29), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/history.rs:68`](../../ui/dr-ui/src/develop/history.rs#L68), [`ui/dr-ui/src/develop/history.rs:82`](../../ui/dr-ui/src/develop/history.rs#L82), [`ui/dr-ui/src/develop/history.rs:93`](../../ui/dr-ui/src/develop/history.rs#L93), [`ui/dr-ui/src/develop/render.rs:671`](../../ui/dr-ui/src/develop/render.rs#L671), [`ui/dr-ui/src/develop/render.rs:847`](../../ui/dr-ui/src/develop/render.rs#L847), [`ui/dr-ui/src/develop/render.rs:883`](../../ui/dr-ui/src/develop/render.rs#L883), [`ui/dr-ui/src/develop/session.rs:80`](../../ui/dr-ui/src/develop/session.rs#L80), [`ui/dr-ui/src/develop/session.rs:89`](../../ui/dr-ui/src/develop/session.rs#L89), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:278`](../../ui/dr-ui/src/labels.rs#L278), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:1921`](../../ui/dr-ui/src/lib.rs#L1921), [`ui/dr-ui/src/lib.rs:1926`](../../ui/dr-ui/src/lib.rs#L1926), [`ui/dr-ui/src/lib.rs:1989`](../../ui/dr-ui/src/lib.rs#L1989), [`ui/dr-ui/src/lib.rs:1998`](../../ui/dr-ui/src/lib.rs#L1998), [`ui/dr-ui/src/lib.rs:2010`](../../ui/dr-ui/src/lib.rs#L2010), [`ui/dr-ui/src/lib.rs:3139`](../../ui/dr-ui/src/lib.rs#L3139), [`ui/dr-ui/src/lib.rs:3159`](../../ui/dr-ui/src/lib.rs#L3159), [`ui/dr-ui/src/library/sidecar.rs:343`](../../ui/dr-ui/src/library/sidecar.rs#L343), [`ui/dr-ui/src/library/sidecar.rs:58`](../../ui/dr-ui/src/library/sidecar.rs#L58), [`ui/dr-ui/src/presets.rs:1678`](../../ui/dr-ui/src/presets.rs#L1678), [`ui/dr-ui/src/presets.rs:373`](../../ui/dr-ui/src/presets.rs#L373), [`ui/dr-ui/src/presets.rs:437`](../../ui/dr-ui/src/presets.rs#L437), [`ui/dr-ui/src/presets.rs:557`](../../ui/dr-ui/src/presets.rs#L557), [`ui/dr-ui/ui/history.slint:191`](../../ui/dr-ui/ui/history.slint#L191), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:218`](../../ui/dr-ui/ui/history.slint#L218), [`ui/dr-ui/ui/history.slint:303`](../../ui/dr-ui/ui/history.slint#L303), [`ui/dr-ui/ui/history.slint:43`](../../ui/dr-ui/ui/history.slint#L43), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53) | -| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:390`](../../core/dr-pipeline/src/preset.rs#L390), [`core/dr-pipeline/src/preset.rs:423`](../../core/dr-pipeline/src/preset.rs#L423), [`core/dr-pipeline/src/starter.rs:1`](../../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:555`](../../ui/dr-ui/src/develop/session.rs#L555), [`ui/dr-ui/src/develop/session.rs:576`](../../ui/dr-ui/src/develop/session.rs#L576), [`ui/dr-ui/src/lib.rs:1888`](../../ui/dr-ui/src/lib.rs#L1888), [`ui/dr-ui/src/lib.rs:2674`](../../ui/dr-ui/src/lib.rs#L2674), [`ui/dr-ui/src/lib.rs:2703`](../../ui/dr-ui/src/lib.rs#L2703), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:39`](../../ui/dr-ui/src/library/sidecar.rs#L39), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui.rs:3255`](../../ui/dr-ui/src/library_ui.rs#L3255), [`ui/dr-ui/src/library_ui.rs:3838`](../../ui/dr-ui/src/library_ui.rs#L3838), [`ui/dr-ui/src/library_ui.rs:630`](../../ui/dr-ui/src/library_ui.rs#L630), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:853`](../../ui/dr-ui/ui/adjust.slint#L853), [`ui/dr-ui/ui/adjust.slint:878`](../../ui/dr-ui/ui/adjust.slint#L878), [`ui/dr-ui/ui/adjust.slint:889`](../../ui/dr-ui/ui/adjust.slint#L889), [`ui/dr-ui/ui/adjust.slint:932`](../../ui/dr-ui/ui/adjust.slint#L932), [`ui/dr-ui/ui/app.slint:1706`](../../ui/dr-ui/ui/app.slint#L1706), [`ui/dr-ui/ui/app.slint:2538`](../../ui/dr-ui/ui/app.slint#L2538), [`ui/dr-ui/ui/app.slint:3173`](../../ui/dr-ui/ui/app.slint#L3173), [`ui/dr-ui/ui/app.slint:765`](../../ui/dr-ui/ui/app.slint#L765), [`ui/dr-ui/ui/app.slint:776`](../../ui/dr-ui/ui/app.slint#L776), [`ui/dr-ui/ui/library.slint:1579`](../../ui/dr-ui/ui/library.slint#L1579), [`ui/dr-ui/ui/library.slint:1585`](../../ui/dr-ui/ui/library.slint#L1585), [`ui/dr-ui/ui/library.slint:4262`](../../ui/dr-ui/ui/library.slint#L4262), [`ui/dr-ui/ui/library.slint:4286`](../../ui/dr-ui/ui/library.slint#L4286), [`ui/dr-ui/ui/presets.slint:105`](../../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:119`](../../ui/dr-ui/ui/settings.slint#L119), [`ui/dr-ui/ui/settings.slint:711`](../../ui/dr-ui/ui/settings.slint#L711) | -| 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/history.rs:107`](../../ui/dr-ui/src/develop/history.rs#L107), [`ui/dr-ui/src/develop/history.rs:124`](../../ui/dr-ui/src/develop/history.rs#L124), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/render.rs:453`](../../ui/dr-ui/src/develop/render.rs#L453), [`ui/dr-ui/src/develop/render.rs:494`](../../ui/dr-ui/src/develop/render.rs#L494), [`ui/dr-ui/src/develop/render.rs:883`](../../ui/dr-ui/src/develop/render.rs#L883), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/lib.rs:1998`](../../ui/dr-ui/src/lib.rs#L1998), [`ui/dr-ui/src/lib.rs:2070`](../../ui/dr-ui/src/lib.rs#L2070), [`ui/dr-ui/src/lib.rs:2084`](../../ui/dr-ui/src/lib.rs#L2084), [`ui/dr-ui/src/lib.rs:3080`](../../ui/dr-ui/src/lib.rs#L3080), [`ui/dr-ui/src/lib.rs:3139`](../../ui/dr-ui/src/lib.rs#L3139), [`ui/dr-ui/src/lib.rs:3211`](../../ui/dr-ui/src/lib.rs#L3211), [`ui/dr-ui/src/lib.rs:411`](../../ui/dr-ui/src/lib.rs#L411), [`ui/dr-ui/ui/app.slint:131`](../../ui/dr-ui/ui/app.slint#L131), [`ui/dr-ui/ui/app.slint:2445`](../../ui/dr-ui/ui/app.slint#L2445), [`ui/dr-ui/ui/app.slint:2460`](../../ui/dr-ui/ui/app.slint#L2460), [`ui/dr-ui/ui/app.slint:2780`](../../ui/dr-ui/ui/app.slint#L2780), [`ui/dr-ui/ui/history.slint:102`](../../ui/dr-ui/ui/history.slint#L102), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:226`](../../ui/dr-ui/ui/history.slint#L226), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:71`](../../ui/dr-ui/ui/widgets.slint#L71) | -| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../../core/dr-gpu/src/detail.rs#L623), [`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:410`](../../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:1092`](../../core/dr-pipeline/src/graph.rs#L1092), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:717`](../../core/dr-pipeline/src/operation.rs#L717), [`core/dr-pipeline/src/sidecar.rs:1071`](../../core/dr-pipeline/src/sidecar.rs#L1071), [`core/dr-pipeline/src/sidecar.rs:1125`](../../core/dr-pipeline/src/sidecar.rs#L1125), [`core/dr-pipeline/src/sidecar.rs:1155`](../../core/dr-pipeline/src/sidecar.rs#L1155), [`core/dr-pipeline/src/sidecar.rs:219`](../../core/dr-pipeline/src/sidecar.rs#L219), [`core/dr-pipeline/src/sidecar.rs:395`](../../core/dr-pipeline/src/sidecar.rs#L395), [`core/dr-pipeline/src/sidecar.rs:888`](../../core/dr-pipeline/src/sidecar.rs#L888), [`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/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:212`](../../ui/dr-ui/src/develop/session.rs#L212), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2028`](../../ui/dr-ui/src/lib.rs#L2028), [`ui/dr-ui/src/lib.rs:3364`](../../ui/dr-ui/src/lib.rs#L3364), [`ui/dr-ui/src/lib.rs:453`](../../ui/dr-ui/src/lib.rs#L453), [`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/app.slint:2301`](../../ui/dr-ui/ui/app.slint#L2301), [`ui/dr-ui/ui/app.slint:2503`](../../ui/dr-ui/ui/app.slint#L2503), [`ui/dr-ui/ui/app.slint:3079`](../../ui/dr-ui/ui/app.slint#L3079), [`ui/dr-ui/ui/icons.slint:366`](../../ui/dr-ui/ui/icons.slint#L366), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) | -| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2366`](../../core/dr-gpu/src/adjust.rs#L2366), [`core/dr-gpu/src/adjust.rs:2443`](../../core/dr-gpu/src/adjust.rs#L2443), [`core/dr-gpu/src/adjust.rs:2528`](../../core/dr-gpu/src/adjust.rs#L2528), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:798`](../../core/dr-gpu/src/adjust.rs#L798), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`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:264`](../../core/dr-gpu/tests/local_contrast.rs#L264), [`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:524`](../../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:922`](../../core/dr-pipeline/src/graph.rs#L922), [`core/dr-pipeline/src/graph.rs:952`](../../core/dr-pipeline/src/graph.rs#L952), [`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:659`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:592`](../../core/dr-pipeline/src/ops/dehaze.rs#L592), [`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:971`](../../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop/framing.rs:444`](../../ui/dr-ui/src/develop/framing.rs#L444), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:140`](../../ui/dr-ui/src/develop/render.rs#L140), [`ui/dr-ui/src/develop/render.rs:940`](../../ui/dr-ui/src/develop/render.rs#L940), [`ui/dr-ui/src/develop/render.rs:974`](../../ui/dr-ui/src/develop/render.rs#L974), [`ui/dr-ui/src/lib.rs:100`](../../ui/dr-ui/src/lib.rs#L100), [`ui/dr-ui/src/lib.rs:1026`](../../ui/dr-ui/src/lib.rs#L1026), [`ui/dr-ui/src/lib.rs:1085`](../../ui/dr-ui/src/lib.rs#L1085) | +| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1440`](../../core/dr-pipeline/src/graph.rs#L1440), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:640`](../../core/dr-pipeline/src/graph.rs#L640), [`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/sidecar.rs:1045`](../../core/dr-pipeline/src/sidecar.rs#L1045), [`core/dr-pipeline/src/sidecar.rs:144`](../../core/dr-pipeline/src/sidecar.rs#L144), [`core/dr-pipeline/src/sidecar.rs:2472`](../../core/dr-pipeline/src/sidecar.rs#L2472), [`core/dr-pipeline/src/sidecar.rs:2508`](../../core/dr-pipeline/src/sidecar.rs#L2508), [`core/dr-pipeline/src/sidecar.rs:2520`](../../core/dr-pipeline/src/sidecar.rs#L2520), [`core/dr-pipeline/src/sidecar.rs:303`](../../core/dr-pipeline/src/sidecar.rs#L303), [`core/dr-pipeline/src/sidecar.rs:694`](../../core/dr-pipeline/src/sidecar.rs#L694), [`core/dr-pipeline/src/sidecar.rs:710`](../../core/dr-pipeline/src/sidecar.rs#L710), [`core/dr-pipeline/src/sidecar.rs:857`](../../core/dr-pipeline/src/sidecar.rs#L857), [`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/framing.rs:720`](../../ui/dr-ui/src/develop/framing.rs#L720), [`ui/dr-ui/src/develop/history.rs:11`](../../ui/dr-ui/src/develop/history.rs#L11), [`ui/dr-ui/src/develop/history.rs:152`](../../ui/dr-ui/src/develop/history.rs#L152), [`ui/dr-ui/src/develop/history.rs:165`](../../ui/dr-ui/src/develop/history.rs#L165), [`ui/dr-ui/src/develop/history.rs:178`](../../ui/dr-ui/src/develop/history.rs#L178), [`ui/dr-ui/src/develop/history.rs:190`](../../ui/dr-ui/src/develop/history.rs#L190), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:242`](../../ui/dr-ui/src/develop/history.rs#L242), [`ui/dr-ui/src/develop/history.rs:261`](../../ui/dr-ui/src/develop/history.rs#L261), [`ui/dr-ui/src/develop/history.rs:277`](../../ui/dr-ui/src/develop/history.rs#L277), [`ui/dr-ui/src/develop/history.rs:299`](../../ui/dr-ui/src/develop/history.rs#L299), [`ui/dr-ui/src/develop/history.rs:29`](../../ui/dr-ui/src/develop/history.rs#L29), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/history.rs:68`](../../ui/dr-ui/src/develop/history.rs#L68), [`ui/dr-ui/src/develop/history.rs:82`](../../ui/dr-ui/src/develop/history.rs#L82), [`ui/dr-ui/src/develop/history.rs:93`](../../ui/dr-ui/src/develop/history.rs#L93), [`ui/dr-ui/src/develop/render.rs:671`](../../ui/dr-ui/src/develop/render.rs#L671), [`ui/dr-ui/src/develop/render.rs:847`](../../ui/dr-ui/src/develop/render.rs#L847), [`ui/dr-ui/src/develop/render.rs:883`](../../ui/dr-ui/src/develop/render.rs#L883), [`ui/dr-ui/src/develop/session.rs:80`](../../ui/dr-ui/src/develop/session.rs#L80), [`ui/dr-ui/src/develop/session.rs:89`](../../ui/dr-ui/src/develop/session.rs#L89), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop_ui.rs:657`](../../ui/dr-ui/src/develop_ui.rs#L657), [`ui/dr-ui/src/develop_ui.rs:677`](../../ui/dr-ui/src/develop_ui.rs#L677), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:278`](../../ui/dr-ui/src/labels.rs#L278), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2117`](../../ui/dr-ui/src/lib.rs#L2117), [`ui/dr-ui/src/lib.rs:2122`](../../ui/dr-ui/src/lib.rs#L2122), [`ui/dr-ui/src/lib.rs:2154`](../../ui/dr-ui/src/lib.rs#L2154), [`ui/dr-ui/src/lib.rs:2163`](../../ui/dr-ui/src/lib.rs#L2163), [`ui/dr-ui/src/lib.rs:2175`](../../ui/dr-ui/src/lib.rs#L2175), [`ui/dr-ui/src/library/sidecar.rs:343`](../../ui/dr-ui/src/library/sidecar.rs#L343), [`ui/dr-ui/src/library/sidecar.rs:58`](../../ui/dr-ui/src/library/sidecar.rs#L58), [`ui/dr-ui/src/presets.rs:1678`](../../ui/dr-ui/src/presets.rs#L1678), [`ui/dr-ui/src/presets.rs:373`](../../ui/dr-ui/src/presets.rs#L373), [`ui/dr-ui/src/presets.rs:437`](../../ui/dr-ui/src/presets.rs#L437), [`ui/dr-ui/src/presets.rs:557`](../../ui/dr-ui/src/presets.rs#L557), [`ui/dr-ui/ui/history.slint:191`](../../ui/dr-ui/ui/history.slint#L191), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:218`](../../ui/dr-ui/ui/history.slint#L218), [`ui/dr-ui/ui/history.slint:303`](../../ui/dr-ui/ui/history.slint#L303), [`ui/dr-ui/ui/history.slint:43`](../../ui/dr-ui/ui/history.slint#L43), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53) | +| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:390`](../../core/dr-pipeline/src/preset.rs#L390), [`core/dr-pipeline/src/preset.rs:423`](../../core/dr-pipeline/src/preset.rs#L423), [`core/dr-pipeline/src/starter.rs:1`](../../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:555`](../../ui/dr-ui/src/develop/session.rs#L555), [`ui/dr-ui/src/develop/session.rs:576`](../../ui/dr-ui/src/develop/session.rs#L576), [`ui/dr-ui/src/develop_ui.rs:167`](../../ui/dr-ui/src/develop_ui.rs#L167), [`ui/dr-ui/src/develop_ui.rs:196`](../../ui/dr-ui/src/develop_ui.rs#L196), [`ui/dr-ui/src/lib.rs:1243`](../../ui/dr-ui/src/lib.rs#L1243), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:39`](../../ui/dr-ui/src/library/sidecar.rs#L39), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui.rs:3255`](../../ui/dr-ui/src/library_ui.rs#L3255), [`ui/dr-ui/src/library_ui.rs:3838`](../../ui/dr-ui/src/library_ui.rs#L3838), [`ui/dr-ui/src/library_ui.rs:630`](../../ui/dr-ui/src/library_ui.rs#L630), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:853`](../../ui/dr-ui/ui/adjust.slint#L853), [`ui/dr-ui/ui/adjust.slint:878`](../../ui/dr-ui/ui/adjust.slint#L878), [`ui/dr-ui/ui/adjust.slint:889`](../../ui/dr-ui/ui/adjust.slint#L889), [`ui/dr-ui/ui/adjust.slint:932`](../../ui/dr-ui/ui/adjust.slint#L932), [`ui/dr-ui/ui/app.slint:1706`](../../ui/dr-ui/ui/app.slint#L1706), [`ui/dr-ui/ui/app.slint:2538`](../../ui/dr-ui/ui/app.slint#L2538), [`ui/dr-ui/ui/app.slint:3173`](../../ui/dr-ui/ui/app.slint#L3173), [`ui/dr-ui/ui/app.slint:765`](../../ui/dr-ui/ui/app.slint#L765), [`ui/dr-ui/ui/app.slint:776`](../../ui/dr-ui/ui/app.slint#L776), [`ui/dr-ui/ui/library.slint:1579`](../../ui/dr-ui/ui/library.slint#L1579), [`ui/dr-ui/ui/library.slint:1585`](../../ui/dr-ui/ui/library.slint#L1585), [`ui/dr-ui/ui/library.slint:4262`](../../ui/dr-ui/ui/library.slint#L4262), [`ui/dr-ui/ui/library.slint:4286`](../../ui/dr-ui/ui/library.slint#L4286), [`ui/dr-ui/ui/presets.slint:105`](../../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:119`](../../ui/dr-ui/ui/settings.slint#L119), [`ui/dr-ui/ui/settings.slint:711`](../../ui/dr-ui/ui/settings.slint#L711) | +| 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/history.rs:107`](../../ui/dr-ui/src/develop/history.rs#L107), [`ui/dr-ui/src/develop/history.rs:124`](../../ui/dr-ui/src/develop/history.rs#L124), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/render.rs:453`](../../ui/dr-ui/src/develop/render.rs#L453), [`ui/dr-ui/src/develop/render.rs:494`](../../ui/dr-ui/src/develop/render.rs#L494), [`ui/dr-ui/src/develop/render.rs:883`](../../ui/dr-ui/src/develop/render.rs#L883), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/develop_ui.rs:598`](../../ui/dr-ui/src/develop_ui.rs#L598), [`ui/dr-ui/src/develop_ui.rs:657`](../../ui/dr-ui/src/develop_ui.rs#L657), [`ui/dr-ui/src/develop_ui.rs:729`](../../ui/dr-ui/src/develop_ui.rs#L729), [`ui/dr-ui/src/lib.rs:2163`](../../ui/dr-ui/src/lib.rs#L2163), [`ui/dr-ui/src/lib.rs:2235`](../../ui/dr-ui/src/lib.rs#L2235), [`ui/dr-ui/src/lib.rs:2249`](../../ui/dr-ui/src/lib.rs#L2249), [`ui/dr-ui/src/lib.rs:412`](../../ui/dr-ui/src/lib.rs#L412), [`ui/dr-ui/ui/app.slint:131`](../../ui/dr-ui/ui/app.slint#L131), [`ui/dr-ui/ui/app.slint:2445`](../../ui/dr-ui/ui/app.slint#L2445), [`ui/dr-ui/ui/app.slint:2460`](../../ui/dr-ui/ui/app.slint#L2460), [`ui/dr-ui/ui/app.slint:2780`](../../ui/dr-ui/ui/app.slint#L2780), [`ui/dr-ui/ui/history.slint:102`](../../ui/dr-ui/ui/history.slint#L102), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:226`](../../ui/dr-ui/ui/history.slint#L226), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:71`](../../ui/dr-ui/ui/widgets.slint#L71) | +| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../../core/dr-gpu/src/detail.rs#L623), [`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:410`](../../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:1092`](../../core/dr-pipeline/src/graph.rs#L1092), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:717`](../../core/dr-pipeline/src/operation.rs#L717), [`core/dr-pipeline/src/sidecar.rs:1071`](../../core/dr-pipeline/src/sidecar.rs#L1071), [`core/dr-pipeline/src/sidecar.rs:1125`](../../core/dr-pipeline/src/sidecar.rs#L1125), [`core/dr-pipeline/src/sidecar.rs:1155`](../../core/dr-pipeline/src/sidecar.rs#L1155), [`core/dr-pipeline/src/sidecar.rs:219`](../../core/dr-pipeline/src/sidecar.rs#L219), [`core/dr-pipeline/src/sidecar.rs:395`](../../core/dr-pipeline/src/sidecar.rs#L395), [`core/dr-pipeline/src/sidecar.rs:888`](../../core/dr-pipeline/src/sidecar.rs#L888), [`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/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:212`](../../ui/dr-ui/src/develop/session.rs#L212), [`ui/dr-ui/src/develop_ui.rs:884`](../../ui/dr-ui/src/develop_ui.rs#L884), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2193`](../../ui/dr-ui/src/lib.rs#L2193), [`ui/dr-ui/src/lib.rs:454`](../../ui/dr-ui/src/lib.rs#L454), [`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/app.slint:2301`](../../ui/dr-ui/ui/app.slint#L2301), [`ui/dr-ui/ui/app.slint:2503`](../../ui/dr-ui/ui/app.slint#L2503), [`ui/dr-ui/ui/app.slint:3079`](../../ui/dr-ui/ui/app.slint#L3079), [`ui/dr-ui/ui/icons.slint:366`](../../ui/dr-ui/ui/icons.slint#L366), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) | +| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2366`](../../core/dr-gpu/src/adjust.rs#L2366), [`core/dr-gpu/src/adjust.rs:2443`](../../core/dr-gpu/src/adjust.rs#L2443), [`core/dr-gpu/src/adjust.rs:2528`](../../core/dr-gpu/src/adjust.rs#L2528), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:798`](../../core/dr-gpu/src/adjust.rs#L798), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`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:264`](../../core/dr-gpu/tests/local_contrast.rs#L264), [`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:524`](../../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:922`](../../core/dr-pipeline/src/graph.rs#L922), [`core/dr-pipeline/src/graph.rs:952`](../../core/dr-pipeline/src/graph.rs#L952), [`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:659`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:592`](../../core/dr-pipeline/src/ops/dehaze.rs#L592), [`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:971`](../../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop/framing.rs:444`](../../ui/dr-ui/src/develop/framing.rs#L444), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:140`](../../ui/dr-ui/src/develop/render.rs#L140), [`ui/dr-ui/src/develop/render.rs:940`](../../ui/dr-ui/src/develop/render.rs#L940), [`ui/dr-ui/src/develop/render.rs:974`](../../ui/dr-ui/src/develop/render.rs#L974), [`ui/dr-ui/src/lib.rs:101`](../../ui/dr-ui/src/lib.rs#L101), [`ui/dr-ui/src/lib.rs:1027`](../../ui/dr-ui/src/lib.rs#L1027), [`ui/dr-ui/src/lib.rs:1086`](../../ui/dr-ui/src/lib.rs#L1086) | | FR-DSP-3 | [`core/dr-gpu/tests/frame_budget.rs:101`](../../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/local_contrast.rs:329`](../../core/dr-gpu/tests/local_contrast.rs#L329), [`core/dr-pipeline/src/ops/local_contrast.rs:447`](../../core/dr-pipeline/src/ops/local_contrast.rs#L447) | | FR-DSP-5 | [`core/dr-gpu/tests/frame_budget.rs:101`](../../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/zoom_resolution.rs:135`](../../core/dr-gpu/tests/zoom_resolution.rs#L135), [`core/dr-gpu/tests/zoom_resolution.rs:166`](../../core/dr-gpu/tests/zoom_resolution.rs#L166), [`core/dr-gpu/tests/zoom_resolution.rs:1`](../../core/dr-gpu/tests/zoom_resolution.rs#L1), [`core/dr-gpu/tests/zoom_resolution.rs:216`](../../core/dr-gpu/tests/zoom_resolution.rs#L216) | -| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:527`](../../core/dr-pipeline/src/operation.rs#L527), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop/render.rs:1316`](../../ui/dr-ui/src/develop/render.rs#L1316), [`ui/dr-ui/src/develop/render.rs:170`](../../ui/dr-ui/src/develop/render.rs#L170), [`ui/dr-ui/src/lib.rs:1974`](../../ui/dr-ui/src/lib.rs#L1974), [`ui/dr-ui/src/lib.rs:3755`](../../ui/dr-ui/src/lib.rs#L3755) | -| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1106`](../../ui/dr-ui/src/develop/render.rs#L1106), [`ui/dr-ui/src/develop/render.rs:1138`](../../ui/dr-ui/src/develop/render.rs#L1138), [`ui/dr-ui/src/develop/render.rs:224`](../../ui/dr-ui/src/develop/render.rs#L224), [`ui/dr-ui/src/develop/session.rs:112`](../../ui/dr-ui/src/develop/session.rs#L112), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:2108`](../../ui/dr-ui/src/lib.rs#L2108), [`ui/dr-ui/src/lib.rs:430`](../../ui/dr-ui/src/lib.rs#L430), [`ui/dr-ui/ui/histogram.slint:184`](../../ui/dr-ui/ui/histogram.slint#L184), [`ui/dr-ui/ui/histogram.slint:1`](../../ui/dr-ui/ui/histogram.slint#L1) | -| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop/render.rs:116`](../../ui/dr-ui/src/develop/render.rs#L116), [`ui/dr-ui/src/develop/render.rs:1316`](../../ui/dr-ui/src/develop/render.rs#L1316), [`ui/dr-ui/src/develop/render.rs:1362`](../../ui/dr-ui/src/develop/render.rs#L1362), [`ui/dr-ui/src/develop/render.rs:1381`](../../ui/dr-ui/src/develop/render.rs#L1381), [`ui/dr-ui/src/develop/render.rs:170`](../../ui/dr-ui/src/develop/render.rs#L170), [`ui/dr-ui/src/develop/session.rs:265`](../../ui/dr-ui/src/develop/session.rs#L265), [`ui/dr-ui/src/display_ui.rs:192`](../../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1910`](../../ui/dr-ui/src/lib.rs#L1910), [`ui/dr-ui/src/lib.rs:1974`](../../ui/dr-ui/src/lib.rs#L1974), [`ui/dr-ui/src/lib.rs:3662`](../../ui/dr-ui/src/lib.rs#L3662), [`ui/dr-ui/src/lib.rs:3755`](../../ui/dr-ui/src/lib.rs#L3755), [`ui/dr-ui/ui/app.slint:1981`](../../ui/dr-ui/ui/app.slint#L1981), [`ui/dr-ui/ui/app.slint:64`](../../ui/dr-ui/ui/app.slint#L64), [`ui/dr-ui/ui/settings.slint:165`](../../ui/dr-ui/ui/settings.slint#L165), [`ui/dr-ui/ui/settings.slint:968`](../../ui/dr-ui/ui/settings.slint#L968) | +| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:527`](../../core/dr-pipeline/src/operation.rs#L527), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop/render.rs:1316`](../../ui/dr-ui/src/develop/render.rs#L1316), [`ui/dr-ui/src/develop/render.rs:170`](../../ui/dr-ui/src/develop/render.rs#L170), [`ui/dr-ui/src/lib.rs:2139`](../../ui/dr-ui/src/lib.rs#L2139), [`ui/dr-ui/src/lib.rs:2923`](../../ui/dr-ui/src/lib.rs#L2923) | +| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1106`](../../ui/dr-ui/src/develop/render.rs#L1106), [`ui/dr-ui/src/develop/render.rs:1138`](../../ui/dr-ui/src/develop/render.rs#L1138), [`ui/dr-ui/src/develop/render.rs:224`](../../ui/dr-ui/src/develop/render.rs#L224), [`ui/dr-ui/src/develop/session.rs:112`](../../ui/dr-ui/src/develop/session.rs#L112), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:2273`](../../ui/dr-ui/src/lib.rs#L2273), [`ui/dr-ui/src/lib.rs:431`](../../ui/dr-ui/src/lib.rs#L431), [`ui/dr-ui/ui/histogram.slint:184`](../../ui/dr-ui/ui/histogram.slint#L184), [`ui/dr-ui/ui/histogram.slint:1`](../../ui/dr-ui/ui/histogram.slint#L1) | +| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop/render.rs:116`](../../ui/dr-ui/src/develop/render.rs#L116), [`ui/dr-ui/src/develop/render.rs:1316`](../../ui/dr-ui/src/develop/render.rs#L1316), [`ui/dr-ui/src/develop/render.rs:1362`](../../ui/dr-ui/src/develop/render.rs#L1362), [`ui/dr-ui/src/develop/render.rs:1381`](../../ui/dr-ui/src/develop/render.rs#L1381), [`ui/dr-ui/src/develop/render.rs:170`](../../ui/dr-ui/src/develop/render.rs#L170), [`ui/dr-ui/src/develop/session.rs:265`](../../ui/dr-ui/src/develop/session.rs#L265), [`ui/dr-ui/src/display_ui.rs:192`](../../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1265`](../../ui/dr-ui/src/lib.rs#L1265), [`ui/dr-ui/src/lib.rs:2139`](../../ui/dr-ui/src/lib.rs#L2139), [`ui/dr-ui/src/lib.rs:2900`](../../ui/dr-ui/src/lib.rs#L2900), [`ui/dr-ui/src/lib.rs:2923`](../../ui/dr-ui/src/lib.rs#L2923), [`ui/dr-ui/ui/app.slint:1981`](../../ui/dr-ui/ui/app.slint#L1981), [`ui/dr-ui/ui/app.slint:64`](../../ui/dr-ui/ui/app.slint#L64), [`ui/dr-ui/ui/settings.slint:165`](../../ui/dr-ui/ui/settings.slint#L165), [`ui/dr-ui/ui/settings.slint:968`](../../ui/dr-ui/ui/settings.slint#L968) | | FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:157`](../../core/dr-export/src/lib.rs#L157), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:57`](../../core/dr-export/src/lib.rs#L57), [`core/dr-gpu/src/adjust.rs:2676`](../../core/dr-gpu/src/adjust.rs#L2676), [`core/dr-pipeline/src/graph.rs:835`](../../core/dr-pipeline/src/graph.rs#L835), [`core/dr-pipeline/src/graph.rs:966`](../../core/dr-pipeline/src/graph.rs#L966), [`core/dr-pipeline/src/operation.rs:527`](../../core/dr-pipeline/src/operation.rs#L527), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:963`](../../core/dr-types/src/settings.rs#L963), [`ui/dr-ui/src/develop/render.rs:1381`](../../ui/dr-ui/src/develop/render.rs#L1381), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-3 | [`core/dr-export/src/lib.rs:186`](../../core/dr-export/src/lib.rs#L186), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1008`](../../core/dr-types/src/settings.rs#L1008), [`core/dr-types/src/settings.rs:1016`](../../core/dr-types/src/settings.rs#L1016), [`core/dr-types/src/settings.rs:1032`](../../core/dr-types/src/settings.rs#L1032), [`core/dr-types/src/settings.rs:1101`](../../core/dr-types/src/settings.rs#L1101), [`core/dr-types/src/settings.rs:1129`](../../core/dr-types/src/settings.rs#L1129), [`core/dr-types/src/settings.rs:1140`](../../core/dr-types/src/settings.rs#L1140), [`core/dr-types/src/settings.rs:1146`](../../core/dr-types/src/settings.rs#L1146), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:274`](../../ui/dr-ui/src/settings_ui.rs#L274), [`ui/dr-ui/src/settings_ui.rs:287`](../../ui/dr-ui/src/settings_ui.rs#L287), [`ui/dr-ui/src/settings_ui.rs:302`](../../ui/dr-ui/src/settings_ui.rs#L302), [`ui/dr-ui/src/settings_ui.rs:727`](../../ui/dr-ui/src/settings_ui.rs#L727), [`ui/dr-ui/ui/settings.slint:851`](../../ui/dr-ui/ui/settings.slint#L851), [`ui/dr-ui/ui/settings.slint:861`](../../ui/dr-ui/ui/settings.slint#L861) | | 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:607`](../../ui/dr-ui/src/lib.rs#L607), [`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:847`](../../ui/dr-ui/src/settings_ui.rs#L847) | -| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1005`](../../ui/dr-ui/src/export.rs#L1005), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:268`](../../ui/dr-ui/src/lib.rs#L268), [`ui/dr-ui/src/lib.rs:2813`](../../ui/dr-ui/src/lib.rs#L2813), [`ui/dr-ui/src/lib.rs:607`](../../ui/dr-ui/src/lib.rs#L607), [`ui/dr-ui/src/lib.rs:653`](../../ui/dr-ui/src/lib.rs#L653), [`ui/dr-ui/src/lib.rs:680`](../../ui/dr-ui/src/lib.rs#L680), [`ui/dr-ui/src/library_ui.rs:4249`](../../ui/dr-ui/src/library_ui.rs#L4249), [`ui/dr-ui/src/library_ui.rs:721`](../../ui/dr-ui/src/library_ui.rs#L721), [`ui/dr-ui/src/library_ui.rs:8208`](../../ui/dr-ui/src/library_ui.rs#L8208), [`ui/dr-ui/src/library_ui.rs:821`](../../ui/dr-ui/src/library_ui.rs#L821), [`ui/dr-ui/src/library_ui.rs:8285`](../../ui/dr-ui/src/library_ui.rs#L8285), [`ui/dr-ui/src/library_ui.rs:8297`](../../ui/dr-ui/src/library_ui.rs#L8297), [`ui/dr-ui/src/library_ui.rs:878`](../../ui/dr-ui/src/library_ui.rs#L878), [`ui/dr-ui/ui/app.slint:1139`](../../ui/dr-ui/ui/app.slint#L1139), [`ui/dr-ui/ui/app.slint:1716`](../../ui/dr-ui/ui/app.slint#L1716), [`ui/dr-ui/ui/library.slint:1588`](../../ui/dr-ui/ui/library.slint#L1588), [`ui/dr-ui/ui/library.slint:4332`](../../ui/dr-ui/ui/library.slint#L4332) | -| FR-EXP-8 | [`core/dr-decode/src/lib.rs:366`](../../core/dr-decode/src/lib.rs#L366), [`core/dr-decode/src/lib.rs:390`](../../core/dr-decode/src/lib.rs#L390), [`core/dr-decode/src/lib.rs:404`](../../core/dr-decode/src/lib.rs#L404), [`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:1233`](../../core/dr-decode/src/locate.rs#L1233), [`core/dr-decode/src/locate.rs:1292`](../../core/dr-decode/src/locate.rs#L1292), [`core/dr-decode/src/locate.rs:359`](../../core/dr-decode/src/locate.rs#L359), [`core/dr-decode/src/locate.rs:556`](../../core/dr-decode/src/locate.rs#L556), [`core/dr-decode/src/locate.rs:640`](../../core/dr-decode/src/locate.rs#L640), [`core/dr-decode/src/locate.rs:653`](../../core/dr-decode/src/locate.rs#L653), [`core/dr-decode/src/locate.rs:736`](../../core/dr-decode/src/locate.rs#L736), [`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:140`](../../core/dr-export/src/lib.rs#L140), [`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:655`](../../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:671`](../../core/dr-types/src/settings.rs#L671), [`ui/dr-ui/src/develop/session.rs:37`](../../ui/dr-ui/src/develop/session.rs#L37), [`ui/dr-ui/src/develop/session.rs:412`](../../ui/dr-ui/src/develop/session.rs#L412), [`ui/dr-ui/src/develop/session.rs:524`](../../ui/dr-ui/src/develop/session.rs#L524), [`ui/dr-ui/src/export.rs:474`](../../ui/dr-ui/src/export.rs#L474), [`ui/dr-ui/src/export.rs:641`](../../ui/dr-ui/src/export.rs#L641), [`ui/dr-ui/src/export.rs:687`](../../ui/dr-ui/src/export.rs#L687), [`ui/dr-ui/src/export.rs:715`](../../ui/dr-ui/src/export.rs#L715), [`ui/dr-ui/src/export.rs:839`](../../ui/dr-ui/src/export.rs#L839), [`ui/dr-ui/src/export.rs:857`](../../ui/dr-ui/src/export.rs#L857), [`ui/dr-ui/src/lib.rs:268`](../../ui/dr-ui/src/lib.rs#L268), [`ui/dr-ui/src/lib.rs:291`](../../ui/dr-ui/src/lib.rs#L291), [`ui/dr-ui/src/lib.rs:639`](../../ui/dr-ui/src/lib.rs#L639), [`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:546`](../../core/dr-decode/src/lib.rs#L546), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1134`](../../core/dr-gpu/src/adjust.rs#L1134), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:546`](../../ui/dr-ui/src/develop/render.rs#L546), [`ui/dr-ui/src/develop/render.rs:584`](../../ui/dr-ui/src/develop/render.rs#L584), [`ui/dr-ui/src/lib.rs:607`](../../ui/dr-ui/src/lib.rs#L607), [`ui/dr-ui/src/library/sweep.rs:506`](../../ui/dr-ui/src/library/sweep.rs#L506), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | -| FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1576`](../../ui/dr-ui/src/lib.rs#L1576), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1721`](../../ui/dr-ui/ui/app.slint#L1721), [`ui/dr-ui/ui/app.slint:829`](../../ui/dr-ui/ui/app.slint#L829), [`ui/dr-ui/ui/library.slint:1596`](../../ui/dr-ui/ui/library.slint#L1596), [`ui/dr-ui/ui/library.slint:4277`](../../ui/dr-ui/ui/library.slint#L4277), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#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:608`](../../ui/dr-ui/src/lib.rs#L608), [`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:847`](../../ui/dr-ui/src/settings_ui.rs#L847) | +| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/develop_ui.rs:309`](../../ui/dr-ui/src/develop_ui.rs#L309), [`ui/dr-ui/src/export.rs:1005`](../../ui/dr-ui/src/export.rs#L1005), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:269`](../../ui/dr-ui/src/lib.rs#L269), [`ui/dr-ui/src/lib.rs:608`](../../ui/dr-ui/src/lib.rs#L608), [`ui/dr-ui/src/lib.rs:654`](../../ui/dr-ui/src/lib.rs#L654), [`ui/dr-ui/src/lib.rs:681`](../../ui/dr-ui/src/lib.rs#L681), [`ui/dr-ui/src/library_ui.rs:4249`](../../ui/dr-ui/src/library_ui.rs#L4249), [`ui/dr-ui/src/library_ui.rs:721`](../../ui/dr-ui/src/library_ui.rs#L721), [`ui/dr-ui/src/library_ui.rs:8208`](../../ui/dr-ui/src/library_ui.rs#L8208), [`ui/dr-ui/src/library_ui.rs:821`](../../ui/dr-ui/src/library_ui.rs#L821), [`ui/dr-ui/src/library_ui.rs:8285`](../../ui/dr-ui/src/library_ui.rs#L8285), [`ui/dr-ui/src/library_ui.rs:8297`](../../ui/dr-ui/src/library_ui.rs#L8297), [`ui/dr-ui/src/library_ui.rs:878`](../../ui/dr-ui/src/library_ui.rs#L878), [`ui/dr-ui/ui/app.slint:1139`](../../ui/dr-ui/ui/app.slint#L1139), [`ui/dr-ui/ui/app.slint:1716`](../../ui/dr-ui/ui/app.slint#L1716), [`ui/dr-ui/ui/library.slint:1588`](../../ui/dr-ui/ui/library.slint#L1588), [`ui/dr-ui/ui/library.slint:4332`](../../ui/dr-ui/ui/library.slint#L4332) | +| FR-EXP-8 | [`core/dr-decode/src/lib.rs:366`](../../core/dr-decode/src/lib.rs#L366), [`core/dr-decode/src/lib.rs:390`](../../core/dr-decode/src/lib.rs#L390), [`core/dr-decode/src/lib.rs:404`](../../core/dr-decode/src/lib.rs#L404), [`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:1233`](../../core/dr-decode/src/locate.rs#L1233), [`core/dr-decode/src/locate.rs:1292`](../../core/dr-decode/src/locate.rs#L1292), [`core/dr-decode/src/locate.rs:359`](../../core/dr-decode/src/locate.rs#L359), [`core/dr-decode/src/locate.rs:556`](../../core/dr-decode/src/locate.rs#L556), [`core/dr-decode/src/locate.rs:640`](../../core/dr-decode/src/locate.rs#L640), [`core/dr-decode/src/locate.rs:653`](../../core/dr-decode/src/locate.rs#L653), [`core/dr-decode/src/locate.rs:736`](../../core/dr-decode/src/locate.rs#L736), [`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:140`](../../core/dr-export/src/lib.rs#L140), [`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:655`](../../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:671`](../../core/dr-types/src/settings.rs#L671), [`ui/dr-ui/src/develop/session.rs:37`](../../ui/dr-ui/src/develop/session.rs#L37), [`ui/dr-ui/src/develop/session.rs:412`](../../ui/dr-ui/src/develop/session.rs#L412), [`ui/dr-ui/src/develop/session.rs:524`](../../ui/dr-ui/src/develop/session.rs#L524), [`ui/dr-ui/src/export.rs:474`](../../ui/dr-ui/src/export.rs#L474), [`ui/dr-ui/src/export.rs:641`](../../ui/dr-ui/src/export.rs#L641), [`ui/dr-ui/src/export.rs:687`](../../ui/dr-ui/src/export.rs#L687), [`ui/dr-ui/src/export.rs:715`](../../ui/dr-ui/src/export.rs#L715), [`ui/dr-ui/src/export.rs:839`](../../ui/dr-ui/src/export.rs#L839), [`ui/dr-ui/src/export.rs:857`](../../ui/dr-ui/src/export.rs#L857), [`ui/dr-ui/src/lib.rs:269`](../../ui/dr-ui/src/lib.rs#L269), [`ui/dr-ui/src/lib.rs:292`](../../ui/dr-ui/src/lib.rs#L292), [`ui/dr-ui/src/lib.rs:640`](../../ui/dr-ui/src/lib.rs#L640), [`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:546`](../../core/dr-decode/src/lib.rs#L546), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1134`](../../core/dr-gpu/src/adjust.rs#L1134), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:546`](../../ui/dr-ui/src/develop/render.rs#L546), [`ui/dr-ui/src/develop/render.rs:584`](../../ui/dr-ui/src/develop/render.rs#L584), [`ui/dr-ui/src/lib.rs:608`](../../ui/dr-ui/src/lib.rs#L608), [`ui/dr-ui/src/library/sweep.rs:506`](../../ui/dr-ui/src/library/sweep.rs#L506), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | +| FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1814`](../../ui/dr-ui/src/lib.rs#L1814), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1721`](../../ui/dr-ui/ui/app.slint#L1721), [`ui/dr-ui/ui/app.slint:829`](../../ui/dr-ui/ui/app.slint#L829), [`ui/dr-ui/ui/library.slint:1596`](../../ui/dr-ui/ui/library.slint#L1596), [`ui/dr-ui/ui/library.slint:4277`](../../ui/dr-ui/ui/library.slint#L4277), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-10 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1) | | FR-MRG-11 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1) | | FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:104`](../../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1157`](../../core/dr-gpu/src/adjust.rs#L1157), [`core/dr-gpu/src/adjust.rs:1276`](../../core/dr-gpu/src/adjust.rs#L1276), [`core/dr-gpu/src/adjust.rs:316`](../../core/dr-gpu/src/adjust.rs#L316), [`core/dr-pipeline/src/graph.rs:852`](../../core/dr-pipeline/src/graph.rs#L852), [`core/dr-pipeline/src/operation.rs:2183`](../../core/dr-pipeline/src/operation.rs#L2183), [`core/dr-pipeline/src/operation.rs:427`](../../core/dr-pipeline/src/operation.rs#L427), [`core/dr-pipeline/src/operation.rs:612`](../../core/dr-pipeline/src/operation.rs#L612), [`core/dr-pipeline/src/operation.rs:916`](../../core/dr-pipeline/src/operation.rs#L916), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) | @@ -111,7 +111,7 @@ A tag naming an ID `requirements.md` does not define — what renumbering produc | FR-MRG-7 | [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:829`](../../ui/dr-ui/ui/app.slint#L829), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-8 | [`core/dr-pano/src/xfeat.rs:1`](../../core/dr-pano/src/xfeat.rs#L1), [`core/dr-segment/examples/onnx_probe.rs:1`](../../core/dr-segment/examples/onnx_probe.rs#L1) | | 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:375`](../../core/dr-sync/src/account.rs#L375), [`ui/dr-ui/src/launch.rs:316`](../../ui/dr-ui/src/launch.rs#L316), [`ui/dr-ui/src/launch.rs:61`](../../ui/dr-ui/src/launch.rs#L61), [`ui/dr-ui/src/launch_ui.rs:418`](../../ui/dr-ui/src/launch_ui.rs#L418) | -| FR-NC-10 | [`core/dr-sync/src/account.rs:240`](../../core/dr-sync/src/account.rs#L240), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:680`](../../ui/dr-ui/src/lib.rs#L680), [`ui/dr-ui/src/library/paths.rs:69`](../../ui/dr-ui/src/library/paths.rs#L69), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:427`](../../ui/dr-ui/src/library/sidecar.rs#L427), [`ui/dr-ui/src/library/thumbnails_fetch.rs:449`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L449), [`ui/dr-ui/src/library_ui.rs:2057`](../../ui/dr-ui/src/library_ui.rs#L2057), [`ui/dr-ui/src/library_ui.rs:4249`](../../ui/dr-ui/src/library_ui.rs#L4249), [`ui/dr-ui/src/library_ui.rs:662`](../../ui/dr-ui/src/library_ui.rs#L662), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-NC-10 | [`core/dr-sync/src/account.rs:240`](../../core/dr-sync/src/account.rs#L240), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:681`](../../ui/dr-ui/src/lib.rs#L681), [`ui/dr-ui/src/library/paths.rs:69`](../../ui/dr-ui/src/library/paths.rs#L69), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:427`](../../ui/dr-ui/src/library/sidecar.rs#L427), [`ui/dr-ui/src/library/thumbnails_fetch.rs:449`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L449), [`ui/dr-ui/src/library_ui.rs:2057`](../../ui/dr-ui/src/library_ui.rs#L2057), [`ui/dr-ui/src/library_ui.rs:4249`](../../ui/dr-ui/src/library_ui.rs#L4249), [`ui/dr-ui/src/library_ui.rs:662`](../../ui/dr-ui/src/library_ui.rs#L662), [`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:1049`](../../core/dr-sync-nextcloud/src/lib.rs#L1049), [`core/dr-sync-nextcloud/src/lib.rs:40`](../../core/dr-sync-nextcloud/src/lib.rs#L40), [`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:87`](../../core/dr-sync/src/account.rs#L87), [`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:317`](../../ui/dr-ui/src/launch_ui.rs#L317), [`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:318`](../../core/dr-sync/src/account.rs#L318), [`core/dr-sync/src/account.rs:375`](../../core/dr-sync/src/account.rs#L375), [`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) | @@ -119,49 +119,49 @@ A tag naming an ID `requirements.md` does not define — what renumbering produc | 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:129`](../../core/dr-sync/src/scan.rs#L129), [`ui/dr-ui/src/launch.rs:61`](../../ui/dr-ui/src/launch.rs#L61) | | FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../../core/dr-sync-nextcloud/src/propfind.rs#L51), [`ui/dr-ui/src/import.rs:488`](../../ui/dr-ui/src/import.rs#L488) | | FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../../ui/dr-ui/src/activity.rs#L1) | -| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1182`](../../core/dr-catalog/src/schema.rs#L1182), [`core/dr-catalog/src/schema.rs:1637`](../../core/dr-catalog/src/schema.rs#L1637), [`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:2940`](../../ui/dr-ui/src/collections_ui.rs#L2940), [`ui/dr-ui/src/collections_ui.rs:3182`](../../ui/dr-ui/src/collections_ui.rs#L3182), [`ui/dr-ui/src/collections_ui.rs:4083`](../../ui/dr-ui/src/collections_ui.rs#L4083), [`ui/dr-ui/src/collections_ui.rs:785`](../../ui/dr-ui/src/collections_ui.rs#L785), [`ui/dr-ui/src/collections_ui.rs:856`](../../ui/dr-ui/src/collections_ui.rs#L856), [`ui/dr-ui/src/lib.rs:1350`](../../ui/dr-ui/src/lib.rs#L1350), [`ui/dr-ui/src/lib.rs:2487`](../../ui/dr-ui/src/lib.rs#L2487), [`ui/dr-ui/src/lib.rs:2558`](../../ui/dr-ui/src/lib.rs#L2558), [`ui/dr-ui/src/lib.rs:3852`](../../ui/dr-ui/src/lib.rs#L3852), [`ui/dr-ui/src/lib.rs:3853`](../../ui/dr-ui/src/lib.rs#L3853), [`ui/dr-ui/src/lib.rs:3892`](../../ui/dr-ui/src/lib.rs#L3892), [`ui/dr-ui/src/library/thumbnails_fetch.rs:118`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L118), [`ui/dr-ui/src/library/thumbnails_fetch.rs:393`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L393), [`ui/dr-ui/src/library/thumbnails_fetch.rs:574`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L574), [`ui/dr-ui/src/library/thumbnails_fetch.rs:711`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L711), [`ui/dr-ui/src/library/thumbnails_fetch.rs:726`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L726), [`ui/dr-ui/src/library/thumbnails_fetch.rs:95`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L95), [`ui/dr-ui/src/library_ui.rs:1482`](../../ui/dr-ui/src/library_ui.rs#L1482), [`ui/dr-ui/src/library_ui.rs:1555`](../../ui/dr-ui/src/library_ui.rs#L1555), [`ui/dr-ui/src/library_ui.rs:1656`](../../ui/dr-ui/src/library_ui.rs#L1656), [`ui/dr-ui/src/library_ui.rs:1711`](../../ui/dr-ui/src/library_ui.rs#L1711), [`ui/dr-ui/src/library_ui.rs:1846`](../../ui/dr-ui/src/library_ui.rs#L1846), [`ui/dr-ui/src/library_ui.rs:1964`](../../ui/dr-ui/src/library_ui.rs#L1964), [`ui/dr-ui/src/library_ui.rs:2731`](../../ui/dr-ui/src/library_ui.rs#L2731), [`ui/dr-ui/src/library_ui.rs:344`](../../ui/dr-ui/src/library_ui.rs#L344), [`ui/dr-ui/src/library_ui.rs:355`](../../ui/dr-ui/src/library_ui.rs#L355), [`ui/dr-ui/src/library_ui.rs:370`](../../ui/dr-ui/src/library_ui.rs#L370), [`ui/dr-ui/src/library_ui.rs:379`](../../ui/dr-ui/src/library_ui.rs#L379), [`ui/dr-ui/src/library_ui.rs:387`](../../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:517`](../../ui/dr-ui/src/library_ui.rs#L517), [`ui/dr-ui/src/library_ui.rs:527`](../../ui/dr-ui/src/library_ui.rs#L527), [`ui/dr-ui/src/library_ui.rs:542`](../../ui/dr-ui/src/library_ui.rs#L542), [`ui/dr-ui/src/library_ui.rs:589`](../../ui/dr-ui/src/library_ui.rs#L589), [`ui/dr-ui/src/library_ui.rs:604`](../../ui/dr-ui/src/library_ui.rs#L604), [`ui/dr-ui/src/library_ui.rs:678`](../../ui/dr-ui/src/library_ui.rs#L678), [`ui/dr-ui/src/library_ui.rs:6945`](../../ui/dr-ui/src/library_ui.rs#L6945), [`ui/dr-ui/src/library_ui.rs:6963`](../../ui/dr-ui/src/library_ui.rs#L6963), [`ui/dr-ui/src/library_ui.rs:709`](../../ui/dr-ui/src/library_ui.rs#L709), [`ui/dr-ui/src/library_ui.rs:721`](../../ui/dr-ui/src/library_ui.rs#L721), [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:157`](../../ui/dr-ui/src/settings_ui.rs#L157), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:542`](../../ui/dr-ui/src/settings_ui.rs#L542), [`ui/dr-ui/ui/app.slint:1610`](../../ui/dr-ui/ui/app.slint#L1610), [`ui/dr-ui/ui/app.slint:3163`](../../ui/dr-ui/ui/app.slint#L3163), [`ui/dr-ui/ui/app.slint:541`](../../ui/dr-ui/ui/app.slint#L541), [`ui/dr-ui/ui/app.slint:549`](../../ui/dr-ui/ui/app.slint#L549), [`ui/dr-ui/ui/app.slint:555`](../../ui/dr-ui/ui/app.slint#L555), [`ui/dr-ui/ui/collections.slint:1037`](../../ui/dr-ui/ui/collections.slint#L1037), [`ui/dr-ui/ui/collections.slint:1048`](../../ui/dr-ui/ui/collections.slint#L1048), [`ui/dr-ui/ui/collections.slint:1127`](../../ui/dr-ui/ui/collections.slint#L1127), [`ui/dr-ui/ui/collections.slint:272`](../../ui/dr-ui/ui/collections.slint#L272), [`ui/dr-ui/ui/collections.slint:424`](../../ui/dr-ui/ui/collections.slint#L424), [`ui/dr-ui/ui/collections.slint:433`](../../ui/dr-ui/ui/collections.slint#L433), [`ui/dr-ui/ui/collections.slint:436`](../../ui/dr-ui/ui/collections.slint#L436), [`ui/dr-ui/ui/collections.slint:482`](../../ui/dr-ui/ui/collections.slint#L482), [`ui/dr-ui/ui/collections.slint:52`](../../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:720`](../../ui/dr-ui/ui/collections.slint#L720), [`ui/dr-ui/ui/collections.slint:877`](../../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/collections.slint:91`](../../ui/dr-ui/ui/collections.slint#L91), [`ui/dr-ui/ui/icons.slint:262`](../../ui/dr-ui/ui/icons.slint#L262), [`ui/dr-ui/ui/settings.slint:114`](../../ui/dr-ui/ui/settings.slint#L114), [`ui/dr-ui/ui/settings.slint:656`](../../ui/dr-ui/ui/settings.slint#L656) | +| 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:1182`](../../core/dr-catalog/src/schema.rs#L1182), [`core/dr-catalog/src/schema.rs:1637`](../../core/dr-catalog/src/schema.rs#L1637), [`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:2940`](../../ui/dr-ui/src/collections_ui.rs#L2940), [`ui/dr-ui/src/collections_ui.rs:3182`](../../ui/dr-ui/src/collections_ui.rs#L3182), [`ui/dr-ui/src/collections_ui.rs:4083`](../../ui/dr-ui/src/collections_ui.rs#L4083), [`ui/dr-ui/src/collections_ui.rs:785`](../../ui/dr-ui/src/collections_ui.rs#L785), [`ui/dr-ui/src/collections_ui.rs:856`](../../ui/dr-ui/src/collections_ui.rs#L856), [`ui/dr-ui/src/lib.rs:1572`](../../ui/dr-ui/src/lib.rs#L1572), [`ui/dr-ui/src/lib.rs:2715`](../../ui/dr-ui/src/lib.rs#L2715), [`ui/dr-ui/src/lib.rs:2786`](../../ui/dr-ui/src/lib.rs#L2786), [`ui/dr-ui/src/lib.rs:3004`](../../ui/dr-ui/src/lib.rs#L3004), [`ui/dr-ui/src/lib.rs:3005`](../../ui/dr-ui/src/lib.rs#L3005), [`ui/dr-ui/src/lib.rs:3044`](../../ui/dr-ui/src/lib.rs#L3044), [`ui/dr-ui/src/library/thumbnails_fetch.rs:118`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L118), [`ui/dr-ui/src/library/thumbnails_fetch.rs:393`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L393), [`ui/dr-ui/src/library/thumbnails_fetch.rs:574`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L574), [`ui/dr-ui/src/library/thumbnails_fetch.rs:711`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L711), [`ui/dr-ui/src/library/thumbnails_fetch.rs:726`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L726), [`ui/dr-ui/src/library/thumbnails_fetch.rs:95`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L95), [`ui/dr-ui/src/library_ui.rs:1482`](../../ui/dr-ui/src/library_ui.rs#L1482), [`ui/dr-ui/src/library_ui.rs:1555`](../../ui/dr-ui/src/library_ui.rs#L1555), [`ui/dr-ui/src/library_ui.rs:1656`](../../ui/dr-ui/src/library_ui.rs#L1656), [`ui/dr-ui/src/library_ui.rs:1711`](../../ui/dr-ui/src/library_ui.rs#L1711), [`ui/dr-ui/src/library_ui.rs:1846`](../../ui/dr-ui/src/library_ui.rs#L1846), [`ui/dr-ui/src/library_ui.rs:1964`](../../ui/dr-ui/src/library_ui.rs#L1964), [`ui/dr-ui/src/library_ui.rs:2731`](../../ui/dr-ui/src/library_ui.rs#L2731), [`ui/dr-ui/src/library_ui.rs:344`](../../ui/dr-ui/src/library_ui.rs#L344), [`ui/dr-ui/src/library_ui.rs:355`](../../ui/dr-ui/src/library_ui.rs#L355), [`ui/dr-ui/src/library_ui.rs:370`](../../ui/dr-ui/src/library_ui.rs#L370), [`ui/dr-ui/src/library_ui.rs:379`](../../ui/dr-ui/src/library_ui.rs#L379), [`ui/dr-ui/src/library_ui.rs:387`](../../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:517`](../../ui/dr-ui/src/library_ui.rs#L517), [`ui/dr-ui/src/library_ui.rs:527`](../../ui/dr-ui/src/library_ui.rs#L527), [`ui/dr-ui/src/library_ui.rs:542`](../../ui/dr-ui/src/library_ui.rs#L542), [`ui/dr-ui/src/library_ui.rs:589`](../../ui/dr-ui/src/library_ui.rs#L589), [`ui/dr-ui/src/library_ui.rs:604`](../../ui/dr-ui/src/library_ui.rs#L604), [`ui/dr-ui/src/library_ui.rs:678`](../../ui/dr-ui/src/library_ui.rs#L678), [`ui/dr-ui/src/library_ui.rs:6945`](../../ui/dr-ui/src/library_ui.rs#L6945), [`ui/dr-ui/src/library_ui.rs:6963`](../../ui/dr-ui/src/library_ui.rs#L6963), [`ui/dr-ui/src/library_ui.rs:709`](../../ui/dr-ui/src/library_ui.rs#L709), [`ui/dr-ui/src/library_ui.rs:721`](../../ui/dr-ui/src/library_ui.rs#L721), [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:157`](../../ui/dr-ui/src/settings_ui.rs#L157), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:542`](../../ui/dr-ui/src/settings_ui.rs#L542), [`ui/dr-ui/ui/app.slint:1610`](../../ui/dr-ui/ui/app.slint#L1610), [`ui/dr-ui/ui/app.slint:3163`](../../ui/dr-ui/ui/app.slint#L3163), [`ui/dr-ui/ui/app.slint:541`](../../ui/dr-ui/ui/app.slint#L541), [`ui/dr-ui/ui/app.slint:549`](../../ui/dr-ui/ui/app.slint#L549), [`ui/dr-ui/ui/app.slint:555`](../../ui/dr-ui/ui/app.slint#L555), [`ui/dr-ui/ui/collections.slint:1037`](../../ui/dr-ui/ui/collections.slint#L1037), [`ui/dr-ui/ui/collections.slint:1048`](../../ui/dr-ui/ui/collections.slint#L1048), [`ui/dr-ui/ui/collections.slint:1127`](../../ui/dr-ui/ui/collections.slint#L1127), [`ui/dr-ui/ui/collections.slint:272`](../../ui/dr-ui/ui/collections.slint#L272), [`ui/dr-ui/ui/collections.slint:424`](../../ui/dr-ui/ui/collections.slint#L424), [`ui/dr-ui/ui/collections.slint:433`](../../ui/dr-ui/ui/collections.slint#L433), [`ui/dr-ui/ui/collections.slint:436`](../../ui/dr-ui/ui/collections.slint#L436), [`ui/dr-ui/ui/collections.slint:482`](../../ui/dr-ui/ui/collections.slint#L482), [`ui/dr-ui/ui/collections.slint:52`](../../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:720`](../../ui/dr-ui/ui/collections.slint#L720), [`ui/dr-ui/ui/collections.slint:877`](../../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/collections.slint:91`](../../ui/dr-ui/ui/collections.slint#L91), [`ui/dr-ui/ui/icons.slint:262`](../../ui/dr-ui/ui/icons.slint#L262), [`ui/dr-ui/ui/settings.slint:114`](../../ui/dr-ui/ui/settings.slint#L114), [`ui/dr-ui/ui/settings.slint:656`](../../ui/dr-ui/ui/settings.slint#L656) | | FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1711`](../../ui/dr-ui/src/library_ui.rs#L1711), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | | FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:172`](../../core/dr-sync-nextcloud/src/desktop_client.rs#L172), [`core/dr-sync-nextcloud/src/desktop_client.rs:35`](../../core/dr-sync-nextcloud/src/desktop_client.rs#L35), [`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:122`](../../core/dr-types/src/lib.rs#L122), [`core/dr-types/src/lib.rs:204`](../../core/dr-types/src/lib.rs#L204), [`ui/dr-ui/src/activity.rs:1`](../../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:4083`](../../ui/dr-ui/src/collections_ui.rs#L4083), [`ui/dr-ui/src/collections_ui.rs:785`](../../ui/dr-ui/src/collections_ui.rs#L785), [`ui/dr-ui/src/collections_ui.rs:856`](../../ui/dr-ui/src/collections_ui.rs#L856), [`ui/dr-ui/src/derived_sync.rs:1042`](../../ui/dr-ui/src/derived_sync.rs#L1042), [`ui/dr-ui/src/derived_sync.rs:642`](../../ui/dr-ui/src/derived_sync.rs#L642), [`ui/dr-ui/src/library/sidecar.rs:531`](../../ui/dr-ui/src/library/sidecar.rs#L531), [`ui/dr-ui/src/library/sweep.rs:677`](../../ui/dr-ui/src/library/sweep.rs#L677), [`ui/dr-ui/src/library/thumbnails_fetch.rs:215`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L215), [`ui/dr-ui/src/library/thumbnails_fetch.rs:305`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L305), [`ui/dr-ui/src/library_ui.rs:1482`](../../ui/dr-ui/src/library_ui.rs#L1482), [`ui/dr-ui/src/library_ui.rs:1555`](../../ui/dr-ui/src/library_ui.rs#L1555), [`ui/dr-ui/src/library_ui.rs:1754`](../../ui/dr-ui/src/library_ui.rs#L1754), [`ui/dr-ui/src/remote.rs:40`](../../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/src/repairs.rs:1140`](../../ui/dr-ui/src/repairs.rs#L1140), [`ui/dr-ui/ui/collections.slint:272`](../../ui/dr-ui/ui/collections.slint#L272), [`ui/dr-ui/ui/collections.slint:877`](../../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/icons.slint:262`](../../ui/dr-ui/ui/icons.slint#L262) | | FR-NC-6d | [`core/dr-sync-folder/src/borrow.rs:1`](../../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:1`](../../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/vfs.rs:1`](../../core/dr-sync-folder/src/vfs.rs#L1) | | FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:120`](../../core/dr-sync-nextcloud/src/lib.rs#L120), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/sweep.rs:557`](../../ui/dr-ui/src/library/sweep.rs#L557), [`ui/dr-ui/src/library_ui.rs:4515`](../../ui/dr-ui/src/library_ui.rs#L4515), [`ui/dr-ui/ui/settings.slint:435`](../../ui/dr-ui/ui/settings.slint#L435) | -| 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:377`](../../core/dr-types/src/settings.rs#L377), [`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:1559`](../../ui/dr-ui/src/lib.rs#L1559), [`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:1559`](../../ui/dr-ui/src/lib.rs#L1559) | -| FR-NC-8 | [`core/dr-catalog/src/rating.rs:204`](../../core/dr-catalog/src/rating.rs#L204), [`core/dr-catalog/src/rating.rs:66`](../../core/dr-catalog/src/rating.rs#L66), [`core/dr-catalog/src/rating.rs:929`](../../core/dr-catalog/src/rating.rs#L929), [`core/dr-catalog/src/schema.rs:327`](../../core/dr-catalog/src/schema.rs#L327), [`core/dr-pipeline/src/sidecar.rs:137`](../../core/dr-pipeline/src/sidecar.rs#L137), [`core/dr-pipeline/src/sidecar.rs:2991`](../../core/dr-pipeline/src/sidecar.rs#L2991), [`core/dr-pipeline/src/sidecar.rs:660`](../../core/dr-pipeline/src/sidecar.rs#L660), [`core/dr-pipeline/src/sidecar.rs:735`](../../core/dr-pipeline/src/sidecar.rs#L735), [`core/dr-pipeline/src/sidecar.rs:96`](../../core/dr-pipeline/src/sidecar.rs#L96), [`ui/dr-ui/src/lib.rs:2457`](../../ui/dr-ui/src/lib.rs#L2457), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:271`](../../ui/dr-ui/src/library/sidecar.rs#L271), [`ui/dr-ui/src/library/sidecar.rs:573`](../../ui/dr-ui/src/library/sidecar.rs#L573), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui.rs:630`](../../ui/dr-ui/src/library_ui.rs#L630), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336) | +| 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:377`](../../core/dr-types/src/settings.rs#L377), [`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:1784`](../../ui/dr-ui/src/lib.rs#L1784), [`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:1784`](../../ui/dr-ui/src/lib.rs#L1784) | +| FR-NC-8 | [`core/dr-catalog/src/rating.rs:204`](../../core/dr-catalog/src/rating.rs#L204), [`core/dr-catalog/src/rating.rs:66`](../../core/dr-catalog/src/rating.rs#L66), [`core/dr-catalog/src/rating.rs:929`](../../core/dr-catalog/src/rating.rs#L929), [`core/dr-catalog/src/schema.rs:327`](../../core/dr-catalog/src/schema.rs#L327), [`core/dr-pipeline/src/sidecar.rs:137`](../../core/dr-pipeline/src/sidecar.rs#L137), [`core/dr-pipeline/src/sidecar.rs:2991`](../../core/dr-pipeline/src/sidecar.rs#L2991), [`core/dr-pipeline/src/sidecar.rs:660`](../../core/dr-pipeline/src/sidecar.rs#L660), [`core/dr-pipeline/src/sidecar.rs:735`](../../core/dr-pipeline/src/sidecar.rs#L735), [`core/dr-pipeline/src/sidecar.rs:96`](../../core/dr-pipeline/src/sidecar.rs#L96), [`ui/dr-ui/src/lib.rs:2685`](../../ui/dr-ui/src/lib.rs#L2685), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:271`](../../ui/dr-ui/src/library/sidecar.rs#L271), [`ui/dr-ui/src/library/sidecar.rs:573`](../../ui/dr-ui/src/library/sidecar.rs#L573), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui.rs:630`](../../ui/dr-ui/src/library_ui.rs#L630), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336) | | FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:204`](../../core/dr-catalog/src/rating.rs#L204), [`core/dr-catalog/src/rating.rs:66`](../../core/dr-catalog/src/rating.rs#L66), [`core/dr-catalog/src/rating.rs:929`](../../core/dr-catalog/src/rating.rs#L929), [`core/dr-catalog/src/schema.rs:1125`](../../core/dr-catalog/src/schema.rs#L1125), [`core/dr-catalog/src/schema.rs:327`](../../core/dr-catalog/src/schema.rs#L327), [`core/dr-catalog/src/schema.rs:740`](../../core/dr-catalog/src/schema.rs#L740), [`core/dr-catalog/src/sync.rs:1`](../../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:192`](../../core/dr-pipeline/src/sidecar.rs#L192), [`core/dr-pipeline/src/sidecar.rs:219`](../../core/dr-pipeline/src/sidecar.rs#L219), [`core/dr-pipeline/src/sidecar.rs:2520`](../../core/dr-pipeline/src/sidecar.rs#L2520), [`core/dr-pipeline/src/sidecar.rs:2757`](../../core/dr-pipeline/src/sidecar.rs#L2757), [`core/dr-pipeline/src/sidecar.rs:2991`](../../core/dr-pipeline/src/sidecar.rs#L2991), [`core/dr-pipeline/src/sidecar.rs:395`](../../core/dr-pipeline/src/sidecar.rs#L395), [`core/dr-pipeline/src/sidecar.rs:493`](../../core/dr-pipeline/src/sidecar.rs#L493), [`core/dr-pipeline/src/sidecar.rs:660`](../../core/dr-pipeline/src/sidecar.rs#L660), [`core/dr-pipeline/src/sidecar.rs:710`](../../core/dr-pipeline/src/sidecar.rs#L710), [`core/dr-pipeline/src/sidecar.rs:735`](../../core/dr-pipeline/src/sidecar.rs#L735), [`core/dr-pipeline/src/spot.rs:245`](../../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/mask_sidecar.rs:1075`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1075), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:139`](../../core/dr-sync-nextcloud/src/lib.rs#L139), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`core/dr-xmp/src/lib.rs:733`](../../core/dr-xmp/src/lib.rs#L733), [`core/dr-xmp/src/lib.rs:746`](../../core/dr-xmp/src/lib.rs#L746), [`ui/dr-ui/src/derived_sync.rs:1070`](../../ui/dr-ui/src/derived_sync.rs#L1070), [`ui/dr-ui/src/derived_sync.rs:613`](../../ui/dr-ui/src/derived_sync.rs#L613), [`ui/dr-ui/src/derived_sync.rs:642`](../../ui/dr-ui/src/derived_sync.rs#L642), [`ui/dr-ui/src/derived_sync.rs:675`](../../ui/dr-ui/src/derived_sync.rs#L675), [`ui/dr-ui/src/derived_sync.rs:821`](../../ui/dr-ui/src/derived_sync.rs#L821), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:271`](../../ui/dr-ui/src/library/sidecar.rs#L271), [`ui/dr-ui/src/library/sidecar.rs:343`](../../ui/dr-ui/src/library/sidecar.rs#L343), [`ui/dr-ui/src/library/sidecar.rs:427`](../../ui/dr-ui/src/library/sidecar.rs#L427), [`ui/dr-ui/src/library/sidecar.rs:573`](../../ui/dr-ui/src/library/sidecar.rs#L573), [`ui/dr-ui/src/library/sidecar.rs:593`](../../ui/dr-ui/src/library/sidecar.rs#L593), [`ui/dr-ui/src/library/thumbnails_fetch.rs:504`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L504), [`ui/dr-ui/src/library_ui.rs:1339`](../../ui/dr-ui/src/library_ui.rs#L1339), [`ui/dr-ui/src/presets.rs:288`](../../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../../ui/dr-ui/src/presets.rs#L336), [`ui/dr-ui/src/xmp_sync.rs:1`](../../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/src/xmp_sync.rs:287`](../../ui/dr-ui/src/xmp_sync.rs#L287) | | FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:1022`](../../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:435`](../../core/dr-catalog/src/walk.rs#L435), [`core/dr-sync-folder/src/lib.rs:242`](../../core/dr-sync-folder/src/lib.rs#L242), [`core/dr-sync-folder/src/tests.rs:51`](../../core/dr-sync-folder/src/tests.rs#L51), [`core/dr-sync/src/error.rs:113`](../../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/error.rs:195`](../../core/dr-sync/src/error.rs#L195), [`core/dr-sync/src/scan.rs:211`](../../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../../core/dr-sync/src/scan.rs#L519), [`core/dr-sync/src/scan.rs:545`](../../core/dr-sync/src/scan.rs#L545), [`core/dr-sync/src/scan.rs:574`](../../core/dr-sync/src/scan.rs#L574), [`ui/dr-ui/src/library/scan.rs:126`](../../ui/dr-ui/src/library/scan.rs#L126), [`ui/dr-ui/src/library/scan.rs:179`](../../ui/dr-ui/src/library/scan.rs#L179), [`ui/dr-ui/src/library/scan.rs:210`](../../ui/dr-ui/src/library/scan.rs#L210), [`ui/dr-ui/src/library/scan.rs:605`](../../ui/dr-ui/src/library/scan.rs#L605), [`ui/dr-ui/src/library_ui.rs:1310`](../../ui/dr-ui/src/library_ui.rs#L1310), [`ui/dr-ui/src/library_ui.rs:1406`](../../ui/dr-ui/src/library_ui.rs#L1406), [`ui/dr-ui/src/library_ui.rs:2005`](../../ui/dr-ui/src/library_ui.rs#L2005), [`ui/dr-ui/src/library_ui.rs:328`](../../ui/dr-ui/src/library_ui.rs#L328), [`ui/dr-ui/src/library_ui.rs:573`](../../ui/dr-ui/src/library_ui.rs#L573) | | FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/runner.rs:1`](../../core/dr-catalog/src/runner.rs#L1) | | FR-PLAT-AND-4 | [`core/dr-catalog/src/runner.rs:1`](../../core/dr-catalog/src/runner.rs#L1) | -| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/detail.rs:161`](../../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop/render.rs:519`](../../ui/dr-ui/src/develop/render.rs#L519), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:1377`](../../ui/dr-ui/src/lib.rs#L1377), [`ui/dr-ui/src/lib.rs:1862`](../../ui/dr-ui/src/lib.rs#L1862), [`ui/dr-ui/src/memory.rs:163`](../../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | +| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/detail.rs:161`](../../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop/render.rs:519`](../../ui/dr-ui/src/develop/render.rs#L519), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:1217`](../../ui/dr-ui/src/lib.rs#L1217), [`ui/dr-ui/src/lib.rs:1599`](../../ui/dr-ui/src/lib.rs#L1599), [`ui/dr-ui/src/memory.rs:163`](../../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | | FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:441`](../../apps/darkroom-android/src/lib.rs#L441) | -| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:1166`](../../ui/dr-ui/src/lib.rs#L1166), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1) | +| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:1385`](../../ui/dr-ui/src/lib.rs#L1385), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../../platform/dr-plat/src/display/x11.rs#L1) | | FR-PLAT-WIN-1 | [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1) | | FR-PLAT-WIN-2 | [`apps/darkroom-desktop/build.rs:1`](../../apps/darkroom-desktop/build.rs#L1), [`apps/darkroom-desktop/src/main.rs:6`](../../apps/darkroom-desktop/src/main.rs#L6), [`ui/dr-ui/src/launch_ui.rs:820`](../../ui/dr-ui/src/launch_ui.rs#L820) | | FR-PLAT-WIN-3 | [`apps/darkroom-desktop/src/main.rs:19`](../../apps/darkroom-desktop/src/main.rs#L19) | | FR-RAW-1 | [`core/dr-decode/src/lib.rs:259`](../../core/dr-decode/src/lib.rs#L259), [`core/dr-types/src/lib.rs:132`](../../core/dr-types/src/lib.rs#L132), [`core/dr-types/src/lib.rs:203`](../../core/dr-types/src/lib.rs#L203) | | FR-RAW-3 | [`core/dr-decode/src/lib.rs:155`](../../core/dr-decode/src/lib.rs#L155), [`core/dr-decode/src/lib.rs:546`](../../core/dr-decode/src/lib.rs#L546), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435) | -| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30), [`ui/dr-ui/src/lib.rs:268`](../../ui/dr-ui/src/lib.rs#L268) | +| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30), [`ui/dr-ui/src/lib.rs:269`](../../ui/dr-ui/src/lib.rs#L269) | | FR-RAW-5 | [`core/dr-decode/src/lib.rs:183`](../../core/dr-decode/src/lib.rs#L183), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:734`](../../core/dr-gpu/src/demosaic.rs#L734), [`core/dr-gpu/src/demosaic.rs:813`](../../core/dr-gpu/src/demosaic.rs#L813), [`core/dr-gpu/src/demosaic.rs:937`](../../core/dr-gpu/src/demosaic.rs#L937) | -| FR-UI-1 | [`core/dr-types/src/settings.rs:1377`](../../core/dr-types/src/settings.rs#L1377), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`core/dr-types/src/settings.rs:512`](../../core/dr-types/src/settings.rs#L512), [`ui/dr-ui/src/lib.rs:108`](../../ui/dr-ui/src/lib.rs#L108), [`ui/dr-ui/src/lib.rs:123`](../../ui/dr-ui/src/lib.rs#L123), [`ui/dr-ui/src/lib.rs:145`](../../ui/dr-ui/src/lib.rs#L145), [`ui/dr-ui/src/lib.rs:2711`](../../ui/dr-ui/src/lib.rs#L2711), [`ui/dr-ui/src/lib.rs:4043`](../../ui/dr-ui/src/lib.rs#L4043), [`ui/dr-ui/src/lib.rs:4062`](../../ui/dr-ui/src/lib.rs#L4062), [`ui/dr-ui/src/lib.rs:4089`](../../ui/dr-ui/src/lib.rs#L4089), [`ui/dr-ui/src/lib.rs:4140`](../../ui/dr-ui/src/lib.rs#L4140), [`ui/dr-ui/src/lib.rs:4173`](../../ui/dr-ui/src/lib.rs#L4173), [`ui/dr-ui/src/masks_ui.rs:1537`](../../ui/dr-ui/src/masks_ui.rs#L1537), [`ui/dr-ui/src/settings_ui.rs:179`](../../ui/dr-ui/src/settings_ui.rs#L179), [`ui/dr-ui/ui/adjust.slint:974`](../../ui/dr-ui/ui/adjust.slint#L974), [`ui/dr-ui/ui/app.slint:1010`](../../ui/dr-ui/ui/app.slint#L1010), [`ui/dr-ui/ui/app.slint:2842`](../../ui/dr-ui/ui/app.slint#L2842), [`ui/dr-ui/ui/app.slint:906`](../../ui/dr-ui/ui/app.slint#L906), [`ui/dr-ui/ui/identity.slint:246`](../../ui/dr-ui/ui/identity.slint#L246), [`ui/dr-ui/ui/library.slint:1076`](../../ui/dr-ui/ui/library.slint#L1076), [`ui/dr-ui/ui/settings.slint:122`](../../ui/dr-ui/ui/settings.slint#L122), [`ui/dr-ui/ui/settings.slint:681`](../../ui/dr-ui/ui/settings.slint#L681), [`ui/dr-ui/ui/toolrail.slint:156`](../../ui/dr-ui/ui/toolrail.slint#L156), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89) | -| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:116`](../../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:1197`](../../ui/dr-ui/src/collections_ui.rs#L1197), [`ui/dr-ui/src/collections_ui.rs:1207`](../../ui/dr-ui/src/collections_ui.rs#L1207), [`ui/dr-ui/src/collections_ui.rs:154`](../../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1704`](../../ui/dr-ui/src/collections_ui.rs#L1704), [`ui/dr-ui/src/collections_ui.rs:1721`](../../ui/dr-ui/src/collections_ui.rs#L1721), [`ui/dr-ui/src/collections_ui.rs:1973`](../../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:2701`](../../ui/dr-ui/src/collections_ui.rs#L2701), [`ui/dr-ui/src/collections_ui.rs:3596`](../../ui/dr-ui/src/collections_ui.rs#L3596), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:108`](../../ui/dr-ui/src/lib.rs#L108), [`ui/dr-ui/src/library_ui.rs:6822`](../../ui/dr-ui/src/library_ui.rs#L6822), [`ui/dr-ui/src/library_ui.rs:8316`](../../ui/dr-ui/src/library_ui.rs#L8316), [`ui/dr-ui/ui/app.slint:594`](../../ui/dr-ui/ui/app.slint#L594), [`ui/dr-ui/ui/app.slint:608`](../../ui/dr-ui/ui/app.slint#L608), [`ui/dr-ui/ui/app.slint:71`](../../ui/dr-ui/ui/app.slint#L71), [`ui/dr-ui/ui/app.slint:994`](../../ui/dr-ui/ui/app.slint#L994), [`ui/dr-ui/ui/controls.slint:626`](../../ui/dr-ui/ui/controls.slint#L626), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1287`](../../ui/dr-ui/ui/library.slint#L1287), [`ui/dr-ui/ui/library.slint:1294`](../../ui/dr-ui/ui/library.slint#L1294), [`ui/dr-ui/ui/library.slint:1391`](../../ui/dr-ui/ui/library.slint#L1391), [`ui/dr-ui/ui/library.slint:2621`](../../ui/dr-ui/ui/library.slint#L2621), [`ui/dr-ui/ui/library.slint:4370`](../../ui/dr-ui/ui/library.slint#L4370), [`ui/dr-ui/ui/library.slint:965`](../../ui/dr-ui/ui/library.slint#L965), [`ui/dr-ui/ui/library.slint:998`](../../ui/dr-ui/ui/library.slint#L998), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:136`](../../ui/dr-ui/ui/settings.slint#L136) | +| FR-UI-1 | [`core/dr-types/src/settings.rs:1377`](../../core/dr-types/src/settings.rs#L1377), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`core/dr-types/src/settings.rs:512`](../../core/dr-types/src/settings.rs#L512), [`ui/dr-ui/src/develop_ui.rs:204`](../../ui/dr-ui/src/develop_ui.rs#L204), [`ui/dr-ui/src/lib.rs:109`](../../ui/dr-ui/src/lib.rs#L109), [`ui/dr-ui/src/lib.rs:124`](../../ui/dr-ui/src/lib.rs#L124), [`ui/dr-ui/src/lib.rs:146`](../../ui/dr-ui/src/lib.rs#L146), [`ui/dr-ui/src/lib.rs:3195`](../../ui/dr-ui/src/lib.rs#L3195), [`ui/dr-ui/src/lib.rs:3214`](../../ui/dr-ui/src/lib.rs#L3214), [`ui/dr-ui/src/lib.rs:3241`](../../ui/dr-ui/src/lib.rs#L3241), [`ui/dr-ui/src/lib.rs:3292`](../../ui/dr-ui/src/lib.rs#L3292), [`ui/dr-ui/src/lib.rs:3325`](../../ui/dr-ui/src/lib.rs#L3325), [`ui/dr-ui/src/masks_ui.rs:1537`](../../ui/dr-ui/src/masks_ui.rs#L1537), [`ui/dr-ui/src/settings_ui.rs:179`](../../ui/dr-ui/src/settings_ui.rs#L179), [`ui/dr-ui/ui/adjust.slint:974`](../../ui/dr-ui/ui/adjust.slint#L974), [`ui/dr-ui/ui/app.slint:1010`](../../ui/dr-ui/ui/app.slint#L1010), [`ui/dr-ui/ui/app.slint:2842`](../../ui/dr-ui/ui/app.slint#L2842), [`ui/dr-ui/ui/app.slint:906`](../../ui/dr-ui/ui/app.slint#L906), [`ui/dr-ui/ui/identity.slint:246`](../../ui/dr-ui/ui/identity.slint#L246), [`ui/dr-ui/ui/library.slint:1076`](../../ui/dr-ui/ui/library.slint#L1076), [`ui/dr-ui/ui/settings.slint:122`](../../ui/dr-ui/ui/settings.slint#L122), [`ui/dr-ui/ui/settings.slint:681`](../../ui/dr-ui/ui/settings.slint#L681), [`ui/dr-ui/ui/toolrail.slint:156`](../../ui/dr-ui/ui/toolrail.slint#L156), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89) | +| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:116`](../../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:1197`](../../ui/dr-ui/src/collections_ui.rs#L1197), [`ui/dr-ui/src/collections_ui.rs:1207`](../../ui/dr-ui/src/collections_ui.rs#L1207), [`ui/dr-ui/src/collections_ui.rs:154`](../../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1704`](../../ui/dr-ui/src/collections_ui.rs#L1704), [`ui/dr-ui/src/collections_ui.rs:1721`](../../ui/dr-ui/src/collections_ui.rs#L1721), [`ui/dr-ui/src/collections_ui.rs:1973`](../../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:2701`](../../ui/dr-ui/src/collections_ui.rs#L2701), [`ui/dr-ui/src/collections_ui.rs:3596`](../../ui/dr-ui/src/collections_ui.rs#L3596), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:109`](../../ui/dr-ui/src/lib.rs#L109), [`ui/dr-ui/src/library_ui.rs:6822`](../../ui/dr-ui/src/library_ui.rs#L6822), [`ui/dr-ui/src/library_ui.rs:8316`](../../ui/dr-ui/src/library_ui.rs#L8316), [`ui/dr-ui/ui/app.slint:594`](../../ui/dr-ui/ui/app.slint#L594), [`ui/dr-ui/ui/app.slint:608`](../../ui/dr-ui/ui/app.slint#L608), [`ui/dr-ui/ui/app.slint:71`](../../ui/dr-ui/ui/app.slint#L71), [`ui/dr-ui/ui/app.slint:994`](../../ui/dr-ui/ui/app.slint#L994), [`ui/dr-ui/ui/controls.slint:626`](../../ui/dr-ui/ui/controls.slint#L626), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1287`](../../ui/dr-ui/ui/library.slint#L1287), [`ui/dr-ui/ui/library.slint:1294`](../../ui/dr-ui/ui/library.slint#L1294), [`ui/dr-ui/ui/library.slint:1391`](../../ui/dr-ui/ui/library.slint#L1391), [`ui/dr-ui/ui/library.slint:2621`](../../ui/dr-ui/ui/library.slint#L2621), [`ui/dr-ui/ui/library.slint:4370`](../../ui/dr-ui/ui/library.slint#L4370), [`ui/dr-ui/ui/library.slint:965`](../../ui/dr-ui/ui/library.slint#L965), [`ui/dr-ui/ui/library.slint:998`](../../ui/dr-ui/ui/library.slint#L998), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:136`](../../ui/dr-ui/ui/settings.slint#L136) | | FR-UI-3 | [`ui/dr-ui/src/collections_ui.rs:209`](../../ui/dr-ui/src/collections_ui.rs#L209), [`ui/dr-ui/src/collections_ui.rs:2701`](../../ui/dr-ui/src/collections_ui.rs#L2701), [`ui/dr-ui/src/collections_ui.rs:414`](../../ui/dr-ui/src/collections_ui.rs#L414), [`ui/dr-ui/src/collections_ui.rs:427`](../../ui/dr-ui/src/collections_ui.rs#L427), [`ui/dr-ui/src/develop/masks.rs:360`](../../ui/dr-ui/src/develop/masks.rs#L360), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/library_ui.rs:5778`](../../ui/dr-ui/src/library_ui.rs#L5778), [`ui/dr-ui/src/masks_ui.rs:1629`](../../ui/dr-ui/src/masks_ui.rs#L1629), [`ui/dr-ui/src/masks_ui.rs:1651`](../../ui/dr-ui/src/masks_ui.rs#L1651), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2385`](../../ui/dr-ui/ui/app.slint#L2385), [`ui/dr-ui/ui/collections.slint:1127`](../../ui/dr-ui/ui/collections.slint#L1127), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:75`](../../ui/dr-ui/ui/collections.slint#L75), [`ui/dr-ui/ui/collections.slint:877`](../../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/masks.slint:1216`](../../ui/dr-ui/ui/masks.slint#L1216), [`ui/dr-ui/ui/widgets.slint:71`](../../ui/dr-ui/ui/widgets.slint#L71) | -| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`ui/dr-ui/src/collections_ui.rs:116`](../../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:117`](../../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1197`](../../ui/dr-ui/src/collections_ui.rs#L1197), [`ui/dr-ui/src/collections_ui.rs:1207`](../../ui/dr-ui/src/collections_ui.rs#L1207), [`ui/dr-ui/src/collections_ui.rs:126`](../../ui/dr-ui/src/collections_ui.rs#L126), [`ui/dr-ui/src/collections_ui.rs:154`](../../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1704`](../../ui/dr-ui/src/collections_ui.rs#L1704), [`ui/dr-ui/src/collections_ui.rs:1721`](../../ui/dr-ui/src/collections_ui.rs#L1721), [`ui/dr-ui/src/collections_ui.rs:1837`](../../ui/dr-ui/src/collections_ui.rs#L1837), [`ui/dr-ui/src/collections_ui.rs:1973`](../../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:2000`](../../ui/dr-ui/src/collections_ui.rs#L2000), [`ui/dr-ui/src/collections_ui.rs:209`](../../ui/dr-ui/src/collections_ui.rs#L209), [`ui/dr-ui/src/collections_ui.rs:2116`](../../ui/dr-ui/src/collections_ui.rs#L2116), [`ui/dr-ui/src/collections_ui.rs:2701`](../../ui/dr-ui/src/collections_ui.rs#L2701), [`ui/dr-ui/src/collections_ui.rs:2755`](../../ui/dr-ui/src/collections_ui.rs#L2755), [`ui/dr-ui/src/collections_ui.rs:3208`](../../ui/dr-ui/src/collections_ui.rs#L3208), [`ui/dr-ui/src/collections_ui.rs:3596`](../../ui/dr-ui/src/collections_ui.rs#L3596), [`ui/dr-ui/src/collections_ui.rs:3633`](../../ui/dr-ui/src/collections_ui.rs#L3633), [`ui/dr-ui/src/collections_ui.rs:414`](../../ui/dr-ui/src/collections_ui.rs#L414), [`ui/dr-ui/src/collections_ui.rs:427`](../../ui/dr-ui/src/collections_ui.rs#L427), [`ui/dr-ui/src/collections_ui.rs:514`](../../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:718`](../../ui/dr-ui/src/collections_ui.rs#L718), [`ui/dr-ui/src/collections_ui.rs:738`](../../ui/dr-ui/src/collections_ui.rs#L738), [`ui/dr-ui/src/develop/framing.rs:444`](../../ui/dr-ui/src/develop/framing.rs#L444), [`ui/dr-ui/src/develop/framing.rs:469`](../../ui/dr-ui/src/develop/framing.rs#L469), [`ui/dr-ui/src/develop/framing.rs:480`](../../ui/dr-ui/src/develop/framing.rs#L480), [`ui/dr-ui/src/develop/framing.rs:494`](../../ui/dr-ui/src/develop/framing.rs#L494), [`ui/dr-ui/src/develop/framing.rs:668`](../../ui/dr-ui/src/develop/framing.rs#L668), [`ui/dr-ui/src/develop/framing.rs:699`](../../ui/dr-ui/src/develop/framing.rs#L699), [`ui/dr-ui/src/develop/framing.rs:720`](../../ui/dr-ui/src/develop/framing.rs#L720), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1350`](../../ui/dr-ui/src/lib.rs#L1350), [`ui/dr-ui/src/lib.rs:1944`](../../ui/dr-ui/src/lib.rs#L1944), [`ui/dr-ui/src/lib.rs:2368`](../../ui/dr-ui/src/lib.rs#L2368), [`ui/dr-ui/src/lib.rs:2558`](../../ui/dr-ui/src/lib.rs#L2558), [`ui/dr-ui/src/lib.rs:2618`](../../ui/dr-ui/src/lib.rs#L2618), [`ui/dr-ui/src/lib.rs:3284`](../../ui/dr-ui/src/lib.rs#L3284), [`ui/dr-ui/src/lib.rs:3853`](../../ui/dr-ui/src/lib.rs#L3853), [`ui/dr-ui/src/lib.rs:4277`](../../ui/dr-ui/src/lib.rs#L4277), [`ui/dr-ui/src/lib.rs:460`](../../ui/dr-ui/src/lib.rs#L460), [`ui/dr-ui/src/lib.rs:489`](../../ui/dr-ui/src/lib.rs#L489), [`ui/dr-ui/src/library/thumbnails_fetch.rs:711`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L711), [`ui/dr-ui/src/library/thumbnails_fetch.rs:726`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L726), [`ui/dr-ui/src/library_ui.rs:387`](../../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:527`](../../ui/dr-ui/src/library_ui.rs#L527), [`ui/dr-ui/src/library_ui.rs:5778`](../../ui/dr-ui/src/library_ui.rs#L5778), [`ui/dr-ui/src/library_ui.rs:5790`](../../ui/dr-ui/src/library_ui.rs#L5790), [`ui/dr-ui/src/library_ui.rs:5860`](../../ui/dr-ui/src/library_ui.rs#L5860), [`ui/dr-ui/src/library_ui.rs:5992`](../../ui/dr-ui/src/library_ui.rs#L5992), [`ui/dr-ui/src/library_ui.rs:6020`](../../ui/dr-ui/src/library_ui.rs#L6020), [`ui/dr-ui/src/library_ui.rs:604`](../../ui/dr-ui/src/library_ui.rs#L604), [`ui/dr-ui/src/library_ui.rs:6963`](../../ui/dr-ui/src/library_ui.rs#L6963), [`ui/dr-ui/src/settings_ui.rs:157`](../../ui/dr-ui/src/settings_ui.rs#L157), [`ui/dr-ui/src/settings_ui.rs:542`](../../ui/dr-ui/src/settings_ui.rs#L542), [`ui/dr-ui/ui/app.slint:171`](../../ui/dr-ui/ui/app.slint#L171), [`ui/dr-ui/ui/app.slint:2080`](../../ui/dr-ui/ui/app.slint#L2080), [`ui/dr-ui/ui/app.slint:2202`](../../ui/dr-ui/ui/app.slint#L2202), [`ui/dr-ui/ui/app.slint:2609`](../../ui/dr-ui/ui/app.slint#L2609), [`ui/dr-ui/ui/app.slint:2742`](../../ui/dr-ui/ui/app.slint#L2742), [`ui/dr-ui/ui/app.slint:3213`](../../ui/dr-ui/ui/app.slint#L3213), [`ui/dr-ui/ui/app.slint:570`](../../ui/dr-ui/ui/app.slint#L570), [`ui/dr-ui/ui/app.slint:599`](../../ui/dr-ui/ui/app.slint#L599), [`ui/dr-ui/ui/app.slint:608`](../../ui/dr-ui/ui/app.slint#L608), [`ui/dr-ui/ui/app.slint:712`](../../ui/dr-ui/ui/app.slint#L712), [`ui/dr-ui/ui/collections.slint:75`](../../ui/dr-ui/ui/collections.slint#L75), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1053`](../../ui/dr-ui/ui/library.slint#L1053), [`ui/dr-ui/ui/library.slint:1262`](../../ui/dr-ui/ui/library.slint#L1262), [`ui/dr-ui/ui/library.slint:1287`](../../ui/dr-ui/ui/library.slint#L1287), [`ui/dr-ui/ui/library.slint:1294`](../../ui/dr-ui/ui/library.slint#L1294), [`ui/dr-ui/ui/library.slint:1327`](../../ui/dr-ui/ui/library.slint#L1327), [`ui/dr-ui/ui/library.slint:1384`](../../ui/dr-ui/ui/library.slint#L1384), [`ui/dr-ui/ui/library.slint:1391`](../../ui/dr-ui/ui/library.slint#L1391), [`ui/dr-ui/ui/library.slint:2621`](../../ui/dr-ui/ui/library.slint#L2621), [`ui/dr-ui/ui/library.slint:4224`](../../ui/dr-ui/ui/library.slint#L4224), [`ui/dr-ui/ui/library.slint:4311`](../../ui/dr-ui/ui/library.slint#L4311), [`ui/dr-ui/ui/library.slint:4370`](../../ui/dr-ui/ui/library.slint#L4370), [`ui/dr-ui/ui/library.slint:965`](../../ui/dr-ui/ui/library.slint#L965), [`ui/dr-ui/ui/library.slint:992`](../../ui/dr-ui/ui/library.slint#L992), [`ui/dr-ui/ui/library.slint:998`](../../ui/dr-ui/ui/library.slint#L998), [`ui/dr-ui/ui/settings.slint:114`](../../ui/dr-ui/ui/settings.slint#L114), [`ui/dr-ui/ui/settings.slint:656`](../../ui/dr-ui/ui/settings.slint#L656) | -| 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:3329`](../../ui/dr-ui/src/lib.rs#L3329), [`ui/dr-ui/src/lib.rs:4237`](../../ui/dr-ui/src/lib.rs#L4237), [`ui/dr-ui/src/lib.rs:4428`](../../ui/dr-ui/src/lib.rs#L4428), [`ui/dr-ui/src/masks_ui.rs:1584`](../../ui/dr-ui/src/masks_ui.rs#L1584), [`ui/dr-ui/ui/app.slint:2569`](../../ui/dr-ui/ui/app.slint#L2569), [`ui/dr-ui/ui/app.slint:2609`](../../ui/dr-ui/ui/app.slint#L2609), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) | +| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`ui/dr-ui/src/collections_ui.rs:116`](../../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:117`](../../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1197`](../../ui/dr-ui/src/collections_ui.rs#L1197), [`ui/dr-ui/src/collections_ui.rs:1207`](../../ui/dr-ui/src/collections_ui.rs#L1207), [`ui/dr-ui/src/collections_ui.rs:126`](../../ui/dr-ui/src/collections_ui.rs#L126), [`ui/dr-ui/src/collections_ui.rs:154`](../../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1704`](../../ui/dr-ui/src/collections_ui.rs#L1704), [`ui/dr-ui/src/collections_ui.rs:1721`](../../ui/dr-ui/src/collections_ui.rs#L1721), [`ui/dr-ui/src/collections_ui.rs:1837`](../../ui/dr-ui/src/collections_ui.rs#L1837), [`ui/dr-ui/src/collections_ui.rs:1973`](../../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:2000`](../../ui/dr-ui/src/collections_ui.rs#L2000), [`ui/dr-ui/src/collections_ui.rs:209`](../../ui/dr-ui/src/collections_ui.rs#L209), [`ui/dr-ui/src/collections_ui.rs:2116`](../../ui/dr-ui/src/collections_ui.rs#L2116), [`ui/dr-ui/src/collections_ui.rs:2701`](../../ui/dr-ui/src/collections_ui.rs#L2701), [`ui/dr-ui/src/collections_ui.rs:2755`](../../ui/dr-ui/src/collections_ui.rs#L2755), [`ui/dr-ui/src/collections_ui.rs:3208`](../../ui/dr-ui/src/collections_ui.rs#L3208), [`ui/dr-ui/src/collections_ui.rs:3596`](../../ui/dr-ui/src/collections_ui.rs#L3596), [`ui/dr-ui/src/collections_ui.rs:3633`](../../ui/dr-ui/src/collections_ui.rs#L3633), [`ui/dr-ui/src/collections_ui.rs:414`](../../ui/dr-ui/src/collections_ui.rs#L414), [`ui/dr-ui/src/collections_ui.rs:427`](../../ui/dr-ui/src/collections_ui.rs#L427), [`ui/dr-ui/src/collections_ui.rs:514`](../../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:718`](../../ui/dr-ui/src/collections_ui.rs#L718), [`ui/dr-ui/src/collections_ui.rs:738`](../../ui/dr-ui/src/collections_ui.rs#L738), [`ui/dr-ui/src/develop/framing.rs:444`](../../ui/dr-ui/src/develop/framing.rs#L444), [`ui/dr-ui/src/develop/framing.rs:469`](../../ui/dr-ui/src/develop/framing.rs#L469), [`ui/dr-ui/src/develop/framing.rs:480`](../../ui/dr-ui/src/develop/framing.rs#L480), [`ui/dr-ui/src/develop/framing.rs:494`](../../ui/dr-ui/src/develop/framing.rs#L494), [`ui/dr-ui/src/develop/framing.rs:668`](../../ui/dr-ui/src/develop/framing.rs#L668), [`ui/dr-ui/src/develop/framing.rs:699`](../../ui/dr-ui/src/develop/framing.rs#L699), [`ui/dr-ui/src/develop/framing.rs:720`](../../ui/dr-ui/src/develop/framing.rs#L720), [`ui/dr-ui/src/develop_ui.rs:804`](../../ui/dr-ui/src/develop_ui.rs#L804), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1284`](../../ui/dr-ui/src/lib.rs#L1284), [`ui/dr-ui/src/lib.rs:1572`](../../ui/dr-ui/src/lib.rs#L1572), [`ui/dr-ui/src/lib.rs:2567`](../../ui/dr-ui/src/lib.rs#L2567), [`ui/dr-ui/src/lib.rs:2786`](../../ui/dr-ui/src/lib.rs#L2786), [`ui/dr-ui/src/lib.rs:2846`](../../ui/dr-ui/src/lib.rs#L2846), [`ui/dr-ui/src/lib.rs:3005`](../../ui/dr-ui/src/lib.rs#L3005), [`ui/dr-ui/src/lib.rs:3429`](../../ui/dr-ui/src/lib.rs#L3429), [`ui/dr-ui/src/lib.rs:461`](../../ui/dr-ui/src/lib.rs#L461), [`ui/dr-ui/src/lib.rs:490`](../../ui/dr-ui/src/lib.rs#L490), [`ui/dr-ui/src/library/thumbnails_fetch.rs:711`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L711), [`ui/dr-ui/src/library/thumbnails_fetch.rs:726`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L726), [`ui/dr-ui/src/library_ui.rs:387`](../../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:527`](../../ui/dr-ui/src/library_ui.rs#L527), [`ui/dr-ui/src/library_ui.rs:5778`](../../ui/dr-ui/src/library_ui.rs#L5778), [`ui/dr-ui/src/library_ui.rs:5790`](../../ui/dr-ui/src/library_ui.rs#L5790), [`ui/dr-ui/src/library_ui.rs:5860`](../../ui/dr-ui/src/library_ui.rs#L5860), [`ui/dr-ui/src/library_ui.rs:5992`](../../ui/dr-ui/src/library_ui.rs#L5992), [`ui/dr-ui/src/library_ui.rs:6020`](../../ui/dr-ui/src/library_ui.rs#L6020), [`ui/dr-ui/src/library_ui.rs:604`](../../ui/dr-ui/src/library_ui.rs#L604), [`ui/dr-ui/src/library_ui.rs:6963`](../../ui/dr-ui/src/library_ui.rs#L6963), [`ui/dr-ui/src/settings_ui.rs:157`](../../ui/dr-ui/src/settings_ui.rs#L157), [`ui/dr-ui/src/settings_ui.rs:542`](../../ui/dr-ui/src/settings_ui.rs#L542), [`ui/dr-ui/ui/app.slint:171`](../../ui/dr-ui/ui/app.slint#L171), [`ui/dr-ui/ui/app.slint:2080`](../../ui/dr-ui/ui/app.slint#L2080), [`ui/dr-ui/ui/app.slint:2202`](../../ui/dr-ui/ui/app.slint#L2202), [`ui/dr-ui/ui/app.slint:2609`](../../ui/dr-ui/ui/app.slint#L2609), [`ui/dr-ui/ui/app.slint:2742`](../../ui/dr-ui/ui/app.slint#L2742), [`ui/dr-ui/ui/app.slint:3213`](../../ui/dr-ui/ui/app.slint#L3213), [`ui/dr-ui/ui/app.slint:570`](../../ui/dr-ui/ui/app.slint#L570), [`ui/dr-ui/ui/app.slint:599`](../../ui/dr-ui/ui/app.slint#L599), [`ui/dr-ui/ui/app.slint:608`](../../ui/dr-ui/ui/app.slint#L608), [`ui/dr-ui/ui/app.slint:712`](../../ui/dr-ui/ui/app.slint#L712), [`ui/dr-ui/ui/collections.slint:75`](../../ui/dr-ui/ui/collections.slint#L75), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1053`](../../ui/dr-ui/ui/library.slint#L1053), [`ui/dr-ui/ui/library.slint:1262`](../../ui/dr-ui/ui/library.slint#L1262), [`ui/dr-ui/ui/library.slint:1287`](../../ui/dr-ui/ui/library.slint#L1287), [`ui/dr-ui/ui/library.slint:1294`](../../ui/dr-ui/ui/library.slint#L1294), [`ui/dr-ui/ui/library.slint:1327`](../../ui/dr-ui/ui/library.slint#L1327), [`ui/dr-ui/ui/library.slint:1384`](../../ui/dr-ui/ui/library.slint#L1384), [`ui/dr-ui/ui/library.slint:1391`](../../ui/dr-ui/ui/library.slint#L1391), [`ui/dr-ui/ui/library.slint:2621`](../../ui/dr-ui/ui/library.slint#L2621), [`ui/dr-ui/ui/library.slint:4224`](../../ui/dr-ui/ui/library.slint#L4224), [`ui/dr-ui/ui/library.slint:4311`](../../ui/dr-ui/ui/library.slint#L4311), [`ui/dr-ui/ui/library.slint:4370`](../../ui/dr-ui/ui/library.slint#L4370), [`ui/dr-ui/ui/library.slint:965`](../../ui/dr-ui/ui/library.slint#L965), [`ui/dr-ui/ui/library.slint:992`](../../ui/dr-ui/ui/library.slint#L992), [`ui/dr-ui/ui/library.slint:998`](../../ui/dr-ui/ui/library.slint#L998), [`ui/dr-ui/ui/settings.slint:114`](../../ui/dr-ui/ui/settings.slint#L114), [`ui/dr-ui/ui/settings.slint:656`](../../ui/dr-ui/ui/settings.slint#L656) | +| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/develop_ui.rs:849`](../../ui/dr-ui/src/develop_ui.rs#L849), [`ui/dr-ui/src/lib.rs:3389`](../../ui/dr-ui/src/lib.rs#L3389), [`ui/dr-ui/src/lib.rs:3580`](../../ui/dr-ui/src/lib.rs#L3580), [`ui/dr-ui/src/masks_ui.rs:1584`](../../ui/dr-ui/src/masks_ui.rs#L1584), [`ui/dr-ui/ui/app.slint:2569`](../../ui/dr-ui/ui/app.slint#L2569), [`ui/dr-ui/ui/app.slint:2609`](../../ui/dr-ui/ui/app.slint#L2609), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) | | FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../../ui/dr-ui/ui/widgets.slint#L1) | -| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:195`](../../core/dr-pipeline/src/descriptor.rs#L195), [`core/dr-pipeline/src/framing.rs:395`](../../core/dr-pipeline/src/framing.rs#L395), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/lib.rs:4062`](../../ui/dr-ui/src/lib.rs#L4062), [`ui/dr-ui/ui/adjust.slint:974`](../../ui/dr-ui/ui/adjust.slint#L974), [`ui/dr-ui/ui/app.slint:2325`](../../ui/dr-ui/ui/app.slint#L2325), [`ui/dr-ui/ui/settings.slint:681`](../../ui/dr-ui/ui/settings.slint#L681), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89) | +| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:195`](../../core/dr-pipeline/src/descriptor.rs#L195), [`core/dr-pipeline/src/framing.rs:395`](../../core/dr-pipeline/src/framing.rs#L395), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/lib.rs:3214`](../../ui/dr-ui/src/lib.rs#L3214), [`ui/dr-ui/ui/adjust.slint:974`](../../ui/dr-ui/ui/adjust.slint#L974), [`ui/dr-ui/ui/app.slint:2325`](../../ui/dr-ui/ui/app.slint#L2325), [`ui/dr-ui/ui/settings.slint:681`](../../ui/dr-ui/ui/settings.slint#L681), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89) | | FR-UI-8 | [`core/dr-catalog/src/collections.rs:654`](../../core/dr-catalog/src/collections.rs#L654), [`core/dr-catalog/src/collections.rs:675`](../../core/dr-catalog/src/collections.rs#L675), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/derived_sync.rs:216`](../../ui/dr-ui/src/derived_sync.rs#L216), [`ui/dr-ui/src/derived_sync.rs:81`](../../ui/dr-ui/src/derived_sync.rs#L81), [`ui/dr-ui/src/derived_sync.rs:894`](../../ui/dr-ui/src/derived_sync.rs#L894), [`ui/dr-ui/src/derived_sync.rs:904`](../../ui/dr-ui/src/derived_sync.rs#L904), [`ui/dr-ui/src/derived_sync.rs:986`](../../ui/dr-ui/src/derived_sync.rs#L986), [`ui/dr-ui/src/library/cells.rs:274`](../../ui/dr-ui/src/library/cells.rs#L274), [`ui/dr-ui/src/library/cells.rs:553`](../../ui/dr-ui/src/library/cells.rs#L553), [`ui/dr-ui/src/library/cells.rs:619`](../../ui/dr-ui/src/library/cells.rs#L619), [`ui/dr-ui/src/library/paths.rs:22`](../../ui/dr-ui/src/library/paths.rs#L22), [`ui/dr-ui/src/library_ui.rs:1007`](../../ui/dr-ui/src/library_ui.rs#L1007), [`ui/dr-ui/src/library_ui.rs:195`](../../ui/dr-ui/src/library_ui.rs#L195), [`ui/dr-ui/src/library_ui.rs:2192`](../../ui/dr-ui/src/library_ui.rs#L2192), [`ui/dr-ui/src/library_ui.rs:2381`](../../ui/dr-ui/src/library_ui.rs#L2381), [`ui/dr-ui/src/library_ui.rs:2398`](../../ui/dr-ui/src/library_ui.rs#L2398), [`ui/dr-ui/src/library_ui.rs:2496`](../../ui/dr-ui/src/library_ui.rs#L2496), [`ui/dr-ui/src/library_ui.rs:5811`](../../ui/dr-ui/src/library_ui.rs#L5811), [`ui/dr-ui/src/library_ui.rs:5851`](../../ui/dr-ui/src/library_ui.rs#L5851), [`ui/dr-ui/src/library_ui.rs:5880`](../../ui/dr-ui/src/library_ui.rs#L5880), [`ui/dr-ui/src/library_ui.rs:6169`](../../ui/dr-ui/src/library_ui.rs#L6169), [`ui/dr-ui/src/library_ui.rs:6405`](../../ui/dr-ui/src/library_ui.rs#L6405), [`ui/dr-ui/src/library_ui.rs:7179`](../../ui/dr-ui/src/library_ui.rs#L7179), [`ui/dr-ui/src/library_ui.rs:935`](../../ui/dr-ui/src/library_ui.rs#L935), [`ui/dr-ui/src/library_ui.rs:968`](../../ui/dr-ui/src/library_ui.rs#L968), [`ui/dr-ui/src/place.rs:1`](../../ui/dr-ui/src/place.rs#L1) | | NFR-A11Y-2 | [`ui/dr-ui/tests/ui_controls_are_accessible.rs:1`](../../ui/dr-ui/tests/ui_controls_are_accessible.rs#L1), [`ui/dr-ui/ui/adjust.slint:171`](../../ui/dr-ui/ui/adjust.slint#L171), [`ui/dr-ui/ui/app.slint:2742`](../../ui/dr-ui/ui/app.slint#L2742), [`ui/dr-ui/ui/app.slint:2780`](../../ui/dr-ui/ui/app.slint#L2780) | | NFR-A11Y-3 | [`ui/dr-ui/src/histogram.rs:356`](../../ui/dr-ui/src/histogram.rs#L356), [`ui/dr-ui/ui/histogram.slint:137`](../../ui/dr-ui/ui/histogram.slint#L137), [`ui/dr-ui/ui/library.slint:761`](../../ui/dr-ui/ui/library.slint#L761), [`ui/dr-ui/ui/library.slint:909`](../../ui/dr-ui/ui/library.slint#L909), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1) | | 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/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1) | -| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1822`](../../ui/dr-ui/src/export.rs#L1822), [`ui/dr-ui/src/export.rs:1849`](../../ui/dr-ui/src/export.rs#L1849), [`ui/dr-ui/src/export.rs:419`](../../ui/dr-ui/src/export.rs#L419), [`ui/dr-ui/src/export.rs:445`](../../ui/dr-ui/src/export.rs#L445), [`ui/dr-ui/src/lib.rs:2847`](../../ui/dr-ui/src/lib.rs#L2847), [`ui/dr-ui/ui/app.slint:1150`](../../ui/dr-ui/ui/app.slint#L1150), [`ui/dr-ui/ui/library.slint:4332`](../../ui/dr-ui/ui/library.slint#L4332) | +| NFR-ARCH-3 | [`ui/dr-ui/src/develop_ui.rs:343`](../../ui/dr-ui/src/develop_ui.rs#L343), [`ui/dr-ui/src/export.rs:1822`](../../ui/dr-ui/src/export.rs#L1822), [`ui/dr-ui/src/export.rs:1849`](../../ui/dr-ui/src/export.rs#L1849), [`ui/dr-ui/src/export.rs:419`](../../ui/dr-ui/src/export.rs#L419), [`ui/dr-ui/src/export.rs:445`](../../ui/dr-ui/src/export.rs#L445), [`ui/dr-ui/ui/app.slint:1150`](../../ui/dr-ui/ui/app.slint#L1150), [`ui/dr-ui/ui/library.slint:4332`](../../ui/dr-ui/ui/library.slint#L4332) | | 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), [`core/dr-xmp/src/lib.rs:798`](../../core/dr-xmp/src/lib.rs#L798), [`platform/dr-plat/src/storage.rs:148`](../../platform/dr-plat/src/storage.rs#L148), [`ui/dr-ui/src/export.rs:521`](../../ui/dr-ui/src/export.rs#L521) | | NFR-COMPAT-1 | [`core/dr-gpu/src/lib.rs:286`](../../core/dr-gpu/src/lib.rs#L286) | -| NFR-OPS-1 | [`core/dr-gpu/src/lib.rs:339`](../../core/dr-gpu/src/lib.rs#L339), [`platform/dr-plat/src/diagnostics.rs:1`](../../platform/dr-plat/src/diagnostics.rs#L1), [`platform/dr-plat/src/diagnostics/bundle.rs:1`](../../platform/dr-plat/src/diagnostics/bundle.rs#L1), [`platform/dr-plat/src/state.rs:1`](../../platform/dr-plat/src/state.rs#L1), [`ui/dr-ui/src/lib.rs:1241`](../../ui/dr-ui/src/lib.rs#L1241), [`ui/dr-ui/ui/app.slint:1473`](../../ui/dr-ui/ui/app.slint#L1473), [`ui/dr-ui/ui/app.slint:75`](../../ui/dr-ui/ui/app.slint#L75), [`ui/dr-ui/ui/settings.slint:1015`](../../ui/dr-ui/ui/settings.slint#L1015), [`ui/dr-ui/ui/settings.slint:154`](../../ui/dr-ui/ui/settings.slint#L154) | +| NFR-OPS-1 | [`core/dr-gpu/src/lib.rs:339`](../../core/dr-gpu/src/lib.rs#L339), [`platform/dr-plat/src/diagnostics.rs:1`](../../platform/dr-plat/src/diagnostics.rs#L1), [`platform/dr-plat/src/diagnostics/bundle.rs:1`](../../platform/dr-plat/src/diagnostics/bundle.rs#L1), [`platform/dr-plat/src/state.rs:1`](../../platform/dr-plat/src/state.rs#L1), [`ui/dr-ui/src/lib.rs:1462`](../../ui/dr-ui/src/lib.rs#L1462), [`ui/dr-ui/ui/app.slint:1473`](../../ui/dr-ui/ui/app.slint#L1473), [`ui/dr-ui/ui/app.slint:75`](../../ui/dr-ui/ui/app.slint#L75), [`ui/dr-ui/ui/settings.slint:1015`](../../ui/dr-ui/ui/settings.slint#L1015), [`ui/dr-ui/ui/settings.slint:154`](../../ui/dr-ui/ui/settings.slint#L154) | | NFR-OPS-2 | [`platform/dr-plat/src/crash.rs:1`](../../platform/dr-plat/src/crash.rs#L1) | | NFR-OPS-3 | [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1) | | 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/bench/src/catalog_open.rs:1`](../../tools/bench/src/catalog_open.rs#L1) | | NFR-P13 | [`core/dr-decode/src/preview.rs:121`](../../core/dr-decode/src/preview.rs#L121) | -| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:348`](../../ui/dr-ui/src/develop/render.rs#L348), [`ui/dr-ui/src/lib.rs:2153`](../../ui/dr-ui/src/lib.rs#L2153) | +| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:348`](../../ui/dr-ui/src/develop/render.rs#L348), [`ui/dr-ui/src/lib.rs:2318`](../../ui/dr-ui/src/lib.rs#L2318) | | NFR-P3 | [`tools/bench/src/thumbnails.rs:1`](../../tools/bench/src/thumbnails.rs#L1) | | NFR-P5 | [`core/dr-catalog/src/schema.rs:564`](../../core/dr-catalog/src/schema.rs#L564), [`ui/dr-ui/src/library/cells.rs:382`](../../ui/dr-ui/src/library/cells.rs#L382), [`ui/dr-ui/src/library/cells.rs:708`](../../ui/dr-ui/src/library/cells.rs#L708), [`ui/dr-ui/src/library_ui.rs:5005`](../../ui/dr-ui/src/library_ui.rs#L5005), [`ui/dr-ui/src/library_ui.rs:64`](../../ui/dr-ui/src/library_ui.rs#L64) | | NFR-P9 | [`core/dr-catalog/src/schema.rs:917`](../../core/dr-catalog/src/schema.rs#L917), [`core/dr-face/src/references.rs:1`](../../core/dr-face/src/references.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1`](../../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/export.rs:1005`](../../ui/dr-ui/src/export.rs#L1005), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.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) | @@ -176,7 +176,7 @@ A tag naming an ID `requirements.md` does not define — what renumbering produc | NFR-R6 | [`core/dr-catalog/src/error.rs:1`](../../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/lib.rs:255`](../../core/dr-catalog/src/lib.rs#L255), [`core/dr-catalog/src/recovery.rs:1`](../../core/dr-catalog/src/recovery.rs#L1) | | NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../../core/dr-gpu/src/error.rs#L1) | | NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../../core/dr-gpu/src/error.rs#L1) | -| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop/render.rs:519`](../../ui/dr-ui/src/develop/render.rs#L519), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:100`](../../ui/dr-ui/src/lib.rs#L100), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | +| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop/render.rs:519`](../../ui/dr-ui/src/develop/render.rs#L519), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:101`](../../ui/dr-ui/src/lib.rs#L101), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | | NFR-RES-2 | [`ui/dr-ui/src/faces.rs:462`](../../ui/dr-ui/src/faces.rs#L462), [`ui/dr-ui/src/library/sweep.rs:241`](../../ui/dr-ui/src/library/sweep.rs#L241), [`ui/dr-ui/src/repairs.rs:1140`](../../ui/dr-ui/src/repairs.rs#L1140), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1) | | NFR-RES-4 | [`core/dr-catalog/src/cache.rs:1`](../../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/face_shard.rs:1`](../../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/schema.rs:1182`](../../core/dr-catalog/src/schema.rs#L1182), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`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:393`](../../core/dr-thumbs/src/lib.rs#L393) | | NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30) | diff --git a/ui/dr-ui/src/develop_ui.rs b/ui/dr-ui/src/develop_ui.rs new file mode 100644 index 0000000..97b7c71 --- /dev/null +++ b/ui/dr-ui/src/develop_ui.rs @@ -0,0 +1,1272 @@ +//! The develop screen's callback wiring. +//! +//! This used to be the bulk of `lib.rs::run` — every `window.on_*` and +//! `global::<...>().on_*` registration the develop view needs, with no +//! module of its own, unlike every other screen (see +//! `docs/dev/code-health.md` CH-1). [`wire`] is the list `run` now calls; +//! each `wire_*` below is one of the sections that function used to contain, +//! moved with its section comment kept as the function's doc comment and its +//! callbacks registered in the same order. +//! +//! [`DevelopWiring`] gathers what those sections capture — the session, the +//! shared rows model, the redraw and render closures, and the handles onto +//! the other controllers a paste or an export reaches — so each function +//! takes one reference instead of a dozen parameters. + +use std::cell::{Cell, RefCell}; +use std::path::PathBuf; +use std::rc::Rc; + +use slint::ComponentHandle as _; + +use crate::activity; +use crate::collections_ui::CollectionsController; +use crate::develop; +use crate::library_ui::LibraryController; +use crate::masks_ui; +use crate::peaking; +use crate::presets; +use crate::settings_ui::SettingsController; +use crate::spots_ui; +use crate::{ + apply_crop_aspect, batch_request, drain_outbox, export, groups_in_rail, refresh_export_label, + render_open_frame, sync_crop_aspect, sync_film, sync_framing, sync_rows, Adjustments, + AppWindow, Develop, DevelopSession, Framing, Inspection, ParamRow, Peaking, Render, Steps, + ViewMode, +}; + +/// Shared state the develop screen's callbacks capture. +/// +/// Built once in [`wire`] from what `run` already owns; each field is an +/// owned `Rc` clone rather than a borrow, so every `wire_*` function below +/// can clone what it needs out of a `&DevelopWiring` and paste in the +/// closure exactly as `run` wrote it. `window` is not a field here — it is +/// not `Clone`, so it travels as an ordinary `&AppWindow` parameter beside +/// `w`, the same way `run` held it. +#[allow(clippy::type_complexity)] +pub(crate) struct DevelopWiring { + pub(crate) session: Rc>>, + pub(crate) rows: Rc>, + pub(crate) redraw: Rc, + pub(crate) render_now: Render, + pub(crate) show: Rc, + pub(crate) entries: Rc>>, + pub(crate) index: Rc>, + pub(crate) viewport: Rc>, + pub(crate) inspection: Rc>, + pub(crate) chosen_peaking: Rc>>, + pub(crate) settings: Rc, + pub(crate) library: Rc, + pub(crate) collections: Rc, + pub(crate) activity: Rc, + pub(crate) gpu: Option, + pub(crate) clipboard: Rc, + pub(crate) open_image: presets::OpenImage, + pub(crate) leave_develop: Rc>>>, +} + +/// Install every callback the develop screen answers to. +/// +/// Called once from `run`, after the session, the render loop and the other +/// screens all exist — every section below reaches at least one of them. +/// The order matches what `run` registered: presets, export, the adjustment +/// panel, film, undo/redo, geometry, then navigation and peaking. +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +pub(crate) fn wire( + window: &AppWindow, + session: &Rc>>, + rows: &Rc>, + redraw: &Rc, + render_now: &Render, + show: &Rc, + entries: &Rc>>, + index: &Rc>, + viewport: &Rc>, + inspection: &Rc>, + chosen_peaking: &Rc>>, + settings: &Rc, + library: &Rc, + collections: &Rc, + activity: &Rc, + gpu: &Option, + clipboard: &Rc, + open_image: &presets::OpenImage, + leave_develop: &Rc>>>, +) { + let w = DevelopWiring { + session: session.clone(), + rows: rows.clone(), + redraw: redraw.clone(), + render_now: render_now.clone(), + show: show.clone(), + entries: entries.clone(), + index: index.clone(), + viewport: viewport.clone(), + inspection: inspection.clone(), + chosen_peaking: chosen_peaking.clone(), + settings: settings.clone(), + library: library.clone(), + collections: collections.clone(), + activity: activity.clone(), + gpu: gpu.clone(), + clipboard: clipboard.clone(), + open_image: open_image.clone(), + leave_develop: leave_develop.clone(), + }; + + // TRACES: FR-DEV-3 + // **The ratio lock belongs to the tool, not to the photograph.** Lives + // here, for the life of the window, rather than on a `DevelopSession` + // which is rebuilt per image — see `wire_zoom_pan_crop` — and is shared + // with `wire_rotation_flips_straighten`, which is the other place a turn + // or a flip has to keep it in step. + let crop_aspect = Rc::new(Cell::new(develop::CropAspect::default())); + let crop_portrait = Rc::new(Cell::new(false)); + + wire_presets(window, &w); + wire_export(window, &w); + wire_adjustments(window, &w); + wire_film(window, &w); + wire_undo_redo(window, &w); + wire_zoom_pan_crop(window, &w, &crop_aspect, &crop_portrait); + wire_rotation_flips_straighten(window, &w, &crop_aspect, &crop_portrait); + wire_navigation(window, &w); + wire_peaking(window, &w); +} + +/// ---- copying settings between photographs (FR-DEV-6) ---------------- +/// +/// Wired after the develop session and the library both exist, because a +/// paste reaches both: onto the image on screen, or onto the grid's +/// selection through its sidecars. +fn wire_presets(window: &AppWindow, w: &DevelopWiring) { + let session = w.session.clone(); + let rows = w.rows.clone(); + let redraw = w.redraw.clone(); + let clipboard = w.clipboard.clone(); + let open_image = w.open_image.clone(); + let settings = w.settings.clone(); + let library = w.library.clone(); + let collections = w.collections.clone(); + let leave_develop = w.leave_develop.clone(); + + presets::wire( + window, + clipboard.clone(), + presets::Develop { + session: session.clone(), + rows: rows.clone(), + redraw: redraw.clone(), + open: open_image.clone(), + }, + settings.clone(), + library.clone(), + collections.clone(), + ); + + // TRACES: FR-DEV-6 + // The saved half of the same requirement, wired from the same bundle: + // applying a named preset to the open image is the paste path with a + // different source. + presets::wire_named( + window, + presets::NamedPresets::open(), + presets::Develop { + session: session.clone(), + rows: rows.clone(), + redraw: redraw.clone(), + open: open_image.clone(), + }, + settings.clone(), + library.clone(), + collections.clone(), + ); + + // Close the knot left open beside `open_from_library`: the grid's + // "‹ Library" button was wired before there was a session to save. + let weak = window.as_weak(); + let open_image = open_image.clone(); + let session = session.clone(); + let library = library.clone(); + *leave_develop.borrow_mut() = Some(Rc::new(move || { + let Some(w) = weak.upgrade() else { return }; + presets::save_open_edit(&w, &open_image.borrow(), &session, &library); + })); + + // TRACES: FR-DEV-6 + // The scope chips, shared by the preset sheet and the settings page. + // Wired once and rendered once: both surfaces draw one set, because + // "what does a paste carry" is one question about how this + // photographer works rather than one per place it is asked. + presets::wire_scope(window, settings.clone(), clipboard.clone()); + presets::render_scope(window, &settings); + + // TRACES: FR-UI-1 + // Rendered from the same settings controller and at the same moment, + // so the rail and the strip cannot both be on screen after a change to + // the preference that chooses between them. + window + .global::() + .set_groups_in_rail(groups_in_rail(&settings)); + presets::render(window, &clipboard, &settings); +} + +/// --- export (FR-EXP-6, FR-EXP-7, FR-EXP-9) --------------------------- +/// +/// Two buttons, one worker. The develop view exports the image on screen and +/// the grid exports its selection; the only difference between them is who +/// renders the frames, so both hand a `Vec` to the same batch and +/// both report through the same activity row. +/// +/// A frame is always rendered at full resolution rather than taken from the +/// canvas: the display render is deliberately viewport-sized (FR-DSP-1), and +/// exporting that would hand the user a soft, screen-sized file with no +/// indication anything had been lost (FR-EXP-9). +fn wire_export(window: &AppWindow, w: &DevelopWiring) { + let session = w.session.clone(); + let settings = w.settings.clone(); + let library = w.library.clone(); + let collections = w.collections.clone(); + let activity = w.activity.clone(); + let gpu = w.gpu.clone(); + + // Replaced at each start, so the button always cancels the run it is + // sitting on and a stale token cancels nothing. + let cancel: Rc> = Rc::new(RefCell::new(export::Cancel::default())); + // Holds the drain. Assigning a new timer drops the previous one, which + // is what takes a superseded batch's row out of the register. + let drain: Rc>> = Rc::new(RefCell::new(None)); + + let start = { + let settings = settings.clone(); + let library = library.clone(); + let activity = activity.clone(); + let gpu = gpu.clone(); + let cancel = cancel.clone(); + let drain = drain.clone(); + Rc::new( + move |window: &AppWindow, sources: Vec, to: export::Reporting| { + let total = sources.len(); + let request = batch_request(sources, &settings, &library, gpu.as_ref()); + + let token = export::Cancel::default(); + *cancel.borrow_mut() = token.clone(); + + let rx = export::spawn_batch(request, token); + + // Upload the moment the batch is done rather than waiting + // for a sync pass. An export bound for the server is + // complete on disk the instant it is staged, but it is not + // where the user asked for it until this runs — and + // "Queued for Exports" sitting unchanged until somebody + // presses Sync reads as an export that did not upload. + let library_for_drain = library.clone(); + export::drain_batch( + window.as_weak(), + &activity, + &drain, + rx, + total, + to, + move || drain_outbox(&library_for_drain), + ); + }, + ) + }; + + { + let weak = window.as_weak(); + let session = session.clone(); + let settings = settings.clone(); + let start = start.clone(); + window.on_export_image(move || { + let Some(w) = weak.upgrade() else { return }; + // One batch at a time, and the grid's counts as one. The two + // buttons share a worker slot, so starting a second run would + // drop the first's drain — leaving a batch still writing files + // with no progress row and a button that never comes back. + if w.get_export_busy() || w.get_library_exporting() { + w.set_export_status("An export is already running".into()); + return; + } + w.set_export_busy(true); + // Set before the render rather than after: the render is the + // one part still on this thread, so a label written afterwards + // would never be drawn in the "Rendering…" state at all. + w.set_export_status("Rendering…".into()); + + match render_open_frame(&w, &session, settings.snapshot().export.colour_space) { + Ok(source) => start(&w, vec![source], export::Reporting::Develop), + Err(e) => { + w.set_export_status(format!("Export failed: {e}").into()); + w.set_export_busy(false); + } + } + refresh_export_label(&w, &settings); + }); + } + + // TRACES: FR-EXP-7 + // The grid's selection. Nothing is rendered here — the worker fetches, + // decodes and renders each photograph itself, so this returns to the + // event loop immediately and a batch of three hundred is a progress bar + // rather than a frozen window. + { + let weak = window.as_weak(); + let library = library.clone(); + let collections = collections.clone(); + let start = start.clone(); + window.on_library_export_selection(move || { + let Some(w) = weak.upgrade() else { return }; + // See the develop button above for why one run excludes the + // other. The button is a cancel by then, so this only catches + // a batch started from develop and left running. + if w.get_library_exporting() || w.get_export_busy() { + w.set_library_status("An export is already running".into()); + return; + } + + let sources = library.export_sources(&collections.selected()); + if sources.is_empty() { + // Said out loud rather than ignored, matching what a paste + // or a judgement keystroke does with an empty selection. + w.set_library_status("Select an image first".into()); + return; + } + + w.set_library_exporting(true); + w.set_library_status(format!("Exporting {} images…", sources.len()).into()); + start(&w, sources, export::Reporting::Library); + }); + } + + // TRACES: NFR-ARCH-3 + { + let weak = window.as_weak(); + window.on_library_cancel_export(move || { + cancel.borrow().cancel(); + if let Some(w) = weak.upgrade() { + // The worker stops at the next point it is safe to — which + // may be a frame away — so the button says "asked for" and + // not "done". The drain writes the real answer. + w.set_library_status("Cancelling the export…".into()); + } + }); + } +} + +/// ---- Adjustment callbacks ------------------------------------------ +/// +/// Generic by construction: they carry indices into the capability list, +/// so adding an operation needs no change here (FR-DEV-3c). +fn wire_adjustments(window: &AppWindow, w: &DevelopWiring) { + let session = w.session.clone(); + let rows = w.rows.clone(); + let redraw = w.redraw.clone(); + + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window + .global::() + .on_param_changed(move |op, param, value| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.set_param(op, param, value); + // TRACES: FR-DEV-3f + // The film's own exposures ride *inside* the baked tables + // rather than arriving as uniforms, because the print balance + // is solved against them — an enlarger's filtration depends on + // how the negative was exposed. So moving one has to rebuild + // the lookup, which no other slider in the panel does. + s.rebake_film_if_affected(op); + } + sync_rows(&w, &rows, &session); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window + .global::() + .on_param_reset(move |op, param| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.reset_param(op, param); + } + sync_rows(&w, &rows, &session); + redraw(&w); + }); + } + // TRACES: FR-DEV-3 + // The local-adjustment panel. Wired as a block rather than inline because + // it is a dozen callbacks that all say the same three things, and they + // read better beside each other than scattered through this function. + { + // Choosing a group re-filters the panel and nothing else — no edit, + // no render. It is navigation. + let weak = window.as_weak(); + let session = session.clone(); + let rows = rows.clone(); + window.global::().on_tab_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.set_active_tab(index); + } + sync_rows(&w, &rows, &session); + // The stock picker is filtered by group like everything else, and + // it is not a row — so the thing that decides whether it is on + // screen has to be recomputed here as well, or it would answer for + // whichever group happened to be open when the image was loaded. + sync_film(&w, &session); + }); + } + + masks_ui::wire(window, &session, &rows, &redraw); + spots_ui::wire(window, &session, redraw.clone()); + + // A way to land on the Identity Manager at startup, for looking at it + // without a mouse. Off unless the variable is set, so it costs a getenv + // per launch and changes nothing otherwise. + if std::env::var_os("DARKROOM_START_IDENTITY").is_some() { + window.invoke_identity_open(); + } + if std::env::var_os("DARKROOM_START_SETTINGS").is_some() { + window.invoke_settings_open(); + } + + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_reset_all(move || { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.reset_all(); + } + sync_rows(&w, &rows, &session); + redraw(&w); + }); + } + { + // A curve is one control spanning many parameters, so resetting it + // clears all of them at once — resetting a single point would leave + // a shape the user did not ask for. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_curve_reset(move |op| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.reset_curve(op); + } + sync_rows(&w, &rows, &session); + redraw(&w); + }); + } + { + // Which of the curve's curves the plot is showing. **No redraw**, and + // that is the whole character of this control: it changes no + // parameter, so the photograph is already correct on screen and + // recomputing it would be a frame spent to produce the same pixels. + // For the same reason it records no history step — there is nothing + // to undo — and the sidecar never hears about it. + let weak = window.as_weak(); + let session = session.clone(); + let rows = rows.clone(); + window + .global::() + .on_curve_channel_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.set_curve_channel(index); + } + sync_rows(&w, &rows, &session); + }); + } +} + +/// ---- the film stock (FR-DEV-3f) ------------------------------------- +/// +/// Unlike the curve channel above, both of these change the photograph: +/// choosing a stock *is* the edit. So they redraw, and they leave the +/// session dirty for the sidecar in the ordinary way. +fn wire_film(window: &AppWindow, w: &DevelopWiring) { + let session = w.session.clone(); + let rows = w.rows.clone(); + let redraw = w.redraw.clone(); + + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_film_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + // Index zero is "None" — see `DevelopSession::film_choices`, which + // puts it first so that the neutral choice needs no sentinel. + let choices = DevelopSession::film_choices(); + let Some((stock, _)) = choices.get(index.max(0) as usize) else { + return; + }; + if let Some(s) = session.borrow_mut().as_mut() { + // Printed by default when the stock has a paper: a colour + // negative that has not been printed is an orange strip, and + // offering that as the first thing a photographer sees when + // they pick Portra would read as a bug rather than as a + // choice. The toggle is there for anyone who wants the scan. + let print = stock + .and_then(dr_film::find) + .and_then(dr_film::default_print) + .is_some(); + s.pick_film(*stock, print); + } + sync_rows(&w, &rows, &session); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window + .global::() + .on_film_print_toggled(move |print| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) { + s.pick_film(Some(&stock), print); + } + } + sync_film(&w, &session); + redraw(&w); + }); + } +} + +/// ---- undo and redo (FR-DEV-5) --------------------------------------- +/// +/// Thin, because the history lives in the session and every mutator there +/// records into it — see `DevelopSession::history`. What is left for the +/// interface is the refresh a paste also needs: the controls are showing +/// values that have just moved underneath them. +/// +/// `can-undo` and `can-redo` are not set here; `render_now` pushes them on +/// every redraw, which is every path that can change them. +fn wire_undo_redo(window: &AppWindow, w: &DevelopWiring) { + let session = w.session.clone(); + let rows = w.rows.clone(); + let redraw = w.redraw.clone(); + let render_now = w.render_now.clone(); + + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_undo(move || { + let Some(w) = weak.upgrade() else { return }; + let stepped = session.borrow_mut().as_mut().is_some_and(|s| s.undo()); + if stepped { + sync_rows(&w, &rows, &session); + redraw(&w); + } + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_redo(move || { + let Some(w) = weak.upgrade() else { return }; + let stepped = session.borrow_mut().as_mut().is_some_and(|s| s.redo()); + if stepped { + sync_rows(&w, &rows, &session); + redraw(&w); + } + }); + } + { + // TRACES: FR-DEV-7 + // Holding the comparison, and letting it go. + // + // **No rows are synced and no history is touched**, and both absences + // are the feature. The panel is describing the edit the photographer + // still has; only the canvas changes, and it changes back. This is the + // whole difference between a comparison and the undo-look-redo that + // had to stand in for one — that put two real steps on the stack at + // the moment somebody was least sure of what they were doing. + // + // A full frame rather than a draft: comparing a half-resolution + // original against a sharp edit would show a difference the edit does + // not have, which is the one thing this must not do. + let weak = window.as_weak(); + let render_now = render_now.clone(); + window.on_compare_original(move |on| { + let Some(w) = weak.upgrade() else { return }; + // A held key repeats. Answering a repeat with a re-render would + // spend a full-resolution pass per keystroke to arrive back at the + // frame already on screen. + if w.get_showing_original() == on { + return; + } + w.set_showing_original(on); + render_now(&w, false); + }); + } + { + // TRACES: FR-DEV-3 + // A neutral picked off the photograph. + // + // The operation is not named here and is not passed in: the click + // carries a point, the session turns it into a colour, and + // `dr_pipeline::neutral` finds the operation that asked to be driven + // by one. This callback's whole contribution is that a click on the + // canvas is a click on the canvas. + // + // The rows are re-synced because the sample moved parameters the panel + // is showing — two of them, from one press, which is exactly the case + // a paste and an undo already go through this path for. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.on_neutral_picked(move |x, y| { + let Some(w) = weak.upgrade() else { return }; + let sampled = session + .borrow_mut() + .as_mut() + .is_some_and(|s| s.sample_neutral(x, y)); + // Nothing to redraw otherwise: the probe renders to its own + // target and a sample that found nothing usable moved nothing. + if sampled { + sync_rows(&w, &rows, &session); + redraw(&w); + } + }); + } + + // TRACES: FR-DEV-5 | FR-DEV-7 + // Clicking a row. Arriving six steps away costs what arriving from one + // does, because a step is a whole state — see `History::go_to`. + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + let stepped = session + .borrow_mut() + .as_mut() + .is_some_and(|s| s.go_to_history(index)); + if stepped { + sync_rows(&w, &rows, &session); + redraw(&w); + } + }); + } + // TRACES: FR-DEV-5 + // Named snapshots. Taking and deleting one change the list and nothing + // else — no rows, no history — so they redraw only to push the list, + // through the same path everything else pushes it on. Restoring one is + // the paste's shape: the whole edit moves, so the rows resync and the + // picture is redrawn. + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.global::().on_snapshot_taken(move |name| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.take_snapshot(&name); + } + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.global::().on_snapshot_restored(move |id| { + let Some(w) = weak.upgrade() else { return }; + let restored = session + .borrow_mut() + .as_mut() + .is_some_and(|s| s.restore_snapshot(&id)); + if restored { + sync_rows(&w, &rows, &session); + // The mask panel too: a snapshot carries its layers, and + // the redraw syncs the repairs and the overlay but not the + // rows the layers are listed in. + masks_ui::sync(&w, &session); + redraw(&w); + } + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.global::().on_snapshot_removed(move |id| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.delete_snapshot(&id); + } + redraw(&w); + }); + } + { + // TRACES: FR-DEV-7 + // Holding a snapshot against the edit, and letting it go — the + // Before button's shape, and its rule: no rows are synced and no + // history is touched. A full frame rather than a draft, for the + // reason the original is: a soft comparison shows a difference the + // edit does not have. + let weak = window.as_weak(); + let session = session.clone(); + let render_now = render_now.clone(); + window + .global::() + .on_snapshot_compared(move |id, down| { + let Some(w) = weak.upgrade() else { return }; + let changed = session + .borrow_mut() + .as_mut() + .is_some_and(|s| s.compare_snapshot(down.then_some(id.as_str()))); + if changed { + render_now(&w, false); + } + }); + } +} + +/// ---- zoom, pan and crop --------------------------------------------- +/// +/// Zoom and pan are viewing state and touch no parameter, so unlike the +/// handlers above they do not `sync_rows`. +fn wire_zoom_pan_crop( + window: &AppWindow, + w: &DevelopWiring, + crop_aspect: &Rc>, + crop_portrait: &Rc>, +) { + let session = w.session.clone(); + let rows = w.rows.clone(); + let redraw = w.redraw.clone(); + let viewport = w.viewport.clone(); + let inspection = w.inspection.clone(); + let crop_aspect = crop_aspect.clone(); + let crop_portrait = crop_portrait.clone(); + + window.global::().set_aspects( + develop::CropAspect::CHOICES + .iter() + .map(|a| slint::SharedString::from(a.label())) + .collect::>() + .as_slice() + .into(), + ); + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.on_zoom_at(move |factor, at_x, at_y| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.zoom_about(factor, at_x, at_y); + } + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.on_pan_by(move |dx, dy| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.pan_by(dx, dy); + } + redraw(&w); + }); + } + { + // TRACES: FR-UI-4 + // Fit and 1:1, and the memory of where 1:1 was pointing. + // + // The remembered point is written from what the session *landed* on + // rather than from the coordinates that arrived, so a point near an + // edge is stored where the view was clamped to. Storing the request + // instead would carry a point the previous photograph could not show + // to a photograph that also cannot show it, and the drift would + // accumulate over a folder. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let viewport = viewport.clone(); + let inspection = inspection.clone(); + window.on_inspect_toggled(move |at_x, at_y| { + let Some(w) = weak.upgrade() else { return }; + let (vw, vh) = *viewport.borrow(); + if let Some(s) = session.borrow_mut().as_mut() { + // A negative anchor is a control with no point of its own — + // the readout button, the keyboard — asking for the last one. + // Resolved here rather than in Slint because this is where + // the memory lives. + // + // The remembered point is in fractions of the framed image and + // the session wants fractions of the *visible* area, which are + // the same thing only while the view is fitted. That is + // exactly when this branch can move the view: from a zoomed + // view the toggle goes out to fit and ignores its anchor + // entirely. + let held = inspection.get(); + let (ax, ay) = if at_x < 0.0 || at_y < 0.0 { + held.at + } else { + (at_x, at_y) + }; + inspection.set(match s.toggle_inspection(ax, ay, vw, vh) { + Some(at) => Inspection { at, on: true }, + // The aim is kept, only switched off. See `Inspection`. + None => Inspection { on: false, ..held }, + }); + } + redraw(&w); + }); + } + { + // TRACES: FR-UI-5 + // **Entering a mode is a side effect, which is why Rust owns it** and + // the strip does not simply write the property. Each of the three has + // work to do that the interface cannot see: + // + // *Crop* drops the zoom. The handles are placed against the whole + // frame, and a zoomed view would put most of that frame off screen + // where it cannot be dragged. + // + // *Local* turns the region overlay on. It used to be a button in the + // masking panel, so the mode could be open with the overlay off — + // which is a mode you have entered that is doing nothing. + // + // *Leaving* clears the selection, and that is the fault this whole + // pass exists for: a selected layer silently re-points thirty sliders + // at that layer's chain, so a mode you have left must not leave one + // behind. After this the controls are unambiguously global again, + // which is what the panel's heading then says. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + window.on_mode_picked(move |mode| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + match mode { + ViewMode::Crop => { + s.reset_zoom(); + let c = s.crop(); + w.set_crop_x(c.x); + w.set_crop_y(c.y); + w.set_crop_w(c.width); + w.set_crop_h(c.height); + } + ViewMode::Local => s.set_overlay(true), + // TRACES: FR-DEV-8 + // Nothing to arm: the circles are drawn whenever there are + // repairs, and what the mode changes is whether a click on + // the photograph makes another one. Leaving the mask + // selection behind would re-point the column at a layer's + // chain while the canvas is showing repairs, which is the + // fault this whole strip exists to prevent. + ViewMode::Spots => { + s.set_overlay(false); + s.set_active_mask(None); + } + ViewMode::Photo => { + s.set_overlay(false); + s.set_active_mask(None); + // A repair stays on the photograph; only the *selection* + // goes, so the source circle does not hang about over a + // frame nobody is repairing any more. + s.select_spot(None); + } + } + } + w.global::().set_view_mode(mode); + masks_ui::sync(&w, &session); + if let Some(s) = session.borrow().as_ref() { + spots_ui::sync_handles(&w, s); + } + // The scope may have just changed, so the panel below is now + // describing a different chain. + sync_rows(&w, &rows, &session); + redraw(&w); + }); + } + { + // The rect arrives raw from the drag; the session normalises it, holds + // it to whatever ratio is locked, and the properties are written back + // from what it actually stored. That round trip is what makes an + // over-drag slide along the edge rather than letting the overlay and + // the pipeline disagree — and it is what lets the lock reshape a drag + // without the overlay having to know a ratio exists. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let crop_aspect = crop_aspect.clone(); + let crop_portrait = crop_portrait.clone(); + window.on_crop_changed(move |x, y, width, height, hx, hy| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + let rect = dr_pipeline::CropRect { + x, + y, + width, + height, + }; + // The overlay reports the corner it is *holding*; the point + // that must not move is the opposite one. A move reports no + // corner at all, and keeps the shape it already has — there is + // nothing to reshape, and reshaping about a centre would drag + // an over-moved rect smaller instead of sliding it along the + // edge. + if hx < 0.0 { + s.set_crop(rect); + } else { + s.set_crop_locked( + rect, + crop_aspect.get(), + crop_portrait.get(), + (1.0 - hx, 1.0 - hy), + ); + } + let c = s.crop(); + w.set_crop_x(c.x); + w.set_crop_y(c.y); + w.set_crop_w(c.width); + w.set_crop_h(c.height); + w.global::().set_modified(s.framing_edits_image()); + } + redraw(&w); + }); + } + { + // TRACES: FR-DEV-3 + // Choosing a ratio reshapes the crop there and then rather than + // waiting for the next drag. A lock that only took effect on the + // following gesture would leave the chip lit over a rect that is not + // that shape, which is the panel lying about the image. + // + // Held about the rect's centre, so the composition stays where it is + // and the shape changes around it. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let crop_aspect = crop_aspect.clone(); + let crop_portrait = crop_portrait.clone(); + window.global::().on_aspect_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + let Some(&aspect) = develop::CropAspect::CHOICES.get(index.max(0) as usize) else { + return; + }; + crop_aspect.set(aspect); + // A ratio with no second orientation cannot stay stood on its + // short edge, or the switch would be off and the crop upright. + if !aspect.has_orientation() { + crop_portrait.set(false); + } + sync_crop_aspect(&w, &crop_aspect, &crop_portrait); + apply_crop_aspect(&w, &session, &crop_aspect, &crop_portrait); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let crop_aspect = crop_aspect.clone(); + let crop_portrait = crop_portrait.clone(); + window.global::().on_portrait_toggled(move || { + let Some(w) = weak.upgrade() else { return }; + if !crop_aspect.get().has_orientation() { + return; + } + crop_portrait.set(!crop_portrait.get()); + sync_crop_aspect(&w, &crop_aspect, &crop_portrait); + apply_crop_aspect(&w, &session, &crop_aspect, &crop_portrait); + redraw(&w); + }); + } +} + +/// ---- rotation, flips and straightening ------------------------------- +/// +/// Framing edits, so unlike zoom and pan they mark the image modified — but +/// they are reached through named session actions rather than through a row +/// index, so they do not `sync_rows` either. `sync_framing` is what carries +/// the applied value back. +fn wire_rotation_flips_straighten( + window: &AppWindow, + w: &DevelopWiring, + crop_aspect: &Rc>, + crop_portrait: &Rc>, +) { + let session = w.session.clone(); + let redraw = w.redraw.clone(); + let crop_aspect = crop_aspect.clone(); + let crop_portrait = crop_portrait.clone(); + + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + let crop_aspect = crop_aspect.clone(); + let crop_portrait = crop_portrait.clone(); + window.global::().on_rotate(move |turns| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.rotate_quarters(turns); + } + // TRACES: FR-DEV-3 + // The crop travels with the frame, so a rect locked to 16:9 comes + // out of the turn at 9:16. The switch has to agree, or the next + // drag would snap it back upright and undo what the turn did to + // the composition. An even number of turns lands where it started. + let aspect = crop_aspect.get(); + if aspect.turns_with_the_frame() && turns.rem_euclid(2) != 0 { + crop_portrait.set(!crop_portrait.get()); + sync_crop_aspect(&w, &crop_aspect, &crop_portrait); + } + sync_framing(&w, &session); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.global::().on_flip_h_toggled(move || { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.toggle_flip_h(); + } + sync_framing(&w, &session); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.global::().on_flip_v_toggled(move || { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.toggle_flip_v(); + } + sync_framing(&w, &session); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.global::().on_angle_changed(move |degrees| { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.set_angle(degrees); + } + sync_framing(&w, &session); + redraw(&w); + }); + } + { + // TRACES: FR-DEV-3 + // **The end of a straightening gesture crops away the corners it + // exposed.** A free angle does not change the output size, by design — + // that is what leaves the frame where the user put it while the slider + // moves — so the corners of the finished photograph would otherwise be + // black wedges of undefined area. + // + // Only on release, and only on release. Doing it per frame ratchets + // the crop down through every angle the slider passed through rather + // than the one it stopped at; see `auto_crop_to_angle`. + // + // Hung off the track's `committed`, not off `engaged-changed`. The + // latter is *hover* — it has to be, because a `Flickable` withholds + // the press — so a correction keyed on it fires when the pointer first + // crosses the track, whether or not anything was dragged, and then + // never fires again for as long as the pointer stays on it. Which + // reads, exactly, as an auto-crop that works once. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window + .global::() + .on_angle_committed(move |_degrees| { + // The angle itself is already applied: `SliderTrack` emits its + // last `changed` before it commits, so `on_straighten_changed` has + // run with this very value. What is left is the correction that + // has to happen exactly once. + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.auto_crop_to_angle(); + } + sync_framing(&w, &session); + redraw(&w); + }); + } + { + // The geometry section's reset: crop, angle, rotation and flips back + // to neutral, leaving every colour adjustment where it is. The panel's + // own reset-all is the one that clears everything. + let weak = window.as_weak(); + let session = session.clone(); + let redraw = redraw.clone(); + window.global::().on_reset(move || { + let Some(w) = weak.upgrade() else { return }; + if let Some(s) = session.borrow_mut().as_mut() { + s.reset_framing(); + } + sync_framing(&w, &session); + redraw(&w); + }); + } +} + +/// Stepping through the browsing list, one image at a time. +fn wire_navigation(window: &AppWindow, w: &DevelopWiring) { + let index = w.index.clone(); + let entries = w.entries.clone(); + let show = w.show.clone(); + + { + let weak = window.as_weak(); + let index = index.clone(); + let entries = entries.clone(); + let show = show.clone(); + window.on_next_image(move || { + let Some(w) = weak.upgrade() else { return }; + let len = entries.borrow().len(); + if len == 0 { + return; + } + // Read, then write — `*x.borrow_mut() = *x.borrow() + 1` holds + // both borrows at once and panics. + let next = { + let cur = *index.borrow(); + (cur + 1) % len + }; + *index.borrow_mut() = next; + show(&w); + }); + } + { + let weak = window.as_weak(); + let index = index.clone(); + let entries = entries.clone(); + let show = show.clone(); + window.on_prev_image(move || { + let Some(w) = weak.upgrade() else { return }; + let len = entries.borrow().len(); + if len == 0 { + return; + } + let prev = { + let cur = *index.borrow(); + if cur == 0 { + len - 1 + } else { + cur - 1 + } + }; + *index.borrow_mut() = prev; + show(&w); + }); + } +} + +/// TRACES: FR-CULL-3 +/// The peaking switch and its two choices. +/// +/// All three write `chosen_peaking` and then redraw, because the marks are +/// produced by a compute pass over the rendered frame: there is nothing the +/// interface can change about the overlay that does not require the frame +/// to be measured again. Turning peaking *off* redraws for the same reason +/// — that render is what drops the overlay textures and clears the flag. +fn wire_peaking(window: &AppWindow, w: &DevelopWiring) { + let chosen_peaking = w.chosen_peaking.clone(); + let redraw = w.redraw.clone(); + + { + let weak = window.as_weak(); + let chosen = chosen_peaking.clone(); + let redraw = redraw.clone(); + window.global::().on_toggled(move |on| { + let Some(w) = weak.upgrade() else { return }; + // Built from the chips as they currently stand rather than from a + // remembered value: they are what the photographer can see, and an + // overlay that came back in a configuration the panel is not + // showing would be the panel lying about itself. + let next = on.then(|| dr_gpu::FocusPeaking { + sensitivity: peaking::sensitivity(w.global::().get_sensitivity()), + colour: peaking::colour(w.global::().get_colour()), + }); + chosen.set(next); + w.global::().set_showing(next.is_some()); + redraw(&w); + }); + } + { + let weak = window.as_weak(); + let chosen = chosen_peaking.clone(); + let redraw = redraw.clone(); + window + .global::() + .on_sensitivity_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + w.global::().set_sensitivity(index); + // Only reachable while peaking is on — the chips are not drawn + // otherwise — but written as a conditional rather than an + // `expect`, because a panel is free to change its mind about that + // and nothing here should fall over when it does. + if let Some(mut current) = chosen.get() { + current.sensitivity = peaking::sensitivity(index); + chosen.set(Some(current)); + redraw(&w); + } + }); + } + { + let weak = window.as_weak(); + let chosen = chosen_peaking.clone(); + let redraw = redraw.clone(); + window.global::().on_colour_picked(move |index| { + let Some(w) = weak.upgrade() else { return }; + w.global::().set_colour(index); + if let Some(mut current) = chosen.get() { + current.colour = peaking::colour(index); + chosen.set(Some(current)); + redraw(&w); + } + }); + } + // The chips open on whatever the vocabulary calls its default, so the + // panel and the pass agree before anything has been pressed. + window + .global::() + .set_sensitivity(peaking::sensitivity_index(Default::default())); + window + .global::() + .set_colour(peaking::colour_index(Default::default())); +} diff --git a/ui/dr-ui/src/lib.rs b/ui/dr-ui/src/lib.rs index c7eb484..7a820c2 100644 --- a/ui/dr-ui/src/lib.rs +++ b/ui/dr-ui/src/lib.rs @@ -24,6 +24,7 @@ mod bursts; mod collections_ui; mod derived_sync; mod develop; +mod develop_ui; mod display_ui; mod export; pub mod faces; @@ -1159,6 +1160,224 @@ pub fn run(paths: Vec) -> Result<()> { let gpu = shared_gpu(); log::info!("gpu opened in {} ms", launch_began.elapsed().as_millis()); + let window = init_window(&gpu)?; + wire_inference_status(&window); + wire_diagnostics(&window, &gpu); + + // Every background job reports here, and this draws the bar across the top + // of the shell and fills the settings page's list. Built before the + // controllers because they take a handle to it: a job that starts during + // startup — the scan a resumed session begins immediately — has to have + // somewhere to report to before it starts, or its first minute is invisible. + let activity = activity::ActivityLog::new(); + activity.attach(&window); + { + let activity = activity.clone(); + window.on_activity_clear_finished(move || activity.clear_finished()); + } + + // Set once `show` exists; see where the library grid is wired below. + #[allow(clippy::type_complexity)] + let open_from_library: Rc>>> = Rc::new(RefCell::new(None)); + + // TRACES: FR-CAT-8 + // The same knot, for the other direction: leaving develop has to persist + // the edit, and the grid's "‹ Library" button is wired before the develop + // session exists to save from. Empty until then, and calling it is a no-op + // rather than a panic — there is nothing open to lose. + #[allow(clippy::type_complexity)] + let leave_develop: Rc>>> = Rc::new(RefCell::new(None)); + + // Before anything binds to a token: the compiled palette is already in + // place, so this only overwrites what style.yaml currently says. + #[cfg(live_style)] + live_style::apply(&window); + + let (library, prefetch, collections, identity, settings) = construct_screens( + &window, + &paths, + &activity, + &gpu, + &open_from_library, + &leave_develop, + ); + // Held for the life of the window only to keep the People screen's cover + // cache registered with the thumbnail tier above — nothing here reads it + // again before `window.run()`. + let _identity = identity; + + wire_import_and_merge(&window, &activity, &gpu, &library, &collections, &settings); + wire_settings_screen(&window, &settings, &library); + + window.set_total(entries.borrow().len() as i32); + let index = Rc::new(RefCell::new(0usize)); + // The current develop session, if the file yielded sensor data. + let session: Rc>> = Rc::new(RefCell::new(None)); + + // TRACES: FR-PLAT-AND-5 + // The GPU tier — the first thing given back under memory pressure, and on + // Android the only thing given back merely for going into the background. + // + // `try_borrow_mut` rather than `borrow_mut`, and the miss is not an error + // worth reporting. A memory warning can land in the middle of a render, at + // which point the slot is already borrowed and freeing its textures under + // the code drawing with them is not something to do politely — skipping is + // correct, because the pass that is running will have finished by the time + // the platform asks again, and a warning that has not been acted on is + // always followed by another one. + { + let session = Rc::downgrade(&session); + memory::evict_at(memory::Tier::Gpu, move || { + let Some(session) = session.upgrade() else { + return; + }; + let Ok(mut slot) = session.try_borrow_mut() else { + return; + }; + if let Some(open) = slot.as_mut() { + open.release_gpu_caches(); + } + }); + } + + // TRACES: FR-DEV-6 | FR-CAT-8 + // The settings clipboard, and where the open image's edit is stored. + // + // Both live for the life of the window rather than the view: a copy is + // taken in develop and may be pasted onto a selection back in the grid, so + // a clipboard owned by the develop view would be emptied by the very + // navigation that carries it to its destination. + let clipboard = presets::Clipboard::new(); + let open_image: presets::OpenImage = Rc::new(RefCell::new(presets::Stored::Nowhere)); + + // One model for the lifetime of the window. Rows are mutated in place; + // see `sync_rows` for why replacing it breaks dragging. + let rows: Rc> = Rc::new(slint::VecModel::default()); + window.global::().set_rows(rows.clone().into()); + // Viewport size, tracked so a re-render after a slider move matches it. + // + // In *physical* pixels, which is what the render target wants and what + // FR-DSP-8 means by handling fractional scaling without resampling. The + // one caller that thinks in logical pixels is Slint's resize callback, + // and it converts on the way in. + let viewport = Rc::new(RefCell::new((1024u32, 768u32))); + + // TRACES: FR-DSP-8 + // What the displays are, and which one the canvas is on. Probed once here + // so that the About page can describe the session's colour path before any + // photograph is opened — the acquisition path is a property of the desktop + // and not of the image. + let display = display_ui::DisplayWatch::probe(); + + // TRACES: FR-CULL-3 + // How the photographer wants focus peaking drawn, or `None` for off. + // + // **Held here rather than on the session, which is the opposite of where + // every edit lives.** A session is one photograph; peaking is a way of + // *looking* at a folder of them. Someone culling three thousand frames + // switches it on once, and a flag that reset with the session would ask + // them to switch it on three thousand times — which is why + // `reset_view_state` deliberately leaves it alone while emptying the + // histogram beside it. + let chosen_peaking: Rc>> = Rc::new(Cell::new(None)); + + // TRACES: FR-UI-4 + // The point being inspected at 1:1, in fractions of the framed image, or + // `None` where the view is fitted. + // + // **Held here rather than on the session, on exactly the argument + // `chosen_peaking` above makes.** A session is one photograph and this is + // a way of looking at a folder of them: checking the same eye across forty + // portraits is the entire reason a 1:1 view is worth reaching, and a + // magnification that reset with the session would ask for the same zoom + // and the same pan forty times. So `reset_view_state` empties the + // histogram and leaves this alone, and the two open paths below put the + // next photograph under the magnifier the last one was left under. + // + // It is a *viewing* state throughout. It never reaches the graph's crop, + // the sidecar or an export — `Framing::view` is kept out of all three, and + // `render_the_file` suspends it — so a photographer left at 4× exports the + // whole frame, as they did before this existed. + let inspection: Rc> = Rc::new(Cell::new(Inspection::default())); + + let render_now = build_render_now(&session, &viewport, &display, &chosen_peaking); + let redraw = build_redraw(&render_now); + let show = build_show( + &entries, + &index, + &session, + &redraw, + &gpu, + &rows, + &open_image, + &library, + &viewport, + &inspection, + ); + + // Now `show` exists, close the knot left open at the library wiring. + wire_remote_open( + &window, + &open_from_library, + &library, + &session, + &redraw, + &rows, + &gpu, + &activity, + &open_image, + &viewport, + &inspection, + &prefetch, + ); + + develop_ui::wire( + &window, + &session, + &rows, + &redraw, + &render_now, + &show, + &entries, + &index, + &viewport, + &inspection, + &chosen_peaking, + &settings, + &library, + &collections, + &activity, + &gpu, + &clipboard, + &open_image, + &leave_develop, + ); + + wire_shell(&window, &viewport, &redraw, &display); + + if !entries.borrow().is_empty() { + show(&window); + } + + // The figure that matters, because it is the one the input dispatcher is + // counting against. Anything approaching five seconds here is an ANR on the + // next Android launch whatever the phases above say, and anything that + // pushes it up belongs on a worker. + log::info!( + "startup: {} ms to the event loop", + launch_began.elapsed().as_millis() + ); + + window.run()?; + Ok(()) +} + +/// Build the window and set what has to be true before it is shown: the +/// window-manager app id, the About page's version, and the backend line +/// that names the adapter (or says there is none). +fn init_window(gpu: &Option) -> Result { + let gpu = gpu.clone(); + let before_window = std::time::Instant::now(); let window = AppWindow::new()?; log::info!("window built in {} ms", before_window.elapsed().as_millis()); @@ -1211,134 +1430,137 @@ pub fn run(paths: Vec) -> Result<()> { } None => window.set_backend("NO GPU".into()), } + Ok(window) +} - // TRACES: FR-INF-1 - // What the models run on. Re-read every two seconds because the answer - // changes twice after launch — when the probe reports and as each - // engine lands — and the page is open for longer than either takes. +/// TRACES: FR-INF-1 +/// What the models run on. Re-read every two seconds because the answer +/// changes twice after launch — when the probe reports and as each +/// engine lands — and the page is open for longer than either takes. +fn wire_inference_status(window: &AppWindow) { + let set = |w: &AppWindow| { + let (line, detail) = inference::about_lines(); + w.set_inference_backend(line.into()); + w.set_inference_detail(detail.into()); + }; + set(window); + let weak = window.as_weak(); + let timer = Rc::new(slint::Timer::default()); + let held = timer.clone(); + timer.start( + slint::TimerMode::Repeated, + std::time::Duration::from_secs(2), + move || { + let _keep = &held; + if let Some(w) = weak.upgrade() { + set(&w); + } + }, + ); +} + +/// TRACES: NFR-OPS-1 +/// The diagnostics bundle, wired as the two presses the requirement +/// describes. Preparing gathers the log and the crash records into memory +/// and shows what would be written; saving writes it; discarding drops it +/// and writes nothing. The gathered bundle is held between the presses so +/// that what is saved is exactly what was shown — a bundle gathered again +/// at save time could differ from its own preview by whatever was logged +/// while the user was reading. +fn wire_diagnostics(window: &AppWindow, gpu: &Option) { + let gpu = gpu.clone(); + + use dr_plat::diagnostics::bundle::{Bundle, Facts}; + let facts = Facts { + app_version: env!("CARGO_PKG_VERSION").to_string(), + schema_version: dr_catalog::schema::SCHEMA_VERSION, + graphics: match &gpu { + Some(ctx) => format!( + "{} ({:?}), {}", + ctx.adapter_name(), + ctx.backend(), + ctx.driver() + ), + None => "no GPU".to_string(), + }, + }; + let pending: Rc>> = Rc::new(RefCell::new(None)); { - let set = |w: &AppWindow| { - let (line, detail) = inference::about_lines(); - w.set_inference_backend(line.into()); - w.set_inference_detail(detail.into()); - }; - set(&window); let weak = window.as_weak(); - let timer = Rc::new(slint::Timer::default()); - let held = timer.clone(); - timer.start( - slint::TimerMode::Repeated, - std::time::Duration::from_secs(2), - move || { - let _keep = &held; - if let Some(w) = weak.upgrade() { - set(&w); + let pending = pending.clone(); + window.on_diagnostics_prepare(move || { + let Some(w) = weak.upgrade() else { return }; + let bundle = Bundle::gather(&facts); + w.set_diagnostics_preview( + bundle + .preview(&dr_plat::diagnostics::bundle::default_dir()) + .into(), + ); + w.set_diagnostics_result("".into()); + *pending.borrow_mut() = Some(bundle); + }); + } + { + let weak = window.as_weak(); + let pending = pending.clone(); + window.on_diagnostics_save(move || { + let Some(w) = weak.upgrade() else { return }; + let Some(bundle) = pending.borrow_mut().take() else { + return; + }; + let dir = dr_plat::diagnostics::bundle::default_dir(); + let result = match bundle.write_to(&dir) { + Ok(path) => { + log::info!("diagnostics bundle written: {}", path.display()); + format!("Saved as {}", path.display()) } - }, - ); + Err(e) => { + log::warn!("diagnostics bundle not written: {e}"); + format!("Could not save the bundle to {}: {e}", dir.display()) + } + }; + w.set_diagnostics_preview("".into()); + w.set_diagnostics_result(result.into()); + }); } - - // TRACES: NFR-OPS-1 - // The diagnostics bundle, wired as the two presses the requirement - // describes. Preparing gathers the log and the crash records into memory - // and shows what would be written; saving writes it; discarding drops it - // and writes nothing. The gathered bundle is held between the presses so - // that what is saved is exactly what was shown — a bundle gathered again - // at save time could differ from its own preview by whatever was logged - // while the user was reading. { - use dr_plat::diagnostics::bundle::{Bundle, Facts}; - let facts = Facts { - app_version: env!("CARGO_PKG_VERSION").to_string(), - schema_version: dr_catalog::schema::SCHEMA_VERSION, - graphics: match &gpu { - Some(ctx) => format!( - "{} ({:?}), {}", - ctx.adapter_name(), - ctx.backend(), - ctx.driver() - ), - None => "no GPU".to_string(), - }, - }; - let pending: Rc>> = Rc::new(RefCell::new(None)); - { - let weak = window.as_weak(); - let pending = pending.clone(); - window.on_diagnostics_prepare(move || { - let Some(w) = weak.upgrade() else { return }; - let bundle = Bundle::gather(&facts); - w.set_diagnostics_preview( - bundle - .preview(&dr_plat::diagnostics::bundle::default_dir()) - .into(), - ); - w.set_diagnostics_result("".into()); - *pending.borrow_mut() = Some(bundle); - }); - } - { - let weak = window.as_weak(); - let pending = pending.clone(); - window.on_diagnostics_save(move || { - let Some(w) = weak.upgrade() else { return }; - let Some(bundle) = pending.borrow_mut().take() else { - return; - }; - let dir = dr_plat::diagnostics::bundle::default_dir(); - let result = match bundle.write_to(&dir) { - Ok(path) => { - log::info!("diagnostics bundle written: {}", path.display()); - format!("Saved as {}", path.display()) - } - Err(e) => { - log::warn!("diagnostics bundle not written: {e}"); - format!("Could not save the bundle to {}: {e}", dir.display()) - } - }; - w.set_diagnostics_preview("".into()); - w.set_diagnostics_result(result.into()); - }); - } - { - let weak = window.as_weak(); - window.on_diagnostics_discard(move || { - let Some(w) = weak.upgrade() else { return }; - *pending.borrow_mut() = None; - w.set_diagnostics_preview("".into()); - w.set_diagnostics_result("".into()); - }); - } + let weak = window.as_weak(); + window.on_diagnostics_discard(move || { + let Some(w) = weak.upgrade() else { return }; + *pending.borrow_mut() = None; + w.set_diagnostics_preview("".into()); + w.set_diagnostics_result("".into()); + }); } +} - // Every background job reports here, and this draws the bar across the top - // of the shell and fills the settings page's list. Built before the - // controllers because they take a handle to it: a job that starts during - // startup — the scan a resumed session begins immediately — has to have - // somewhere to report to before it starts, or its first minute is invisible. - let activity = activity::ActivityLog::new(); - activity.attach(&window); - { - let activity = activity.clone(); - window.on_activity_clear_finished(move || activity.clear_finished()); - } - - // Set once `show` exists; see where the library grid is wired below. - #[allow(clippy::type_complexity)] - let open_from_library: Rc>>> = Rc::new(RefCell::new(None)); - - // TRACES: FR-CAT-8 - // The same knot, for the other direction: leaving develop has to persist - // the edit, and the grid's "‹ Library" button is wired before the develop - // session exists to save from. Empty until then, and calling it is a no-op - // rather than a panic — there is nothing open to lose. - #[allow(clippy::type_complexity)] - let leave_develop: Rc>>> = Rc::new(RefCell::new(None)); - - // Before anything binds to a token: the compiled palette is already in - // place, so this only overwrites what style.yaml currently says. - #[cfg(live_style)] - live_style::apply(&window); +/// Construct the library grid, the collections sidebar, the People screen +/// and the settings controller, and show whichever of launch, the local +/// files on the command line, or a remembered library the startup state +/// calls for. +/// +/// Returns the controllers later sections need — `settings`, `library` and +/// `collections` for import, merge, export and presets — plus `identity` and +/// `prefetch`, kept alive by the caller for the life of the window. +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +fn construct_screens( + window: &AppWindow, + paths: &[PathBuf], + activity: &Rc, + gpu: &Option, + open_from_library: &Rc>>>, + leave_develop: &Rc>>>, +) -> ( + Rc, + Rc, + Rc, + Rc, + Rc, +) { + let activity = activity.clone(); + let gpu = gpu.clone(); + let open_from_library = open_from_library.clone(); + let leave_develop = leave_develop.clone(); // The library grid: scan the remote tree into the catalog, then show what // was found. Clicking a cell opens it in develop. @@ -1409,7 +1631,7 @@ pub fn run(paths: Vec) -> Result<()> { let open_from_library = open_from_library.clone(); let leave_develop = leave_develop.clone(); library_ui::wire( - &window, + window, library.clone(), collections.clone(), move |path| { @@ -1444,7 +1666,7 @@ pub fn run(paths: Vec) -> Result<()> { let lib_span = library.clone(); let lib_session = library.clone(); collections_ui::wire( - &window, + window, collections.clone(), library.catalog(), move || { @@ -1473,7 +1695,7 @@ pub fn run(paths: Vec) -> Result<()> { // affordable (FR-CULL-8). let lib_store = library.clone(); identity_ui::wire( - &window, + window, identity.clone(), library.catalog(), activity.clone(), @@ -1520,7 +1742,7 @@ pub fn run(paths: Vec) -> Result<()> { let store_ctl = controller.clone(); let lib = library.clone(); let coll = collections.clone(); - launch_ui::wire(&window, controller.clone(), move |session| { + launch_ui::wire(window, controller.clone(), move |session| { let Some(w) = weak.upgrade() else { return }; log::info!("opening library for {}", session.describe()); library_ui::open(&w, lib.clone(), coll.clone(), &store_ctl.store, session); @@ -1545,7 +1767,7 @@ pub fn run(paths: Vec) -> Result<()> { // ratings and edits waiting beside them. library::migrate_legacy_cache_data(&session); library_ui::open( - &window, + window, library.clone(), collections.clone(), &controller.store, @@ -1556,368 +1778,342 @@ pub fn run(paths: Vec) -> Result<()> { } } - // TRACES: FR-CAT-10 | FR-CAT-11 | FR-NC-7a | FR-NC-7b - // Import: a card into the library, and on to the server. - // - // Wired after the settings controller exists because it shares it — an - // import's options are stored in the same file as everything else, and two - // controllers each holding their own copy would each save over the other. - // - // The account is fetched afresh inside the closure rather than captured: - // this runs once at startup, where a library can be opened, closed and - // re-opened for the whole life of the window. + (library, prefetch, collections, identity, settings) +} + +/// TRACES: FR-CAT-10 | FR-CAT-11 | FR-NC-7a | FR-NC-7b +/// Import: a card into the library, and on to the server. +/// +/// Wired after the settings controller exists because it shares it — an +/// import's options are stored in the same file as everything else, and two +/// controllers each holding their own copy would each save over the other. +/// +/// The account is fetched afresh inside the closure rather than captured: +/// this runs once at startup, where a library can be opened, closed and +/// re-opened for the whole life of the window. +fn wire_import_and_merge( + window: &AppWindow, + activity: &Rc, + gpu: &Option, + library: &Rc, + collections: &Rc, + settings: &Rc, +) { + let activity = activity.clone(); + let gpu = gpu.clone(); + let library = library.clone(); + let collections = collections.clone(); + let settings = settings.clone(); + + // Whether the header offers Import at all. A fact about the platform, + // set once: it cannot change while the window is open, and on Android + // it is false because there is no card to reach and nothing to write + // through (`dr_plat::imports_supported`). + window.set_import_supported(dr_plat::imports_supported()); + + // TRACES: FR-MRG-1 { - // Whether the header offers Import at all. A fact about the platform, - // set once: it cannot change while the window is open, and on Android - // it is false because there is no card to reach and nothing to write - // through (`dr_plat::imports_supported`). - window.set_import_supported(dr_plat::imports_supported()); - - // TRACES: FR-MRG-1 - { - let merge = merge_ui::MergeController::new(activity.clone()); - let library_for_sources = library.clone(); - let collections_for_sources = collections.clone(); - let library_for_context = library.clone(); - let library_for_done = library.clone(); - merge_ui::wire( - &window, - merge, - gpu.clone(), - move || library_for_sources.export_sources(&collections_for_sources.selected()), - move || { - let conn = library_for_context.session()?; - Some(merge_ui::Context { - outbox: export::outbox_dir(&conn.account), - conn, - }) - }, - move |w| { - // Staged beside its sources: upload it now rather than - // on the next sync pass, then look for it, exactly as an - // import does. - drain_outbox(&library_for_done); - w.invoke_library_rescan(); - }, - ); - } - - let import = import_ui::ImportController::new(settings.clone(), activity.clone()); + let merge = merge_ui::MergeController::new(activity.clone()); + let library_for_sources = library.clone(); + let collections_for_sources = collections.clone(); let library_for_context = library.clone(); - let weak = window.as_weak(); - import_ui::wire( - &window, - import, + let library_for_done = library.clone(); + merge_ui::wire( + window, + merge, + gpu.clone(), + move || library_for_sources.export_sources(&collections_for_sources.selected()), move || { let conn = library_for_context.session()?; - Some(import_ui::Context { - catalog: library::catalog_path(&conn.account), - library_label: conn.account.root.clone(), - // The same formats the scan looks for. An import that took - // types the library then ignores would copy files off the - // card that never appear in the grid. - filter: conn.account.format_filter(), - upload: Some(import::Upload { - library: conn.account.root.clone(), - // The same shard store the grid reads and the sync - // pushes, so a thumbnail made during an import is the - // one every other client gets. - thumbs: library::thumbs_dir(&conn.account), - staging: import::staging_dir(&conn.account), - conn, - }), + Some(merge_ui::Context { + outbox: export::outbox_dir(&conn.account), + conn, }) }, - move || { - // The import wrote files into a folder on this machine and, if - // the account allowed it, into the library on the server. Only - // the second is what the grid shows, so this asks for the scan - // that finds them rather than inserting rows itself. - if let Some(w) = weak.upgrade() { - w.invoke_library_rescan(); - } + move |w| { + // Staged beside its sources: upload it now rather than + // on the next sync pass, then look for it, exactly as an + // import does. + drain_outbox(&library_for_done); + w.invoke_library_rescan(); }, ); } + + let import = import_ui::ImportController::new(settings.clone(), activity.clone()); + let library_for_context = library.clone(); + let weak = window.as_weak(); + import_ui::wire( + window, + import, + move || { + let conn = library_for_context.session()?; + Some(import_ui::Context { + catalog: library::catalog_path(&conn.account), + library_label: conn.account.root.clone(), + // The same formats the scan looks for. An import that took + // types the library then ignores would copy files off the + // card that never appear in the grid. + filter: conn.account.format_filter(), + upload: Some(import::Upload { + library: conn.account.root.clone(), + // The same shard store the grid reads and the sync + // pushes, so a thumbnail made during an import is the + // one every other client gets. + thumbs: library::thumbs_dir(&conn.account), + staging: import::staging_dir(&conn.account), + conn, + }), + }) + }, + move || { + // The import wrote files into a folder on this machine and, if + // the account allowed it, into the library on the server. Only + // the second is what the grid shows, so this asks for the scan + // that finds them rather than inserting rows itself. + if let Some(w) = weak.upgrade() { + w.invoke_library_rescan(); + } + }, + ); +} + +/// The settings page: cache usage, the export folder picker, and the +/// callbacks a saved change has to ripple into the library controller. +fn wire_settings_screen( + window: &AppWindow, + settings: &Rc, + library: &Rc, +) { + let settings = settings.clone(); + let library = library.clone(); + + // What the cache actually holds, so the ceiling above it is a figure + // the user can judge rather than an abstract one. + // + // Empty at this point on a launch and that is expected: the catalog it + // queries is being opened on a worker (`library_ui::open_catalog_soon`) + // and is not there yet. The figure is filled in when the settings page + // is opened, which is the only time it is looked at — see the `on_open` + // closure passed to `settings_ui::wire`. + settings.set_usage_label(describe_cache_usage(&library)); + // Rendered once up front so the page is correct the first time it is + // opened, rather than on the second open after a callback has run. + settings_ui::render(window, &settings); + // The export button carries its destination, so it has to be correct + // before the first click rather than after the first settings edit. + refresh_export_label(window, &settings); + + // Apply what is on disk before anything can use it. Without this the + // controller's defaults stand until the user happens to open the + // settings page and change something — so a cache deliberately capped + // at 2 GB last session would spend this one filling to the default. { - // What the cache actually holds, so the ceiling above it is a figure - // the user can judge rather than an abstract one. - // - // Empty at this point on a launch and that is expected: the catalog it - // queries is being opened on a worker (`library_ui::open_catalog_soon`) - // and is not there yet. The figure is filled in when the settings page - // is opened, which is the only time it is looked at — see the `on_open` - // closure passed to `settings_ui::wire`. - settings.set_usage_label(describe_cache_usage(&library)); - // Rendered once up front so the page is correct the first time it is - // opened, rather than on the second open after a callback has run. - settings_ui::render(&window, &settings); - // The export button carries its destination, so it has to be correct - // before the first click rather than after the first settings edit. - refresh_export_label(&window, &settings); + let stored = settings.snapshot(); + library.set_cache_budget(stored.cache.original_budget_bytes); + library.set_keep_opened_originals(stored.cache.keep_opened_originals); + library.set_fetch_ahead(stored.cache.fetch_ahead); + library.set_write_xmp_sidecars(stored.library.write_xmp_sidecars); + library.set_timeline_bars(stored.library.timeline_bars); + library.set_face_model_id(inference::model_id(stored.faces.detector)); + } - // Apply what is on disk before anything can use it. Without this the - // controller's defaults stand until the user happens to open the - // settings page and change something — so a cache deliberately capped - // at 2 GB last session would spend this one filling to the default. - { - let stored = settings.snapshot(); - library.set_cache_budget(stored.cache.original_budget_bytes); - library.set_keep_opened_originals(stored.cache.keep_opened_originals); - library.set_fetch_ahead(stored.cache.fetch_ahead); - library.set_write_xmp_sidecars(stored.library.write_xmp_sidecars); - library.set_timeline_bars(stored.library.timeline_bars); - library.set_face_model_id(inference::model_id(stored.faces.detector)); - } - - // --- the export folder picker ---------------------------------------- - // - // Wired here rather than inside `settings_ui::wire` because listing a - // remote folder needs credentials, and the settings page deliberately - // holds no session — it is reachable before a library is opened and must - // not depend on one existing. - { - let weak = window.as_weak(); - let ctl = settings.clone(); - let library = library.clone(); - - // Every entry point needs the same three things, so they are fetched - // once here rather than at four call sites. - let start = move |ctl: &Rc, - weak: &slint::Weak, - library: &Rc, - path: String| { - match library.session() { - Some(conn) => { - settings_ui::spawn_folder_list(weak.clone(), ctl.clone(), conn, path); - } - None => { - // No account, so nothing to browse. Said plainly rather - // than left as an empty list, which would read as a server - // with no folders on it. - ctl.set_error("Sign in to a library before choosing a folder on it."); - ctl.browser.replace(None); - } - } - }; - - { - let (weak, ctl, library, start) = - (weak.clone(), ctl.clone(), library.clone(), start); - window.on_settings_browse_open_picker(move || { - let Some(w) = weak.upgrade() else { return }; - // Opens on the library root rather than on whatever the - // destination field happens to contain: a half-typed path - // would list nothing and look like a broken picker. - ctl.browser.replace(Some(launch::FolderBrowser { - path: String::new(), - entries: Vec::new(), - loading: true, - })); - start(&ctl, &weak, &library, String::new()); - settings_ui::render(&w, &ctl); - }); - } - - { - let (weak, ctl, library, start) = - (weak.clone(), ctl.clone(), library.clone(), start); - window.on_settings_browse_into(move |name| { - let Some(w) = weak.upgrade() else { return }; - let path = { - let mut browser = ctl.browser.borrow_mut(); - let Some(b) = browser.as_mut() else { return }; - let path = b.child_path(&name); - b.path = path.clone(); - b.entries.clear(); - b.loading = true; - path - }; - start(&ctl, &weak, &library, path); - settings_ui::render(&w, &ctl); - }); - } - - { - let (weak, ctl, library, start) = - (weak.clone(), ctl.clone(), library.clone(), start); - window.on_settings_browse_up(move || { - let Some(w) = weak.upgrade() else { return }; - let path = { - let mut browser = ctl.browser.borrow_mut(); - let Some(b) = browser.as_mut() else { return }; - let Some(path) = b.parent_path() else { return }; - b.path = path.clone(); - b.entries.clear(); - b.loading = true; - path - }; - start(&ctl, &weak, &library, path); - settings_ui::render(&w, &ctl); - }); - } - - { - let (weak, ctl) = (weak.clone(), ctl.clone()); - window.on_settings_browse_confirm(move || { - let Some(w) = weak.upgrade() else { return }; - // The folder being *shown* is the one chosen, matching the - // library picker — so "use this one" means the same thing in - // both places rather than depending on a selection the list - // does not have. - let chosen = ctl.browser.borrow().as_ref().map(|b| b.path.clone()); - if let Some(path) = chosen { - ctl.set_destination(path); - } - ctl.browser.replace(None); - settings_ui::render(&w, &ctl); - refresh_export_label(&w, &ctl); - }); - } - - { - let (weak, ctl) = (weak.clone(), ctl.clone()); - window.on_settings_browse_cancel(move || { - let Some(w) = weak.upgrade() else { return }; - ctl.browser.replace(None); - settings_ui::render(&w, &ctl); - }); - } - } - - let lib = library.clone(); - let ctl = settings.clone(); + // --- the export folder picker ---------------------------------------- + // + // Wired here rather than inside `settings_ui::wire` because listing a + // remote folder needs credentials, and the settings page deliberately + // holds no session — it is reachable before a library is opened and must + // not depend on one existing. + { let weak = window.as_weak(); - settings_ui::wire( - &window, - settings.clone(), - move |s| { - // A ceiling that moved has to be applied to what is already on - // disk, or lowering it would only affect future downloads and the - // cache would sit over budget indefinitely. - lib.set_cache_budget(s.cache.original_budget_bytes); - lib.set_keep_opened_originals(s.cache.keep_opened_originals); - lib.set_fetch_ahead(s.cache.fetch_ahead); - lib.set_write_xmp_sidecars(s.library.write_xmp_sidecars); - if let Some(w) = weak.upgrade() { - refresh_export_label(&w, &ctl); - } + let ctl = settings.clone(); + let library = library.clone(); - // The axis is cut into bars when the window is loaded, so a new - // count only shows once something reloads it. Done here, and only - // when the figure actually moved: a setting that appears to do - // nothing until the user scrolls is one they will change twice and - // then leave wrong. - if lib.set_timeline_bars(s.library.timeline_bars) { - if let Some(w) = weak.upgrade() { - library_ui::reload(&w, &lib); - } + // Every entry point needs the same three things, so they are fetched + // once here rather than at four call sites. + let start = move |ctl: &Rc, + weak: &slint::Weak, + library: &Rc, + path: String| { + match library.session() { + Some(conn) => { + settings_ui::spawn_folder_list(weak.clone(), ctl.clone(), conn, path); } - - // The pipeline the next sync and the next sweep run under, and - // the two lines on the page that describe it: which detector - // file is installed, and how much of the library that - // pipeline has covered — which for a freshly chosen one is - // nothing, and saying so is the point. - lib.set_face_model_id(inference::model_id(s.faces.detector)); - if let Some(w) = weak.upgrade() { - refresh_face_status(&w, &lib, s.faces.detector); + None => { + // No account, so nothing to browse. Said plainly rather + // than left as an empty list, which would read as a server + // with no folders on it. + ctl.set_error("Sign in to a library before choosing a folder on it."); + ctl.browser.replace(None); } - - // Lowering the ceiling evicts, so the figure beside it has just - // changed — leaving the old one would show the cache still over a - // limit that was enforced a moment ago. - ctl.set_usage_label(describe_cache_usage(&lib)); - if let Some(w) = weak.upgrade() { - settings_ui::render(&w, &ctl); - } - }, - { - // Face coverage, read when the page opens. The figures live in the - // catalog and the settings page holds no session, so they arrive - // through here rather than being kept up to date continuously — - // they are only ever looked at while this page is on screen. - let lib = library.clone(); - let ctl = settings.clone(); - move |w: &AppWindow| { - refresh_face_status(w, &lib, ctl.snapshot().faces.detector); - // Here rather than at startup, on exactly the reasoning - // above: the catalog this reads is opened on a worker now, - // so a launch has nothing to describe, and the figure is - // only ever read while this page is on screen. `render` - // again because `settings_ui::wire` renders *before* it - // calls this, and the label is one of the things it draws. - ctl.set_usage_label(describe_cache_usage(&lib)); - settings_ui::render(w, &ctl); - } - }, - ); - } - - window.set_total(entries.borrow().len() as i32); - let index = Rc::new(RefCell::new(0usize)); - // The current develop session, if the file yielded sensor data. - let session: Rc>> = Rc::new(RefCell::new(None)); - - // TRACES: FR-PLAT-AND-5 - // The GPU tier — the first thing given back under memory pressure, and on - // Android the only thing given back merely for going into the background. - // - // `try_borrow_mut` rather than `borrow_mut`, and the miss is not an error - // worth reporting. A memory warning can land in the middle of a render, at - // which point the slot is already borrowed and freeing its textures under - // the code drawing with them is not something to do politely — skipping is - // correct, because the pass that is running will have finished by the time - // the platform asks again, and a warning that has not been acted on is - // always followed by another one. - { - let session = Rc::downgrade(&session); - memory::evict_at(memory::Tier::Gpu, move || { - let Some(session) = session.upgrade() else { - return; - }; - let Ok(mut slot) = session.try_borrow_mut() else { - return; - }; - if let Some(open) = slot.as_mut() { - open.release_gpu_caches(); } - }); + }; + + { + let (weak, ctl, library, start) = (weak.clone(), ctl.clone(), library.clone(), start); + window.on_settings_browse_open_picker(move || { + let Some(w) = weak.upgrade() else { return }; + // Opens on the library root rather than on whatever the + // destination field happens to contain: a half-typed path + // would list nothing and look like a broken picker. + ctl.browser.replace(Some(launch::FolderBrowser { + path: String::new(), + entries: Vec::new(), + loading: true, + })); + start(&ctl, &weak, &library, String::new()); + settings_ui::render(&w, &ctl); + }); + } + + { + let (weak, ctl, library, start) = (weak.clone(), ctl.clone(), library.clone(), start); + window.on_settings_browse_into(move |name| { + let Some(w) = weak.upgrade() else { return }; + let path = { + let mut browser = ctl.browser.borrow_mut(); + let Some(b) = browser.as_mut() else { return }; + let path = b.child_path(&name); + b.path = path.clone(); + b.entries.clear(); + b.loading = true; + path + }; + start(&ctl, &weak, &library, path); + settings_ui::render(&w, &ctl); + }); + } + + { + let (weak, ctl, library, start) = (weak.clone(), ctl.clone(), library.clone(), start); + window.on_settings_browse_up(move || { + let Some(w) = weak.upgrade() else { return }; + let path = { + let mut browser = ctl.browser.borrow_mut(); + let Some(b) = browser.as_mut() else { return }; + let Some(path) = b.parent_path() else { return }; + b.path = path.clone(); + b.entries.clear(); + b.loading = true; + path + }; + start(&ctl, &weak, &library, path); + settings_ui::render(&w, &ctl); + }); + } + + { + let (weak, ctl) = (weak.clone(), ctl.clone()); + window.on_settings_browse_confirm(move || { + let Some(w) = weak.upgrade() else { return }; + // The folder being *shown* is the one chosen, matching the + // library picker — so "use this one" means the same thing in + // both places rather than depending on a selection the list + // does not have. + let chosen = ctl.browser.borrow().as_ref().map(|b| b.path.clone()); + if let Some(path) = chosen { + ctl.set_destination(path); + } + ctl.browser.replace(None); + settings_ui::render(&w, &ctl); + refresh_export_label(&w, &ctl); + }); + } + + { + let (weak, ctl) = (weak.clone(), ctl.clone()); + window.on_settings_browse_cancel(move || { + let Some(w) = weak.upgrade() else { return }; + ctl.browser.replace(None); + settings_ui::render(&w, &ctl); + }); + } } - // TRACES: FR-DEV-6 | FR-CAT-8 - // The settings clipboard, and where the open image's edit is stored. - // - // Both live for the life of the window rather than the view: a copy is - // taken in develop and may be pasted onto a selection back in the grid, so - // a clipboard owned by the develop view would be emptied by the very - // navigation that carries it to its destination. - let clipboard = presets::Clipboard::new(); - let open_image: presets::OpenImage = Rc::new(RefCell::new(presets::Stored::Nowhere)); + let lib = library.clone(); + let ctl = settings.clone(); + let weak = window.as_weak(); + settings_ui::wire( + window, + settings.clone(), + move |s| { + // A ceiling that moved has to be applied to what is already on + // disk, or lowering it would only affect future downloads and the + // cache would sit over budget indefinitely. + lib.set_cache_budget(s.cache.original_budget_bytes); + lib.set_keep_opened_originals(s.cache.keep_opened_originals); + lib.set_fetch_ahead(s.cache.fetch_ahead); + lib.set_write_xmp_sidecars(s.library.write_xmp_sidecars); + if let Some(w) = weak.upgrade() { + refresh_export_label(&w, &ctl); + } - // One model for the lifetime of the window. Rows are mutated in place; - // see `sync_rows` for why replacing it breaks dragging. - let rows: Rc> = Rc::new(slint::VecModel::default()); - window.global::().set_rows(rows.clone().into()); - // Viewport size, tracked so a re-render after a slider move matches it. - // - // In *physical* pixels, which is what the render target wants and what - // FR-DSP-8 means by handling fractional scaling without resampling. The - // one caller that thinks in logical pixels is Slint's resize callback, - // and it converts on the way in. - let viewport = Rc::new(RefCell::new((1024u32, 768u32))); + // The axis is cut into bars when the window is loaded, so a new + // count only shows once something reloads it. Done here, and only + // when the figure actually moved: a setting that appears to do + // nothing until the user scrolls is one they will change twice and + // then leave wrong. + if lib.set_timeline_bars(s.library.timeline_bars) { + if let Some(w) = weak.upgrade() { + library_ui::reload(&w, &lib); + } + } - // TRACES: FR-DSP-8 - // What the displays are, and which one the canvas is on. Probed once here - // so that the About page can describe the session's colour path before any - // photograph is opened — the acquisition path is a property of the desktop - // and not of the image. - let display = display_ui::DisplayWatch::probe(); + // The pipeline the next sync and the next sweep run under, and + // the two lines on the page that describe it: which detector + // file is installed, and how much of the library that + // pipeline has covered — which for a freshly chosen one is + // nothing, and saying so is the point. + lib.set_face_model_id(inference::model_id(s.faces.detector)); + if let Some(w) = weak.upgrade() { + refresh_face_status(&w, &lib, s.faces.detector); + } + + // Lowering the ceiling evicts, so the figure beside it has just + // changed — leaving the old one would show the cache still over a + // limit that was enforced a moment ago. + ctl.set_usage_label(describe_cache_usage(&lib)); + if let Some(w) = weak.upgrade() { + settings_ui::render(&w, &ctl); + } + }, + { + // Face coverage, read when the page opens. The figures live in the + // catalog and the settings page holds no session, so they arrive + // through here rather than being kept up to date continuously — + // they are only ever looked at while this page is on screen. + let lib = library.clone(); + let ctl = settings.clone(); + move |w: &AppWindow| { + refresh_face_status(w, &lib, ctl.snapshot().faces.detector); + // Here rather than at startup, on exactly the reasoning + // above: the catalog this reads is opened on a worker now, + // so a launch has nothing to describe, and the figure is + // only ever read while this page is on screen. `render` + // again because `settings_ui::wire` renders *before* it + // calls this, and the label is one of the things it draws. + ctl.set_usage_label(describe_cache_usage(&lib)); + settings_ui::render(w, &ctl); + } + }, + ); +} + +/// Build the render closure: re-render the current session into the canvas. +/// +/// Called on every slider change, so it must do no more than run the +/// adjust pass — the demosaic is not repeated. +fn build_render_now( + session: &Rc>>, + viewport: &Rc>, + display: &Rc, + chosen_peaking: &Rc>>, +) -> Render { + let session = session.clone(); + let viewport = viewport.clone(); + let display = display.clone(); + let chosen_peaking = chosen_peaking.clone(); - // Re-render the current session into the canvas. - // - // Called on every slider change, so it must do no more than run the - // adjust pass — the demosaic is not repeated. // TRACES: FR-DEV-5 // The history revision the panel was last built from. See the use below: // the list is rebuilt when it would read differently, not when the picture @@ -1929,38 +2125,7 @@ pub fn run(paths: Vec) -> Result<()> { // without a step being taken. let drawn_snapshots: Rc>>> = Rc::new(RefCell::new(None)); - // TRACES: FR-CULL-3 - // How the photographer wants focus peaking drawn, or `None` for off. - // - // **Held here rather than on the session, which is the opposite of where - // every edit lives.** A session is one photograph; peaking is a way of - // *looking* at a folder of them. Someone culling three thousand frames - // switches it on once, and a flag that reset with the session would ask - // them to switch it on three thousand times — which is why - // `reset_view_state` deliberately leaves it alone while emptying the - // histogram beside it. - let chosen_peaking: Rc>> = Rc::new(Cell::new(None)); - - // TRACES: FR-UI-4 - // The point being inspected at 1:1, in fractions of the framed image, or - // `None` where the view is fitted. - // - // **Held here rather than on the session, on exactly the argument - // `chosen_peaking` above makes.** A session is one photograph and this is - // a way of looking at a folder of them: checking the same eye across forty - // portraits is the entire reason a 1:1 view is worth reaching, and a - // magnification that reset with the session would ask for the same zoom - // and the same pan forty times. So `reset_view_state` empties the - // histogram and leaves this alone, and the two open paths below put the - // next photograph under the magnifier the last one was left under. - // - // It is a *viewing* state throughout. It never reaches the graph's crop, - // the sidecar or an export — `Framing::view` is kept out of all three, and - // `render_the_file` suspends it — so a photographer left at 4× exports the - // whole frame, as they did before this existed. - let inspection: Rc> = Rc::new(Cell::new(Inspection::default())); - - let render_now: Render = { + { let session = session.clone(); let viewport = viewport.clone(); let drawn_history = drawn_history.clone(); @@ -2199,29 +2364,34 @@ pub fn run(paths: Vec) -> Result<()> { } } }) - }; + } +} - // **Rendering is decoupled from input, and this is why.** - // - // A render used to be a blocking GPU round-trip — S1 removed the block, - // but not the reason for this, so read it as history that still applies. - // Running one straight from a `moved` handler put that stall *inside* the - // gesture: touch events arrive far faster than a render completes, so the - // input queue backed up, positions arrived stale, and Android — seeing the - // events go unconsumed — reclaimed the gesture and delivered `cancel` - // instead of `up`. That is the dropped-drag bug, and no amount of tuning - // inside the Slint handlers fixes it while the stall is on the input path. - // - // So `redraw` no longer renders. It marks the canvas dirty and posts a - // single render onto the event loop; every further request while one is - // already pending just sets the flag again. A drag emitting forty events - // therefore renders a handful of times instead of forty, and — the part - // that actually fixes the drop — each event handler returns immediately, - // so the gesture is always consumed promptly. - // - // The flag is re-checked *after* the render because parameters may have - // moved again while it ran; that repost is what keeps the image converging - // on the finger rather than settling on a stale frame. +/// Post exactly one render onto the event loop per burst of requests. +/// +/// **Rendering is decoupled from input, and this is why.** +/// +/// A render used to be a blocking GPU round-trip — S1 removed the block, +/// but not the reason for this, so read it as history that still applies. +/// Running one straight from a `moved` handler put that stall *inside* the +/// gesture: touch events arrive far faster than a render completes, so the +/// input queue backed up, positions arrived stale, and Android — seeing the +/// events go unconsumed — reclaimed the gesture and delivered `cancel` +/// instead of `up`. That is the dropped-drag bug, and no amount of tuning +/// inside the Slint handlers fixes it while the stall is on the input path. +/// +/// So `redraw` no longer renders. It marks the canvas dirty and posts a +/// single render onto the event loop; every further request while one is +/// already pending just sets the flag again. A drag emitting forty events +/// therefore renders a handful of times instead of forty, and — the part +/// that actually fixes the drop — each event handler returns immediately, +/// so the gesture is always consumed promptly. +/// +/// The flag is re-checked *after* the render because parameters may have +/// moved again while it ran; that repost is what keeps the image converging +/// on the finger rather than settling on a stale frame. +fn build_redraw(render_now: &Render) -> Rc { + let render_now = render_now.clone(); let render_pending = Rc::new(std::cell::Cell::new(false)); let render_dirty = Rc::new(std::cell::Cell::new(false)); // Set while a settle render is already queued, so a burst of draft frames @@ -2230,7 +2400,7 @@ pub fn run(paths: Vec) -> Result<()> { // Whether a request had to be coalesced into one already queued — see the // gesture note below, where this stands in for a drag boundary. let was_coalesced = Rc::new(std::cell::Cell::new(false)); - let redraw: Rc = { + { let render_now = render_now.clone(); let render_pending = render_pending.clone(); let render_dirty = render_dirty.clone(); @@ -2292,9 +2462,38 @@ pub fn run(paths: Vec) -> Result<()> { }); }); }) - }; + } +} - let show = { +/// Build `show`: load the entry at `index` and put it on screen. +/// +/// The local half of the two load routes view-composition.md describes — +/// `wire_remote_open` is the other, fetching bytes from the server instead of +/// reading a path — and each runs the same post-load sequence independently. +#[allow(clippy::too_many_arguments)] +fn build_show( + entries: &Rc>>, + index: &Rc>, + session: &Rc>>, + redraw: &Rc, + gpu: &Option, + rows: &Rc>, + open_image: &presets::OpenImage, + library: &Rc, + viewport: &Rc>, + inspection: &Rc>, +) -> Rc { + let entries = entries.clone(); + let index = index.clone(); + let session = session.clone(); + let redraw = redraw.clone(); + let gpu = gpu.clone(); + let rows = rows.clone(); + let open_image = open_image.clone(); + let library = library.clone(); + let viewport = viewport.clone(); + let inspection = inspection.clone(); + { let entries = entries.clone(); let index = index.clone(); let session = session.clone(); @@ -2409,14 +2608,43 @@ pub fn run(paths: Vec) -> Result<()> { } } }) - }; + } +} + +/// The grid's paths are *remote*: there is no local file to open, so the +/// click starts a download and the image appears when it lands. That is a +/// whole RAW file over WebDAV, so the wait is real and has to be visible — +/// the status line says so rather than leaving a blank frame. +/// +/// The other half of the two load routes view-composition.md describes; see +/// [`build_show`] for the local one. +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +fn wire_remote_open( + window: &AppWindow, + open_from_library: &Rc>>>, + library: &Rc, + session: &Rc>>, + redraw: &Rc, + rows: &Rc>, + gpu: &Option, + activity: &Rc, + open_image: &presets::OpenImage, + viewport: &Rc>, + inspection: &Rc>, + prefetch: &Rc, +) { + let open_from_library = open_from_library.clone(); + let library = library.clone(); + let session = session.clone(); + let redraw = redraw.clone(); + let rows = rows.clone(); + let gpu = gpu.clone(); + let activity = activity.clone(); + let open_image = open_image.clone(); + let viewport = viewport.clone(); + let inspection = inspection.clone(); + let prefetch = prefetch.clone(); - // Now `show` exists, close the knot left open at the library wiring. - // - // The grid's paths are *remote*: there is no local file to open, so the - // click starts a download and the image appears when it lands. That is a - // whole RAW file over WebDAV, so the wait is real and has to be visible — - // the status line says so rather than leaving a blank frame. { let weak = window.as_weak(); let library = library.clone(); @@ -2650,1011 +2878,21 @@ pub fn run(paths: Vec) -> Result<()> { ); })); } +} - // ---- copying settings between photographs (FR-DEV-6) ---------------- - // - // Wired after the develop session and the library both exist, because a - // paste reaches both: onto the image on screen, or onto the grid's - // selection through its sidecars. - { - presets::wire( - &window, - clipboard.clone(), - presets::Develop { - session: session.clone(), - rows: rows.clone(), - redraw: redraw.clone(), - open: open_image.clone(), - }, - settings.clone(), - library.clone(), - collections.clone(), - ); - - // TRACES: FR-DEV-6 - // The saved half of the same requirement, wired from the same bundle: - // applying a named preset to the open image is the paste path with a - // different source. - presets::wire_named( - &window, - presets::NamedPresets::open(), - presets::Develop { - session: session.clone(), - rows: rows.clone(), - redraw: redraw.clone(), - open: open_image.clone(), - }, - settings.clone(), - library.clone(), - collections.clone(), - ); - - // Close the knot left open beside `open_from_library`: the grid's - // "‹ Library" button was wired before there was a session to save. - let weak = window.as_weak(); - let open_image = open_image.clone(); - let session = session.clone(); - let library = library.clone(); - *leave_develop.borrow_mut() = Some(Rc::new(move || { - let Some(w) = weak.upgrade() else { return }; - presets::save_open_edit(&w, &open_image.borrow(), &session, &library); - })); - - // TRACES: FR-DEV-6 - // The scope chips, shared by the preset sheet and the settings page. - // Wired once and rendered once: both surfaces draw one set, because - // "what does a paste carry" is one question about how this - // photographer works rather than one per place it is asked. - presets::wire_scope(&window, settings.clone(), clipboard.clone()); - presets::render_scope(&window, &settings); - - // TRACES: FR-UI-1 - // Rendered from the same settings controller and at the same moment, - // so the rail and the strip cannot both be on screen after a change to - // the preference that chooses between them. - window - .global::() - .set_groups_in_rail(groups_in_rail(&settings)); - presets::render(&window, &clipboard, &settings); - } - - // ---- Adjustment callbacks ------------------------------------------ - // - // Generic by construction: they carry indices into the capability list, - // so adding an operation needs no change here (FR-DEV-3c). - // --- export (FR-EXP-6, FR-EXP-7, FR-EXP-9) --------------------------- - // - // Two buttons, one worker. The develop view exports the image on screen and - // the grid exports its selection; the only difference between them is who - // renders the frames, so both hand a `Vec` to the same batch and - // both report through the same activity row. - // - // A frame is always rendered at full resolution rather than taken from the - // canvas: the display render is deliberately viewport-sized (FR-DSP-1), and - // exporting that would hand the user a soft, screen-sized file with no - // indication anything had been lost (FR-EXP-9). - { - // Replaced at each start, so the button always cancels the run it is - // sitting on and a stale token cancels nothing. - let cancel: Rc> = Rc::new(RefCell::new(export::Cancel::default())); - // Holds the drain. Assigning a new timer drops the previous one, which - // is what takes a superseded batch's row out of the register. - let drain: Rc>> = Rc::new(RefCell::new(None)); - - let start = { - let settings = settings.clone(); - let library = library.clone(); - let activity = activity.clone(); - let gpu = gpu.clone(); - let cancel = cancel.clone(); - let drain = drain.clone(); - Rc::new( - move |window: &AppWindow, sources: Vec, to: export::Reporting| { - let total = sources.len(); - let request = batch_request(sources, &settings, &library, gpu.as_ref()); - - let token = export::Cancel::default(); - *cancel.borrow_mut() = token.clone(); - - let rx = export::spawn_batch(request, token); - - // Upload the moment the batch is done rather than waiting - // for a sync pass. An export bound for the server is - // complete on disk the instant it is staged, but it is not - // where the user asked for it until this runs — and - // "Queued for Exports" sitting unchanged until somebody - // presses Sync reads as an export that did not upload. - let library_for_drain = library.clone(); - export::drain_batch( - window.as_weak(), - &activity, - &drain, - rx, - total, - to, - move || drain_outbox(&library_for_drain), - ); - }, - ) - }; - - { - let weak = window.as_weak(); - let session = session.clone(); - let settings = settings.clone(); - let start = start.clone(); - window.on_export_image(move || { - let Some(w) = weak.upgrade() else { return }; - // One batch at a time, and the grid's counts as one. The two - // buttons share a worker slot, so starting a second run would - // drop the first's drain — leaving a batch still writing files - // with no progress row and a button that never comes back. - if w.get_export_busy() || w.get_library_exporting() { - w.set_export_status("An export is already running".into()); - return; - } - w.set_export_busy(true); - // Set before the render rather than after: the render is the - // one part still on this thread, so a label written afterwards - // would never be drawn in the "Rendering…" state at all. - w.set_export_status("Rendering…".into()); - - match render_open_frame(&w, &session, settings.snapshot().export.colour_space) { - Ok(source) => start(&w, vec![source], export::Reporting::Develop), - Err(e) => { - w.set_export_status(format!("Export failed: {e}").into()); - w.set_export_busy(false); - } - } - refresh_export_label(&w, &settings); - }); - } - - // TRACES: FR-EXP-7 - // The grid's selection. Nothing is rendered here — the worker fetches, - // decodes and renders each photograph itself, so this returns to the - // event loop immediately and a batch of three hundred is a progress bar - // rather than a frozen window. - { - let weak = window.as_weak(); - let library = library.clone(); - let collections = collections.clone(); - let start = start.clone(); - window.on_library_export_selection(move || { - let Some(w) = weak.upgrade() else { return }; - // See the develop button above for why one run excludes the - // other. The button is a cancel by then, so this only catches - // a batch started from develop and left running. - if w.get_library_exporting() || w.get_export_busy() { - w.set_library_status("An export is already running".into()); - return; - } - - let sources = library.export_sources(&collections.selected()); - if sources.is_empty() { - // Said out loud rather than ignored, matching what a paste - // or a judgement keystroke does with an empty selection. - w.set_library_status("Select an image first".into()); - return; - } - - w.set_library_exporting(true); - w.set_library_status(format!("Exporting {} images…", sources.len()).into()); - start(&w, sources, export::Reporting::Library); - }); - } - - // TRACES: NFR-ARCH-3 - { - let weak = window.as_weak(); - window.on_library_cancel_export(move || { - cancel.borrow().cancel(); - if let Some(w) = weak.upgrade() { - // The worker stops at the next point it is safe to — which - // may be a frame away — so the button says "asked for" and - // not "done". The drain writes the real answer. - w.set_library_status("Cancelling the export…".into()); - } - }); - } - } - - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window - .global::() - .on_param_changed(move |op, param, value| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.set_param(op, param, value); - // TRACES: FR-DEV-3f - // The film's own exposures ride *inside* the baked tables - // rather than arriving as uniforms, because the print balance - // is solved against them — an enlarger's filtration depends on - // how the negative was exposed. So moving one has to rebuild - // the lookup, which no other slider in the panel does. - s.rebake_film_if_affected(op); - } - sync_rows(&w, &rows, &session); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window - .global::() - .on_param_reset(move |op, param| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.reset_param(op, param); - } - sync_rows(&w, &rows, &session); - redraw(&w); - }); - } - // TRACES: FR-DEV-3 - // The local-adjustment panel. Wired as a block rather than inline because - // it is a dozen callbacks that all say the same three things, and they - // read better beside each other than scattered through this function. - { - // Choosing a group re-filters the panel and nothing else — no edit, - // no render. It is navigation. - let weak = window.as_weak(); - let session = session.clone(); - let rows = rows.clone(); - window.global::().on_tab_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.set_active_tab(index); - } - sync_rows(&w, &rows, &session); - // The stock picker is filtered by group like everything else, and - // it is not a row — so the thing that decides whether it is on - // screen has to be recomputed here as well, or it would answer for - // whichever group happened to be open when the image was loaded. - sync_film(&w, &session); - }); - } - - masks_ui::wire(&window, &session, &rows, &redraw); - spots_ui::wire(&window, &session, redraw.clone()); - - // A way to land on the Identity Manager at startup, for looking at it - // without a mouse. Off unless the variable is set, so it costs a getenv - // per launch and changes nothing otherwise. - if std::env::var_os("DARKROOM_START_IDENTITY").is_some() { - window.invoke_identity_open(); - } - if std::env::var_os("DARKROOM_START_SETTINGS").is_some() { - window.invoke_settings_open(); - } - - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_reset_all(move || { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.reset_all(); - } - sync_rows(&w, &rows, &session); - redraw(&w); - }); - } - { - // A curve is one control spanning many parameters, so resetting it - // clears all of them at once — resetting a single point would leave - // a shape the user did not ask for. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_curve_reset(move |op| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.reset_curve(op); - } - sync_rows(&w, &rows, &session); - redraw(&w); - }); - } - { - // Which of the curve's curves the plot is showing. **No redraw**, and - // that is the whole character of this control: it changes no - // parameter, so the photograph is already correct on screen and - // recomputing it would be a frame spent to produce the same pixels. - // For the same reason it records no history step — there is nothing - // to undo — and the sidecar never hears about it. - let weak = window.as_weak(); - let session = session.clone(); - let rows = rows.clone(); - window - .global::() - .on_curve_channel_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.set_curve_channel(index); - } - sync_rows(&w, &rows, &session); - }); - } - - // ---- the film stock (FR-DEV-3f) ------------------------------------- - // - // Unlike the curve channel above, both of these change the photograph: - // choosing a stock *is* the edit. So they redraw, and they leave the - // session dirty for the sidecar in the ordinary way. - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_film_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - // Index zero is "None" — see `DevelopSession::film_choices`, which - // puts it first so that the neutral choice needs no sentinel. - let choices = DevelopSession::film_choices(); - let Some((stock, _)) = choices.get(index.max(0) as usize) else { - return; - }; - if let Some(s) = session.borrow_mut().as_mut() { - // Printed by default when the stock has a paper: a colour - // negative that has not been printed is an orange strip, and - // offering that as the first thing a photographer sees when - // they pick Portra would read as a bug rather than as a - // choice. The toggle is there for anyone who wants the scan. - let print = stock - .and_then(dr_film::find) - .and_then(dr_film::default_print) - .is_some(); - s.pick_film(*stock, print); - } - sync_rows(&w, &rows, &session); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window - .global::() - .on_film_print_toggled(move |print| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) { - s.pick_film(Some(&stock), print); - } - } - sync_film(&w, &session); - redraw(&w); - }); - } - - // ---- undo and redo (FR-DEV-5) --------------------------------------- - // - // Thin, because the history lives in the session and every mutator there - // records into it — see `DevelopSession::history`. What is left for the - // interface is the refresh a paste also needs: the controls are showing - // values that have just moved underneath them. - // - // `can-undo` and `can-redo` are not set here; `render_now` pushes them on - // every redraw, which is every path that can change them. - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_undo(move || { - let Some(w) = weak.upgrade() else { return }; - let stepped = session.borrow_mut().as_mut().is_some_and(|s| s.undo()); - if stepped { - sync_rows(&w, &rows, &session); - redraw(&w); - } - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_redo(move || { - let Some(w) = weak.upgrade() else { return }; - let stepped = session.borrow_mut().as_mut().is_some_and(|s| s.redo()); - if stepped { - sync_rows(&w, &rows, &session); - redraw(&w); - } - }); - } - { - // TRACES: FR-DEV-7 - // Holding the comparison, and letting it go. - // - // **No rows are synced and no history is touched**, and both absences - // are the feature. The panel is describing the edit the photographer - // still has; only the canvas changes, and it changes back. This is the - // whole difference between a comparison and the undo-look-redo that - // had to stand in for one — that put two real steps on the stack at - // the moment somebody was least sure of what they were doing. - // - // A full frame rather than a draft: comparing a half-resolution - // original against a sharp edit would show a difference the edit does - // not have, which is the one thing this must not do. - let weak = window.as_weak(); - let render_now = render_now.clone(); - window.on_compare_original(move |on| { - let Some(w) = weak.upgrade() else { return }; - // A held key repeats. Answering a repeat with a re-render would - // spend a full-resolution pass per keystroke to arrive back at the - // frame already on screen. - if w.get_showing_original() == on { - return; - } - w.set_showing_original(on); - render_now(&w, false); - }); - } - { - // TRACES: FR-DEV-3 - // A neutral picked off the photograph. - // - // The operation is not named here and is not passed in: the click - // carries a point, the session turns it into a colour, and - // `dr_pipeline::neutral` finds the operation that asked to be driven - // by one. This callback's whole contribution is that a click on the - // canvas is a click on the canvas. - // - // The rows are re-synced because the sample moved parameters the panel - // is showing — two of them, from one press, which is exactly the case - // a paste and an undo already go through this path for. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.on_neutral_picked(move |x, y| { - let Some(w) = weak.upgrade() else { return }; - let sampled = session - .borrow_mut() - .as_mut() - .is_some_and(|s| s.sample_neutral(x, y)); - // Nothing to redraw otherwise: the probe renders to its own - // target and a sample that found nothing usable moved nothing. - if sampled { - sync_rows(&w, &rows, &session); - redraw(&w); - } - }); - } - - // TRACES: FR-DEV-5 | FR-DEV-7 - // Clicking a row. Arriving six steps away costs what arriving from one - // does, because a step is a whole state — see `History::go_to`. - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - let stepped = session - .borrow_mut() - .as_mut() - .is_some_and(|s| s.go_to_history(index)); - if stepped { - sync_rows(&w, &rows, &session); - redraw(&w); - } - }); - } - // TRACES: FR-DEV-5 - // Named snapshots. Taking and deleting one change the list and nothing - // else — no rows, no history — so they redraw only to push the list, - // through the same path everything else pushes it on. Restoring one is - // the paste's shape: the whole edit moves, so the rows resync and the - // picture is redrawn. - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.global::().on_snapshot_taken(move |name| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.take_snapshot(&name); - } - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.global::().on_snapshot_restored(move |id| { - let Some(w) = weak.upgrade() else { return }; - let restored = session - .borrow_mut() - .as_mut() - .is_some_and(|s| s.restore_snapshot(&id)); - if restored { - sync_rows(&w, &rows, &session); - // The mask panel too: a snapshot carries its layers, and - // the redraw syncs the repairs and the overlay but not the - // rows the layers are listed in. - masks_ui::sync(&w, &session); - redraw(&w); - } - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.global::().on_snapshot_removed(move |id| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.delete_snapshot(&id); - } - redraw(&w); - }); - } - { - // TRACES: FR-DEV-7 - // Holding a snapshot against the edit, and letting it go — the - // Before button's shape, and its rule: no rows are synced and no - // history is touched. A full frame rather than a draft, for the - // reason the original is: a soft comparison shows a difference the - // edit does not have. - let weak = window.as_weak(); - let session = session.clone(); - let render_now = render_now.clone(); - window - .global::() - .on_snapshot_compared(move |id, down| { - let Some(w) = weak.upgrade() else { return }; - let changed = session - .borrow_mut() - .as_mut() - .is_some_and(|s| s.compare_snapshot(down.then_some(id.as_str()))); - if changed { - render_now(&w, false); - } - }); - } - - // ---- zoom, pan and crop --------------------------------------------- - // - // Zoom and pan are viewing state and touch no parameter, so unlike the - // handlers above they do not `sync_rows`. - - // TRACES: FR-DEV-3 - // **The ratio lock belongs to the tool, not to the photograph.** A - // `DevelopSession` is built per image, so holding the lock there would - // reset it at every step through a shoot — and cropping a set of frames to - // one shape is precisely when the lock earns its place. It lives here, for - // the life of the window, and is re-applied to whichever session is open. - // - // It is deliberately *not* an edit. Nothing about it reaches the sidecar: - // what is saved is the rectangle it produced, which is the whole of what - // the pipeline needs and the whole of what another application could read. - let crop_aspect = Rc::new(Cell::new(develop::CropAspect::default())); - let crop_portrait = Rc::new(Cell::new(false)); - window.global::().set_aspects( - develop::CropAspect::CHOICES - .iter() - .map(|a| slint::SharedString::from(a.label())) - .collect::>() - .as_slice() - .into(), - ); - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.on_zoom_at(move |factor, at_x, at_y| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.zoom_about(factor, at_x, at_y); - } - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.on_pan_by(move |dx, dy| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.pan_by(dx, dy); - } - redraw(&w); - }); - } - { - // TRACES: FR-UI-4 - // Fit and 1:1, and the memory of where 1:1 was pointing. - // - // The remembered point is written from what the session *landed* on - // rather than from the coordinates that arrived, so a point near an - // edge is stored where the view was clamped to. Storing the request - // instead would carry a point the previous photograph could not show - // to a photograph that also cannot show it, and the drift would - // accumulate over a folder. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let viewport = viewport.clone(); - let inspection = inspection.clone(); - window.on_inspect_toggled(move |at_x, at_y| { - let Some(w) = weak.upgrade() else { return }; - let (vw, vh) = *viewport.borrow(); - if let Some(s) = session.borrow_mut().as_mut() { - // A negative anchor is a control with no point of its own — - // the readout button, the keyboard — asking for the last one. - // Resolved here rather than in Slint because this is where - // the memory lives. - // - // The remembered point is in fractions of the framed image and - // the session wants fractions of the *visible* area, which are - // the same thing only while the view is fitted. That is - // exactly when this branch can move the view: from a zoomed - // view the toggle goes out to fit and ignores its anchor - // entirely. - let held = inspection.get(); - let (ax, ay) = if at_x < 0.0 || at_y < 0.0 { - held.at - } else { - (at_x, at_y) - }; - inspection.set(match s.toggle_inspection(ax, ay, vw, vh) { - Some(at) => Inspection { at, on: true }, - // The aim is kept, only switched off. See `Inspection`. - None => Inspection { on: false, ..held }, - }); - } - redraw(&w); - }); - } - { - // TRACES: FR-UI-5 - // **Entering a mode is a side effect, which is why Rust owns it** and - // the strip does not simply write the property. Each of the three has - // work to do that the interface cannot see: - // - // *Crop* drops the zoom. The handles are placed against the whole - // frame, and a zoomed view would put most of that frame off screen - // where it cannot be dragged. - // - // *Local* turns the region overlay on. It used to be a button in the - // masking panel, so the mode could be open with the overlay off — - // which is a mode you have entered that is doing nothing. - // - // *Leaving* clears the selection, and that is the fault this whole - // pass exists for: a selected layer silently re-points thirty sliders - // at that layer's chain, so a mode you have left must not leave one - // behind. After this the controls are unambiguously global again, - // which is what the panel's heading then says. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let rows = rows.clone(); - window.on_mode_picked(move |mode| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - match mode { - ViewMode::Crop => { - s.reset_zoom(); - let c = s.crop(); - w.set_crop_x(c.x); - w.set_crop_y(c.y); - w.set_crop_w(c.width); - w.set_crop_h(c.height); - } - ViewMode::Local => s.set_overlay(true), - // TRACES: FR-DEV-8 - // Nothing to arm: the circles are drawn whenever there are - // repairs, and what the mode changes is whether a click on - // the photograph makes another one. Leaving the mask - // selection behind would re-point the column at a layer's - // chain while the canvas is showing repairs, which is the - // fault this whole strip exists to prevent. - ViewMode::Spots => { - s.set_overlay(false); - s.set_active_mask(None); - } - ViewMode::Photo => { - s.set_overlay(false); - s.set_active_mask(None); - // A repair stays on the photograph; only the *selection* - // goes, so the source circle does not hang about over a - // frame nobody is repairing any more. - s.select_spot(None); - } - } - } - w.global::().set_view_mode(mode); - masks_ui::sync(&w, &session); - if let Some(s) = session.borrow().as_ref() { - spots_ui::sync_handles(&w, s); - } - // The scope may have just changed, so the panel below is now - // describing a different chain. - sync_rows(&w, &rows, &session); - redraw(&w); - }); - } - { - // The rect arrives raw from the drag; the session normalises it, holds - // it to whatever ratio is locked, and the properties are written back - // from what it actually stored. That round trip is what makes an - // over-drag slide along the edge rather than letting the overlay and - // the pipeline disagree — and it is what lets the lock reshape a drag - // without the overlay having to know a ratio exists. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let crop_aspect = crop_aspect.clone(); - let crop_portrait = crop_portrait.clone(); - window.on_crop_changed(move |x, y, width, height, hx, hy| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - let rect = dr_pipeline::CropRect { - x, - y, - width, - height, - }; - // The overlay reports the corner it is *holding*; the point - // that must not move is the opposite one. A move reports no - // corner at all, and keeps the shape it already has — there is - // nothing to reshape, and reshaping about a centre would drag - // an over-moved rect smaller instead of sliding it along the - // edge. - if hx < 0.0 { - s.set_crop(rect); - } else { - s.set_crop_locked( - rect, - crop_aspect.get(), - crop_portrait.get(), - (1.0 - hx, 1.0 - hy), - ); - } - let c = s.crop(); - w.set_crop_x(c.x); - w.set_crop_y(c.y); - w.set_crop_w(c.width); - w.set_crop_h(c.height); - w.global::().set_modified(s.framing_edits_image()); - } - redraw(&w); - }); - } - { - // TRACES: FR-DEV-3 - // Choosing a ratio reshapes the crop there and then rather than - // waiting for the next drag. A lock that only took effect on the - // following gesture would leave the chip lit over a rect that is not - // that shape, which is the panel lying about the image. - // - // Held about the rect's centre, so the composition stays where it is - // and the shape changes around it. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let crop_aspect = crop_aspect.clone(); - let crop_portrait = crop_portrait.clone(); - window.global::().on_aspect_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - let Some(&aspect) = develop::CropAspect::CHOICES.get(index.max(0) as usize) else { - return; - }; - crop_aspect.set(aspect); - // A ratio with no second orientation cannot stay stood on its - // short edge, or the switch would be off and the crop upright. - if !aspect.has_orientation() { - crop_portrait.set(false); - } - sync_crop_aspect(&w, &crop_aspect, &crop_portrait); - apply_crop_aspect(&w, &session, &crop_aspect, &crop_portrait); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let crop_aspect = crop_aspect.clone(); - let crop_portrait = crop_portrait.clone(); - window.global::().on_portrait_toggled(move || { - let Some(w) = weak.upgrade() else { return }; - if !crop_aspect.get().has_orientation() { - return; - } - crop_portrait.set(!crop_portrait.get()); - sync_crop_aspect(&w, &crop_aspect, &crop_portrait); - apply_crop_aspect(&w, &session, &crop_aspect, &crop_portrait); - redraw(&w); - }); - } - - // ---- rotation, flips and straightening ------------------------------- - // - // Framing edits, so unlike zoom and pan they mark the image modified — but - // they are reached through named session actions rather than through a row - // index, so they do not `sync_rows` either. `sync_framing` is what carries - // the applied value back. - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - let crop_aspect = crop_aspect.clone(); - let crop_portrait = crop_portrait.clone(); - window.global::().on_rotate(move |turns| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.rotate_quarters(turns); - } - // TRACES: FR-DEV-3 - // The crop travels with the frame, so a rect locked to 16:9 comes - // out of the turn at 9:16. The switch has to agree, or the next - // drag would snap it back upright and undo what the turn did to - // the composition. An even number of turns lands where it started. - let aspect = crop_aspect.get(); - if aspect.turns_with_the_frame() && turns.rem_euclid(2) != 0 { - crop_portrait.set(!crop_portrait.get()); - sync_crop_aspect(&w, &crop_aspect, &crop_portrait); - } - sync_framing(&w, &session); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.global::().on_flip_h_toggled(move || { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.toggle_flip_h(); - } - sync_framing(&w, &session); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.global::().on_flip_v_toggled(move || { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.toggle_flip_v(); - } - sync_framing(&w, &session); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.global::().on_angle_changed(move |degrees| { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.set_angle(degrees); - } - sync_framing(&w, &session); - redraw(&w); - }); - } - { - // TRACES: FR-DEV-3 - // **The end of a straightening gesture crops away the corners it - // exposed.** A free angle does not change the output size, by design — - // that is what leaves the frame where the user put it while the slider - // moves — so the corners of the finished photograph would otherwise be - // black wedges of undefined area. - // - // Only on release, and only on release. Doing it per frame ratchets - // the crop down through every angle the slider passed through rather - // than the one it stopped at; see `auto_crop_to_angle`. - // - // Hung off the track's `committed`, not off `engaged-changed`. The - // latter is *hover* — it has to be, because a `Flickable` withholds - // the press — so a correction keyed on it fires when the pointer first - // crosses the track, whether or not anything was dragged, and then - // never fires again for as long as the pointer stays on it. Which - // reads, exactly, as an auto-crop that works once. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window - .global::() - .on_angle_committed(move |_degrees| { - // The angle itself is already applied: `SliderTrack` emits its - // last `changed` before it commits, so `on_straighten_changed` has - // run with this very value. What is left is the correction that - // has to happen exactly once. - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.auto_crop_to_angle(); - } - sync_framing(&w, &session); - redraw(&w); - }); - } - { - // The geometry section's reset: crop, angle, rotation and flips back - // to neutral, leaving every colour adjustment where it is. The panel's - // own reset-all is the one that clears everything. - let weak = window.as_weak(); - let session = session.clone(); - let redraw = redraw.clone(); - window.global::().on_reset(move || { - let Some(w) = weak.upgrade() else { return }; - if let Some(s) = session.borrow_mut().as_mut() { - s.reset_framing(); - } - sync_framing(&w, &session); - redraw(&w); - }); - } - - { - let weak = window.as_weak(); - let index = index.clone(); - let entries = entries.clone(); - let show = show.clone(); - window.on_next_image(move || { - let Some(w) = weak.upgrade() else { return }; - let len = entries.borrow().len(); - if len == 0 { - return; - } - // Read, then write — `*x.borrow_mut() = *x.borrow() + 1` holds - // both borrows at once and panics. - let next = { - let cur = *index.borrow(); - (cur + 1) % len - }; - *index.borrow_mut() = next; - show(&w); - }); - } - { - let weak = window.as_weak(); - let index = index.clone(); - let entries = entries.clone(); - let show = show.clone(); - window.on_prev_image(move || { - let Some(w) = weak.upgrade() else { return }; - let len = entries.borrow().len(); - if len == 0 { - return; - } - let prev = { - let cur = *index.borrow(); - if cur == 0 { - len - 1 - } else { - cur - 1 - } - }; - *index.borrow_mut() = prev; - show(&w); - }); - } +/// The window chrome shared by every view: canvas resize (which reaches the +/// develop viewport), which display the window is on, the layout class that +/// picks compact versus expanded, the two collapsible columns, and the +/// platform's back gesture. +fn wire_shell( + window: &AppWindow, + viewport: &Rc>, + redraw: &Rc, + display: &Rc, +) { + let viewport = viewport.clone(); + let redraw = redraw.clone(); + let display = display.clone(); // Track the canvas size so the adjust pass renders at viewport // resolution rather than sensor resolution (FR-DSP-1). @@ -3682,79 +2920,9 @@ pub fn run(paths: Vec) -> Result<()> { }); } - // TRACES: FR-CULL-3 - // The peaking switch and its two choices. - // - // All three write `chosen_peaking` and then redraw, because the marks are - // produced by a compute pass over the rendered frame: there is nothing the - // interface can change about the overlay that does not require the frame - // to be measured again. Turning peaking *off* redraws for the same reason - // — that render is what drops the overlay textures and clears the flag. - { - let weak = window.as_weak(); - let chosen = chosen_peaking.clone(); - let redraw = redraw.clone(); - window.global::().on_toggled(move |on| { - let Some(w) = weak.upgrade() else { return }; - // Built from the chips as they currently stand rather than from a - // remembered value: they are what the photographer can see, and an - // overlay that came back in a configuration the panel is not - // showing would be the panel lying about itself. - let next = on.then(|| dr_gpu::FocusPeaking { - sensitivity: peaking::sensitivity(w.global::().get_sensitivity()), - colour: peaking::colour(w.global::().get_colour()), - }); - chosen.set(next); - w.global::().set_showing(next.is_some()); - redraw(&w); - }); - } - { - let weak = window.as_weak(); - let chosen = chosen_peaking.clone(); - let redraw = redraw.clone(); - window - .global::() - .on_sensitivity_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - w.global::().set_sensitivity(index); - // Only reachable while peaking is on — the chips are not drawn - // otherwise — but written as a conditional rather than an - // `expect`, because a panel is free to change its mind about that - // and nothing here should fall over when it does. - if let Some(mut current) = chosen.get() { - current.sensitivity = peaking::sensitivity(index); - chosen.set(Some(current)); - redraw(&w); - } - }); - } - { - let weak = window.as_weak(); - let chosen = chosen_peaking.clone(); - let redraw = redraw.clone(); - window.global::().on_colour_picked(move |index| { - let Some(w) = weak.upgrade() else { return }; - w.global::().set_colour(index); - if let Some(mut current) = chosen.get() { - current.colour = peaking::colour(index); - chosen.set(Some(current)); - redraw(&w); - } - }); - } - // The chips open on whatever the vocabulary calls its default, so the - // panel and the pass agree before anything has been pressed. - window - .global::() - .set_sensitivity(peaking::sensitivity_index(Default::default())); - window - .global::() - .set_colour(peaking::colour_index(Default::default())); - // TRACES: FR-DSP-8 | FR-DSP-6 // And which display that canvas is on, from now until the window closes. - display_ui::attach(&window, &display, &viewport, redraw.clone()); + display_ui::attach(window, &display, &viewport, redraw.clone()); // FR-UI-1: layout class from window width. Computed here rather than in // Slint because a property that both derives from and feeds the layout is @@ -3785,7 +2953,7 @@ pub fn run(paths: Vec) -> Result<()> { let size = window.window().size(); let scale = window.window().scale_factor().max(0.01); apply_layout_class( - &window, + window, size.width as f32 / scale, size.height as f32 / scale, &panels, @@ -3796,7 +2964,7 @@ pub fn run(paths: Vec) -> Result<()> { // reads zero. `window_metrics_level` is what decides whether that is // worth `info`; here it will not be, and the first resize carries the // measurement instead. - log_window_metrics(&window, window_metrics_level(&window, &reported_geometry)); + log_window_metrics(window, window_metrics_level(window, &reported_geometry)); } // FR-UI-2: the two collapsible columns, opened and closed by hand. @@ -3831,22 +2999,6 @@ pub fn run(paths: Vec) -> Result<()> { back_one_step(&w) }); } - - if !entries.borrow().is_empty() { - show(&window); - } - - // The figure that matters, because it is the one the input dispatcher is - // counting against. Anything approaching five seconds here is an ANR on the - // next Android launch whatever the phases above say, and anything that - // pushes it up belongs on a worker. - log::info!( - "startup: {} ms to the event loop", - launch_began.elapsed().as_millis() - ); - - window.run()?; - Ok(()) } /// TRACES: FR-NC-6a