diff --git a/core/dr-pipeline/presets/bw_film.drpl b/core/dr-pipeline/presets/bw_film.drpl new file mode 100644 index 0000000..22d3e88 --- /dev/null +++ b/core/dr-pipeline/presets/bw_film.drpl @@ -0,0 +1,37 @@ +drpl 1 + +# Black and white film: each preset is one measured stock, developed and — for a +# negative — printed on the paper its profile names, with every other +# control left where it was. The stock is the look; a grade on top is the +# photographer's to add. +# +# The measurements are spektrafilm's (Andrea Volpato, CC BY-SA 4.0), as +# converted in core/dr-film/profiles. A preset names a stock by id, so +# these lines are that attribution's reach: nothing of the data is here. + +[preset Ilford Delta 100] +film = ilford_delta_100 +film_print = kodak_2302 + +[preset Ilford Delta 400] +film = ilford_delta_400 +film_print = kodak_2302 + +[preset Ilford FP4 Plus] +film = ilford_fp4_plus +film_print = kodak_2302 + +[preset Ilford HP5 Plus] +film = ilford_hp5_plus +film_print = kodak_2302 + +[preset Ilford Pan F Plus] +film = ilford_pan_f_plus +film_print = kodak_2302 + +[preset Kodak Double-X 5222] +film = kodak_doublex +film_print = kodak_2302 + +[preset Kodak Tri-X Reversal 7266] +film = kodak_trix diff --git a/core/dr-pipeline/presets/cinema_film.drpl b/core/dr-pipeline/presets/cinema_film.drpl new file mode 100644 index 0000000..6fdfba5 --- /dev/null +++ b/core/dr-pipeline/presets/cinema_film.drpl @@ -0,0 +1,30 @@ +drpl 1 + +# Cinema film: each preset is one measured stock, developed and — for a +# negative — printed on the paper its profile names, with every other +# control left where it was. The stock is the look; a grade on top is the +# photographer's to add. +# +# The measurements are spektrafilm's (Andrea Volpato, CC BY-SA 4.0), as +# converted in core/dr-film/profiles. A preset names a stock by id, so +# these lines are that attribution's reach: nothing of the data is here. + +[preset Kodak Verita 200D] +film = kodak_verita_200d +film_print = kodak_2383 + +[preset Kodak Vision3 200T] +film = kodak_vision3_200t +film_print = kodak_2383 + +[preset Kodak Vision3 250D] +film = kodak_vision3_250d +film_print = kodak_2383 + +[preset Kodak Vision3 500T] +film = kodak_vision3_500t +film_print = kodak_2383 + +[preset Kodak Vision3 50D] +film = kodak_vision3_50d +film_print = kodak_2383 diff --git a/core/dr-pipeline/presets/colour_film.drpl b/core/dr-pipeline/presets/colour_film.drpl new file mode 100644 index 0000000..1faef84 --- /dev/null +++ b/core/dr-pipeline/presets/colour_film.drpl @@ -0,0 +1,66 @@ +drpl 1 + +# Colour film: each preset is one measured stock, developed and — for a +# negative — printed on the paper its profile names, with every other +# control left where it was. The stock is the look; a grade on top is the +# photographer's to add. +# +# The measurements are spektrafilm's (Andrea Volpato, CC BY-SA 4.0), as +# converted in core/dr-film/profiles. A preset names a stock by id, so +# these lines are that attribution's reach: nothing of the data is here. + +[preset Fujifilm C200] +film = fujifilm_c200 +film_print = fujifilm_crystal_archive_typeii + +[preset Fujifilm Pro 400H] +film = fujifilm_pro_400h +film_print = fujifilm_crystal_archive_typeii + +[preset Fujifilm Provia 100F] +film = fujifilm_provia_100f + +[preset Fujifilm Velvia 100] +film = fujifilm_velvia_100 + +[preset Fujifilm X-Tra 400] +film = fujifilm_xtra_400 +film_print = fujifilm_crystal_archive_typeii + +[preset Kodak Ektachrome 100] +film = kodak_ektachrome_100 + +[preset Kodak Ektar 100] +film = kodak_ektar_100 +film_print = kodak_portra_endura + +[preset Kodak Gold 200] +film = kodak_gold_200 +film_print = kodak_portra_endura + +[preset Kodak Kodachrome 64] +film = kodak_kodachrome_64 + +[preset Kodak Portra 160] +film = kodak_portra_160 +film_print = kodak_portra_endura + +[preset Kodak Portra 400] +film = kodak_portra_400 +film_print = kodak_portra_endura + +[preset Kodak Portra 800] +film = kodak_portra_800 +film_print = kodak_portra_endura + +[preset Kodak Portra 800 pushed one stop] +film = kodak_portra_800_push1 +film_print = kodak_portra_endura + +[preset Kodak Portra 800 pushed two stops] +film = kodak_portra_800_push2 +film_print = kodak_portra_endura + +[preset Kodak Ultramax 400] +film = kodak_ultramax_400 +film_print = kodak_portra_endura diff --git a/core/dr-pipeline/presets/essentials.drpl b/core/dr-pipeline/presets/essentials.drpl new file mode 100644 index 0000000..6a78f6f --- /dev/null +++ b/core/dr-pipeline/presets/essentials.drpl @@ -0,0 +1,41 @@ +drpl 1 + +# Essentials: small, general corrections written against this pipeline. +# These were the first-run starter set; they ship here now so that a +# new release can improve them without rewriting anybody's own presets. +# Deliberately mild — see bundled.rs. + +[preset Crisp detail] +capture_sharpen.amount = 35 +clarity.amount = 10 +texture.amount = 20 + +[preset Lift the shadows] +blacks_whites.blacks = 12 +contrast.contrast = -5 +highlights_shadows.shadows = 40 + +[preset Muted] +contrast.contrast = -10 +highlights_shadows.shadows = 12 +saturation.saturation = -30 +vibrance.vibrance = 10 + +[preset Punch] +blacks_whites.blacks = -8 +clarity.amount = 12 +contrast.contrast = 18 +vibrance.vibrance = 18 + +[preset Recover the sky] +blacks_whites.whites = -10 +highlights_shadows.highlights = -55 +highlights_shadows.shadows = 35 + +[preset Soft portrait] +clarity.amount = -10 +contrast.contrast = -8 +highlights_shadows.highlights = -20 +highlights_shadows.shadows = 15 +saturation.saturation = -5 +vibrance.vibrance = 10 diff --git a/core/dr-pipeline/src/bundled.rs b/core/dr-pipeline/src/bundled.rs new file mode 100644 index 0000000..cd78a85 --- /dev/null +++ b/core/dr-pipeline/src/bundled.rs @@ -0,0 +1,444 @@ +//! TRACES: FR-DEV-6 +//! The presets that ship with the application. +//! +//! # Shipped, not seeded +//! +//! The first six used to be *copied* into the photographer's own library on +//! the first run and were theirs from then on. That was the right answer for +//! six, and it cannot grow: a copy is frozen at the version that made it, so a +//! better "Portra" in the next release would reach nobody who already had the +//! old one, and re-seeding would overwrite a preset someone had tuned. So the +//! shipped set is now read from the binary every time, never written to the +//! user's file, and changes when the application does. +//! +//! # Your copy wins, as long as it keeps the name +//! +//! Saving over a shipped preset's name makes the photographer's version the +//! one that name means, here and in every apply. It is still *that* preset — +//! listed where the shipped one was, marked as changed — and deleting it +//! reveals the shipped one again, which is what "revert" means to the person +//! pressing it. Renaming it cuts the link: it becomes one of their own, and +//! the shipped preset reappears beside it. The lookup is by name because the +//! name is what the photographer sees and chooses by; an id they never see +//! would link two presets they believe are different. +//! +//! # Looks, not whole edits +//! +//! Every shipped preset reaches only the operations it names +//! ([`Reach::Named`]): a look applied to a corrected photograph must keep the +//! correction. A photographer's own saved edits keep [`Reach::Whole`], which +//! is what saving an edit has always meant. +//! +//! # Why the data is text files +//! +//! The same format the user's library is written in, so a shipped preset can +//! be read, diffed and copied into one's own library by hand, and each file +//! can say in a comment where its looks came from — the attribution a licence +//! may require travels with the data it covers. +//! +//! # Why this lives in the core +//! +//! It names operations — "Punch" is a statement about contrast and clarity — +//! and nothing in `ui/` may (`ui_names_no_operation.rs`, ARCH §4.3a). The +//! frontend asks for the listing and applies what it is handed. + +use crate::preset::{Preset, PresetLibrary, Reach}; + +/// One group of shipped presets, as the sheet lists it. +pub struct Section { + /// A stable identifier, for a frontend that remembers which sections a + /// photographer folded away. Never shown. + pub id: &'static str, + /// What the section is called on screen. + pub title: &'static str, + /// The presets in it, every one reaching only what it names. + pub presets: PresetLibrary, +} + +/// The files, in the order the sheet lists them. +const SECTIONS: &[(&str, &str, &str)] = &[ + ( + "essentials", + "Essentials", + include_str!("../presets/essentials.drpl"), + ), + ( + "colour_film", + "Colour film", + include_str!("../presets/colour_film.drpl"), + ), + ( + "cinema_film", + "Cinema film", + include_str!("../presets/cinema_film.drpl"), + ), + ( + "bw_film", + "Black and white film", + include_str!("../presets/bw_film.drpl"), + ), +]; + +/// Every shipped section, parsed. +/// +/// Parsed on each call rather than held: it is a few kilobytes read when the +/// preset sheet is drawn, and a static would be one more thing to keep +/// consistent with the files in a test. A file that fails to parse costs its +/// section, with a warning, rather than the sheet — and the tests below make +/// sure none does. +pub fn sections() -> Vec
{ + SECTIONS + .iter() + .filter_map(|(id, title, text)| match PresetLibrary::parse(text) { + Ok(library) => Some(Section { + id, + title, + presets: as_looks(library), + }), + Err(e) => { + log::warn!("shipped preset section {id} is unreadable ({e}); skipping"); + None + } + }) + .collect() +} + +/// Mark every preset in `library` as a look. +/// +/// Here rather than as a `reach = named` line in every block of every file: +/// the rule is about where a preset came from, and a file that forgot the +/// line would ship a preset that wiped a photographer's corrections. +fn as_looks(library: PresetLibrary) -> PresetLibrary { + let mut looks = PresetLibrary::default(); + for (name, preset) in library.iter() { + let _ = looks.insert(name, preset.clone().with_reach(Reach::Named)); + } + looks +} + +/// Where a listed preset comes from. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Origin { + /// The photographer's own, with no shipped preset of that name. + Yours, + /// Shipped, and not overridden. + Shipped, + /// Shipped, and overridden by the photographer's copy under the same + /// name. The copy is what applies; deleting it reverts to the shipped one. + Changed, +} + +/// One row of the preset sheet. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Listed { + pub name: String, + pub origin: Origin, +} + +/// One group of rows: the photographer's own, then each shipped section. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ListedSection { + /// `None` for the photographer's own presets. + pub id: Option<&'static str>, + pub title: &'static str, + pub rows: Vec, +} + +/// Everything the sheet lists, in order, with the photographer's copies +/// standing in for the shipped presets they override. +/// +/// Their own presets come first, because a photographer reaches for their own +/// work more than for anybody's defaults, and a copy of a shipped preset is +/// listed in the shipped section rather than among their own — it is still +/// that preset, changed, and belongs where they would look for it. +pub fn listing(yours: &PresetLibrary) -> Vec { + let shipped = sections(); + let is_shipped = |name: &str| shipped.iter().any(|section| section.presets.contains(name)); + + let mut out = vec![ListedSection { + id: None, + title: "Yours", + rows: yours + .names() + .filter(|name| !is_shipped(name)) + .map(|name| Listed { + name: name.to_string(), + origin: Origin::Yours, + }) + .collect(), + }]; + out.extend(shipped.iter().map(|section| { + ListedSection { + id: Some(section.id), + title: section.title, + rows: section + .presets + .names() + .map(|name| Listed { + name: name.to_string(), + origin: if yours.contains(name) { + Origin::Changed + } else { + Origin::Shipped + }, + }) + .collect(), + } + })); + out +} + +/// The preset a name means: the photographer's if they have one, the shipped +/// one otherwise. +pub fn lookup(yours: &PresetLibrary, name: &str) -> Option { + yours.get(name).cloned().or_else(|| { + sections() + .into_iter() + .find_map(|section| section.presets.get(name).cloned()) + }) +} + +/// Whether `name` is a shipped preset's. +pub fn is_shipped(name: &str) -> bool { + sections() + .iter() + .any(|section| section.presets.contains(name)) +} + +/// Remove the copies a first run used to seed, where they are still exactly +/// as seeded. Returns how many went. +/// +/// Those copies would otherwise all list as changed — overriding a shipped +/// preset with an identical one — and would freeze the six at their old +/// values forever. One that differs in any way was tuned by somebody and is +/// kept: it is theirs, and it now overrides the shipped one, which is the +/// rule above doing what it is for. +/// +/// Compared on the parameters and the film and not on [`Reach`]: the seeded +/// copies were whole edits, the shipped ones are looks, and that difference +/// is the thing this migration exists to deliver. +pub fn forget_unchanged_copies(yours: &mut PresetLibrary) -> usize { + let shipped = sections(); + let stale: Vec = yours + .iter() + .filter(|(name, preset)| { + shipped.iter().any(|section| { + section + .presets + .get(name) + .is_some_and(|s| s.params() == preset.params() && s.film() == preset.film()) + }) + }) + .map(|(name, _)| name.to_string()) + .collect(); + for name in &stale { + yours.remove(name); + } + stale.len() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{EditGraph, Scope}; + + fn all() -> Vec<(&'static str, String, Preset)> { + sections() + .into_iter() + .flat_map(|s| { + let id = s.id; + s.presets + .iter() + .map(|(n, p)| (id, n.to_string(), p.clone())) + .collect::>() + }) + .collect() + } + + #[test] + fn every_file_parses_and_every_line_is_understood() { + // A misspelt key would otherwise be kept as a line this build does + // not understand — preserved faithfully, and doing nothing. + assert_eq!( + sections().len(), + SECTIONS.len(), + "a section failed to parse" + ); + for (id, _, text) in SECTIONS { + let library = PresetLibrary::parse(text).unwrap(); + assert_eq!(library.unread_lines(), 0, "{id} has lines nobody reads"); + assert!(!library.is_empty(), "{id} is empty"); + } + } + + #[test] + fn every_shipped_name_is_unique_across_sections() { + // A name is what the lookup and the override key on. Two shipped + // presets sharing one would make one of them unreachable. + let mut names: Vec = all().into_iter().map(|(_, n, _)| n).collect(); + let before = names.len(); + names.sort(); + names.dedup(); + assert_eq!(names.len(), before); + } + + #[test] + fn every_shipped_preset_is_a_look() { + for (id, name, preset) in all() { + assert_eq!(preset.reach(), Reach::Named, "{id}/{name}"); + } + } + + #[test] + fn every_shipped_preset_names_parameters_this_build_actually_has() { + // A renamed parameter must break the build rather than ship a preset + // that quietly does nothing. + let graph = EditGraph::default_chain(); + let capabilities = graph.capabilities(); + for (id, name, preset) in all() { + for (op, param) in preset.params().keys() { + let capability = capabilities + .iter() + .find(|c| c.id.0 == op) + .unwrap_or_else(|| panic!("{id}/{name}: no operation {op:?}")); + assert!( + capability.params.iter().any(|p| p.id.0 == param), + "{id}/{name}: operation {op:?} has no parameter {param:?}" + ); + } + } + } + + #[test] + fn every_shipped_preset_changes_something() { + // A preset that applies to nothing teaches the photographer that the + // list does not work. + for (id, name, preset) in all() { + let mut graph = EditGraph::default_chain(); + let rebake = preset.apply(&mut graph, Scope::adjustments()); + assert!( + rebake.wanted().is_some() || Preset::capture(&graph) != Preset::default(), + "{id}/{name} left the graph at its defaults" + ); + } + } + + #[test] + fn no_shipped_preset_carries_a_crop() { + for (id, name, preset) in all() { + assert!(!preset.touches_framing(), "{id}/{name} carries framing"); + } + } + + #[test] + fn the_values_stay_inside_what_the_controls_accept() { + // Clamping happens on apply, so an out-of-range literal would be + // silently trimmed and the preset would not be the one written. + for (id, name, preset) in all() { + let mut graph = EditGraph::default_chain(); + preset + .clone() + .with_film(None) + .apply(&mut graph, Scope::everything()) + .expect_no_film(); + for ((op, param), value) in preset.params() { + let (op_id, param_id) = crate::preset::resolve(&graph, op, param).unwrap(); + assert_eq!( + graph.param(op_id, param_id), + Some(*value), + "{id}/{name}: {op}.{param} = {value} was clamped" + ); + } + } + } + + // --- the listing and the override ------------------------------------ + + fn yours_with(names: &[(&str, Preset)]) -> PresetLibrary { + let mut lib = PresetLibrary::default(); + for (n, p) in names { + lib.insert(n, p.clone()).unwrap(); + } + lib + } + + fn first_shipped() -> (String, Preset) { + let (_, name, preset) = all().into_iter().next().unwrap(); + (name, preset) + } + + #[test] + fn your_copy_under_a_shipped_name_is_what_that_name_applies() { + let (name, shipped) = first_shipped(); + let mine = Preset::default(); + let yours = yours_with(&[(&name, mine.clone())]); + + assert_eq!(lookup(&yours, &name), Some(mine)); + assert_eq!(lookup(&PresetLibrary::default(), &name), Some(shipped)); + } + + #[test] + fn your_copy_is_listed_in_the_shipped_section_as_changed() { + let (name, _) = first_shipped(); + let yours = yours_with(&[(&name, Preset::default()), ("Mine", Preset::default())]); + let listing = listing(&yours); + + assert_eq!(listing[0].id, None); + assert_eq!( + listing[0].rows, + vec![Listed { + name: "Mine".into(), + origin: Origin::Yours + }], + "an override must not be listed twice" + ); + let row = listing[1..] + .iter() + .flat_map(|s| &s.rows) + .find(|r| r.name == name) + .unwrap(); + assert_eq!(row.origin, Origin::Changed); + } + + #[test] + fn deleting_your_copy_reverts_to_the_shipped_one() { + let (name, shipped) = first_shipped(); + let mut yours = yours_with(&[(&name, Preset::default())]); + yours.remove(&name); + assert_eq!(lookup(&yours, &name), Some(shipped)); + } + + #[test] + fn renaming_your_copy_cuts_the_link() { + let (name, shipped) = first_shipped(); + let mut yours = yours_with(&[(&name, Preset::default())]); + yours.rename(&name, "My version").unwrap(); + + assert_eq!(lookup(&yours, &name), Some(shipped)); + assert_eq!(lookup(&yours, "My version"), Some(Preset::default())); + assert_eq!(listing(&yours)[0].rows[0].name, "My version"); + } + + #[test] + fn the_old_seeded_copies_are_forgotten_and_tuned_ones_kept() { + // The six as a first run wrote them: the same parameters, as whole + // edits, because that is what a seeded copy was. + let essentials = sections().into_iter().next().unwrap().presets; + let mut yours = PresetLibrary::default(); + for (name, preset) in essentials.iter() { + yours + .insert(name, preset.clone().with_reach(Reach::Whole)) + .unwrap(); + } + let tuned = essentials.names().next().unwrap().to_string(); + yours.insert(&tuned, Preset::default()).unwrap(); + yours.insert("Mine", Preset::default()).unwrap(); + + let forgotten = forget_unchanged_copies(&mut yours); + + assert_eq!(forgotten, essentials.len() - 1); + assert!(yours.contains(&tuned), "a tuned copy was thrown away"); + assert!(yours.contains("Mine")); + assert_eq!(yours.len(), 2); + } +} diff --git a/core/dr-pipeline/src/lib.rs b/core/dr-pipeline/src/lib.rs index 628604d..f78f758 100644 --- a/core/dr-pipeline/src/lib.rs +++ b/core/dr-pipeline/src/lib.rs @@ -32,6 +32,7 @@ //! single multiply and white balance a per-channel scale; on gamma-encoded //! data neither would be physically meaningful (ARCH §5.2). +pub mod bundled; pub mod coverage; pub mod declared; pub mod descriptor; @@ -48,7 +49,6 @@ pub mod orphan; pub mod preset; pub mod sidecar; pub mod spot; -pub mod starter; pub mod state; pub use coverage::Coverage; diff --git a/core/dr-pipeline/src/starter.rs b/core/dr-pipeline/src/starter.rs deleted file mode 100644 index bb69fb6..0000000 --- a/core/dr-pipeline/src/starter.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! TRACES: FR-DEV-6 -//! The presets a first run starts with. -//! -//! # Why any at all -//! -//! A preset sheet that opens on "No presets yet" teaches the photographer that -//! the feature is homework. These exist so the first thing the sheet does is -//! demonstrate what a preset *is* — and so that applying one to a selection, -//! which is the action worth discovering, is available before anybody has -//! saved anything. -//! -//! # Why these are ours and not Adobe's -//! -//! Lightroom ships a large bundled set, and importing one of *those* files is -//! what [`crate::preset_import`] is for — a photographer's own library, -//! carried across. Redistributing Adobe's inside this application would be -//! shipping their creative work under a licence that does not permit it, which -//! is a reason on its own; and their numbers are calibrated against their tone -//! curve rather than ours, so the look would not survive the trip even if the -//! licence allowed it. -//! -//! So these are written against this pipeline, in its units, and they are -//! deliberately mild. A starter preset is a starting point — a photographer -//! who wanted the full effect can push the sliders, where one who is handed a -//! caricature learns to distrust the list. -//! -//! # Why they live in the core rather than in the interface -//! -//! Because they name operations, and nothing in `ui/` may -//! (`ui_names_no_operation.rs`, ARCH §4.3a). That test is right to object: a -//! preset called "Punch" *is* a statement about contrast, clarity and -//! vibrance, which makes it a statement in the pipeline's vocabulary rather -//! than a fact about any interface. The frontend asks for the set and stores -//! it; it never learns what is in it. -//! -//! # Why they are seeded rather than merged -//! -//! Written once, on the first run that finds no library at all, and never -//! again. Re-adding them on every start would resurrect one the photographer -//! deleted on purpose, and updating them in place would silently rewrite an -//! edit they had adjusted and kept under the same name. After the first run -//! these are ordinary presets: renameable, editable, deletable, and gone for -//! good when deleted. - -use std::collections::BTreeMap; - -use crate::{Preset, PresetLibrary}; - -/// One starter preset: a name and the parameters that differ from default. -struct Starter { - name: &'static str, - params: &'static [(&'static str, &'static str, f32)], -} - -/// The set. Small on purpose — six a photographer might actually reach for -/// beats forty they have to scroll past. -const STARTERS: &[Starter] = &[ - Starter { - name: "Punch", - params: &[ - ("contrast", "contrast", 18.0), - ("clarity", "amount", 12.0), - ("vibrance", "vibrance", 18.0), - ("blacks_whites", "blacks", -8.0), - ], - }, - Starter { - name: "Soft portrait", - params: &[ - ("contrast", "contrast", -8.0), - ("highlights_shadows", "highlights", -20.0), - ("highlights_shadows", "shadows", 15.0), - ("clarity", "amount", -10.0), - ("vibrance", "vibrance", 10.0), - ("saturation", "saturation", -5.0), - ], - }, - Starter { - name: "Recover the sky", - params: &[ - // The most common single fix in landscape work: a bright sky and a - // dark foreground, both pulled back toward the middle. - ("highlights_shadows", "highlights", -55.0), - ("highlights_shadows", "shadows", 35.0), - ("blacks_whites", "whites", -10.0), - ], - }, - Starter { - name: "Lift the shadows", - params: &[ - ("highlights_shadows", "shadows", 40.0), - ("blacks_whites", "blacks", 12.0), - ("contrast", "contrast", -5.0), - ], - }, - Starter { - name: "Crisp detail", - params: &[ - ("texture", "amount", 20.0), - ("clarity", "amount", 10.0), - ("capture_sharpen", "amount", 35.0), - ], - }, - Starter { - name: "Muted", - params: &[ - ("saturation", "saturation", -30.0), - ("vibrance", "vibrance", 10.0), - ("contrast", "contrast", -10.0), - ("highlights_shadows", "shadows", 12.0), - ], - }, -]; - -/// The starter library, for a device that has never had one. -pub fn library() -> PresetLibrary { - let mut library = PresetLibrary::default(); - for starter in STARTERS { - let params: BTreeMap<(String, String), f32> = starter - .params - .iter() - .map(|(op, param, value)| ((op.to_string(), param.to_string()), *value)) - .collect(); - // The name is a literal in this file, so a refusal would be a bug here - // rather than bad input — but it still must not take the whole set - // down, since the alternative to five presets is not six, it is none. - if let Err(e) = library.insert(starter.name, Preset::from_params(params)) { - log::warn!( - "starter preset {:?} is unusable ({e:?}); skipping", - starter.name - ); - } - } - library -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{EditGraph, Scope}; - - #[test] - fn every_starter_names_parameters_this_build_actually_has() { - // The same guard the importer's table has, for the same reason: a - // renamed parameter must break the build rather than ship a preset - // that quietly does nothing. - let graph = EditGraph::default_chain(); - let capabilities = graph.capabilities(); - for starter in STARTERS { - for (op, param, _) in starter.params { - let capability = capabilities - .iter() - .find(|c| c.id.0 == *op) - .unwrap_or_else(|| panic!("{:?}: no operation {op:?}", starter.name)); - assert!( - capability.params.iter().any(|p| p.id.0 == *param), - "{:?}: operation {op:?} has no parameter {param:?}", - starter.name - ); - } - } - } - - #[test] - fn every_starter_actually_changes_something() { - // A preset that applies to nothing is worse than one fewer preset: it - // teaches the photographer that the list does not work. - for (name, preset) in library().iter() { - assert!(!preset.is_empty(), "{name} carries nothing"); - - let mut graph = EditGraph::default_chain(); - preset - .apply(&mut graph, Scope::adjustments()) - .expect_no_film(); - assert_ne!( - Preset::capture(&graph), - Preset::default(), - "{name} left the graph at its defaults" - ); - } - } - - #[test] - fn no_starter_carries_a_crop() { - // These are looks, not compositions. One that re-framed every image it - // was applied to would be the exact accident `Scope`'s default exists - // to prevent. - for (name, preset) in library().iter() { - assert!(!preset.touches_framing(), "{name} carries framing"); - } - } - - #[test] - fn the_names_are_distinct() { - assert_eq!(library().len(), STARTERS.len()); - } - - #[test] - fn the_values_stay_inside_what_the_controls_accept() { - // Clamping happens on apply, so an out-of-range literal here would be - // silently trimmed and the preset would not be the one written. - let graph = EditGraph::default_chain(); - for starter in STARTERS { - for (op, param, value) in starter.params { - let mut applied = EditGraph::default_chain(); - let capability = graph - .capabilities() - .into_iter() - .find(|c| c.id.0 == *op) - .unwrap(); - let descriptor = capability.params.iter().find(|p| p.id.0 == *param).unwrap(); - applied.set_param(capability.id, descriptor.id, *value); - assert_eq!( - applied.param(capability.id, descriptor.id), - Some(*value), - "{}: {op}.{param} = {value} was clamped", - starter.name - ); - } - } - } -} diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md index 652edc2..f254296 100644 --- a/docs/dev/traceability.md +++ b/docs/dev/traceability.md @@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| | Source files scanned | 454 | -| TRACES tags found | 1848 | +| TRACES tags found | 1851 | | Requirements defined | 191 | | Requirements deferred (post-v1) | 24 | | Requirements covered | 160 | @@ -80,12 +80,12 @@ _None._ | 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:1744`](../../core/dr-pipeline/src/mask.rs#L1744), [`core/dr-pipeline/src/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`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:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/adjust.rs:114`](../../core/dr-gpu/src/adjust.rs#L114), [`core/dr-gpu/src/adjust.rs:1279`](../../core/dr-gpu/src/adjust.rs#L1279), [`core/dr-gpu/src/adjust.rs:1373`](../../core/dr-gpu/src/adjust.rs#L1373), [`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:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/graph.rs:997`](../../core/dr-pipeline/src/graph.rs#L997), [`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:127`](../../core/dr-decode/src/lib.rs#L127), [`core/dr-decode/src/lib.rs:777`](../../core/dr-decode/src/lib.rs#L777), [`core/dr-decode/src/lib.rs:817`](../../core/dr-decode/src/lib.rs#L817), [`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:1260`](../../core/dr-gpu/src/adjust.rs#L1260), [`core/dr-gpu/src/adjust.rs:37`](../../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/demosaic.rs:142`](../../core/dr-gpu/src/demosaic.rs#L142), [`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:2057`](../../core/dr-pipeline/src/operation.rs#L2057), [`core/dr-pipeline/src/operation.rs:2138`](../../core/dr-pipeline/src/operation.rs#L2138), [`core/dr-pipeline/src/operation.rs:2163`](../../core/dr-pipeline/src/operation.rs#L2163), [`core/dr-pipeline/src/operation.rs:2178`](../../core/dr-pipeline/src/operation.rs#L2178), [`core/dr-pipeline/src/operation.rs:2202`](../../core/dr-pipeline/src/operation.rs#L2202), [`core/dr-pipeline/src/operation.rs:310`](../../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:502`](../../core/dr-pipeline/src/operation.rs#L502), [`core/dr-pipeline/src/operation.rs:512`](../../core/dr-pipeline/src/operation.rs#L512), [`core/dr-pipeline/src/operation.rs:805`](../../core/dr-pipeline/src/operation.rs#L805) | -| 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:152`](../../core/dr-gpu/src/adjust.rs#L152), [`core/dr-gpu/src/adjust.rs:408`](../../core/dr-gpu/src/adjust.rs#L408), [`core/dr-gpu/src/adjust.rs:583`](../../core/dr-gpu/src/adjust.rs#L583), [`core/dr-gpu/src/adjust.rs:713`](../../core/dr-gpu/src/adjust.rs#L713), [`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:1373`](../../core/dr-pipeline/src/operation.rs#L1373), [`core/dr-pipeline/src/operation.rs:1401`](../../core/dr-pipeline/src/operation.rs#L1401), [`core/dr-pipeline/src/operation.rs:1447`](../../core/dr-pipeline/src/operation.rs#L1447), [`core/dr-pipeline/src/operation.rs:2057`](../../core/dr-pipeline/src/operation.rs#L2057), [`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/preset.rs:1291`](../../core/dr-pipeline/src/preset.rs#L1291), [`core/dr-pipeline/src/preset.rs:1301`](../../core/dr-pipeline/src/preset.rs#L1301), [`core/dr-pipeline/src/preset.rs:1310`](../../core/dr-pipeline/src/preset.rs#L1310), [`core/dr-pipeline/src/preset.rs:1326`](../../core/dr-pipeline/src/preset.rs#L1326), [`core/dr-pipeline/src/preset.rs:1338`](../../core/dr-pipeline/src/preset.rs#L1338), [`core/dr-pipeline/src/preset.rs:1354`](../../core/dr-pipeline/src/preset.rs#L1354), [`core/dr-pipeline/src/preset.rs:1527`](../../core/dr-pipeline/src/preset.rs#L1527), [`core/dr-pipeline/src/preset.rs:1542`](../../core/dr-pipeline/src/preset.rs#L1542), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2722`](../../core/dr-pipeline/src/sidecar.rs#L2722), [`core/dr-pipeline/src/sidecar.rs:2798`](../../core/dr-pipeline/src/sidecar.rs#L2798), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`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:1212`](../../ui/dr-ui/src/develop/render.rs#L1212), [`ui/dr-ui/src/develop/render.rs:684`](../../ui/dr-ui/src/develop/render.rs#L684), [`ui/dr-ui/src/develop/render.rs:701`](../../ui/dr-ui/src/develop/render.rs#L701), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:751`](../../ui/dr-ui/src/develop/render.rs#L751), [`ui/dr-ui/src/develop/render.rs:760`](../../ui/dr-ui/src/develop/render.rs#L760), [`ui/dr-ui/src/develop/render.rs:863`](../../ui/dr-ui/src/develop/render.rs#L863), [`ui/dr-ui/src/develop/render.rs:889`](../../ui/dr-ui/src/develop/render.rs#L889), [`ui/dr-ui/src/develop/session.rs:588`](../../ui/dr-ui/src/develop/session.rs#L588), [`ui/dr-ui/src/develop/session.rs:608`](../../ui/dr-ui/src/develop/session.rs#L608), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:386`](../../ui/dr-ui/src/develop_ui.rs#L386), [`ui/dr-ui/src/lib.rs:845`](../../ui/dr-ui/src/lib.rs#L845), [`ui/dr-ui/src/lib.rs:910`](../../ui/dr-ui/src/lib.rs#L910), [`ui/dr-ui/src/library/sidecar.rs:324`](../../ui/dr-ui/src/library/sidecar.rs#L324), [`ui/dr-ui/src/library/sidecar.rs:49`](../../ui/dr-ui/src/library/sidecar.rs#L49), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:595`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L595), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) | +| 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:152`](../../core/dr-gpu/src/adjust.rs#L152), [`core/dr-gpu/src/adjust.rs:408`](../../core/dr-gpu/src/adjust.rs#L408), [`core/dr-gpu/src/adjust.rs:583`](../../core/dr-gpu/src/adjust.rs#L583), [`core/dr-gpu/src/adjust.rs:713`](../../core/dr-gpu/src/adjust.rs#L713), [`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:1373`](../../core/dr-pipeline/src/operation.rs#L1373), [`core/dr-pipeline/src/operation.rs:1401`](../../core/dr-pipeline/src/operation.rs#L1401), [`core/dr-pipeline/src/operation.rs:1447`](../../core/dr-pipeline/src/operation.rs#L1447), [`core/dr-pipeline/src/operation.rs:2057`](../../core/dr-pipeline/src/operation.rs#L2057), [`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/preset.rs:1291`](../../core/dr-pipeline/src/preset.rs#L1291), [`core/dr-pipeline/src/preset.rs:1301`](../../core/dr-pipeline/src/preset.rs#L1301), [`core/dr-pipeline/src/preset.rs:1310`](../../core/dr-pipeline/src/preset.rs#L1310), [`core/dr-pipeline/src/preset.rs:1326`](../../core/dr-pipeline/src/preset.rs#L1326), [`core/dr-pipeline/src/preset.rs:1338`](../../core/dr-pipeline/src/preset.rs#L1338), [`core/dr-pipeline/src/preset.rs:1354`](../../core/dr-pipeline/src/preset.rs#L1354), [`core/dr-pipeline/src/preset.rs:1527`](../../core/dr-pipeline/src/preset.rs#L1527), [`core/dr-pipeline/src/preset.rs:1542`](../../core/dr-pipeline/src/preset.rs#L1542), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2722`](../../core/dr-pipeline/src/sidecar.rs#L2722), [`core/dr-pipeline/src/sidecar.rs:2798`](../../core/dr-pipeline/src/sidecar.rs#L2798), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`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:1212`](../../ui/dr-ui/src/develop/render.rs#L1212), [`ui/dr-ui/src/develop/render.rs:684`](../../ui/dr-ui/src/develop/render.rs#L684), [`ui/dr-ui/src/develop/render.rs:701`](../../ui/dr-ui/src/develop/render.rs#L701), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:751`](../../ui/dr-ui/src/develop/render.rs#L751), [`ui/dr-ui/src/develop/render.rs:760`](../../ui/dr-ui/src/develop/render.rs#L760), [`ui/dr-ui/src/develop/render.rs:863`](../../ui/dr-ui/src/develop/render.rs#L863), [`ui/dr-ui/src/develop/render.rs:889`](../../ui/dr-ui/src/develop/render.rs#L889), [`ui/dr-ui/src/develop/session.rs:588`](../../ui/dr-ui/src/develop/session.rs#L588), [`ui/dr-ui/src/develop/session.rs:608`](../../ui/dr-ui/src/develop/session.rs#L608), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:386`](../../ui/dr-ui/src/develop_ui.rs#L386), [`ui/dr-ui/src/lib.rs:845`](../../ui/dr-ui/src/lib.rs#L845), [`ui/dr-ui/src/lib.rs:910`](../../ui/dr-ui/src/lib.rs#L910), [`ui/dr-ui/src/library/sidecar.rs:324`](../../ui/dr-ui/src/library/sidecar.rs#L324), [`ui/dr-ui/src/library/sidecar.rs:49`](../../ui/dr-ui/src/library/sidecar.rs#L49), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:595`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L595), [`ui/dr-ui/src/presets.rs:1558`](../../ui/dr-ui/src/presets.rs#L1558), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) | | FR-DEV-3h | [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-decode/src/preview.rs:29`](../../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1477`](../../core/dr-pipeline/src/framing.rs#L1477), [`core/dr-pipeline/src/framing.rs:486`](../../core/dr-pipeline/src/framing.rs#L486), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`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:280`](../../ui/dr-ui/src/develop/masks.rs#L280), [`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:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`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:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`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:1067`](../../core/dr-pipeline/src/sidecar.rs#L1067), [`core/dr-pipeline/src/sidecar.rs:147`](../../core/dr-pipeline/src/sidecar.rs#L147), [`core/dr-pipeline/src/sidecar.rs:2513`](../../core/dr-pipeline/src/sidecar.rs#L2513), [`core/dr-pipeline/src/sidecar.rs:2549`](../../core/dr-pipeline/src/sidecar.rs#L2549), [`core/dr-pipeline/src/sidecar.rs:2561`](../../core/dr-pipeline/src/sidecar.rs#L2561), [`core/dr-pipeline/src/sidecar.rs:318`](../../core/dr-pipeline/src/sidecar.rs#L318), [`core/dr-pipeline/src/sidecar.rs:713`](../../core/dr-pipeline/src/sidecar.rs#L713), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`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:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`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:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:889`](../../ui/dr-ui/src/develop/render.rs#L889), [`ui/dr-ui/src/develop/render.rs:925`](../../ui/dr-ui/src/develop/render.rs#L925), [`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:705`](../../ui/dr-ui/src/develop_ui.rs#L705), [`ui/dr-ui/src/develop_ui.rs:725`](../../ui/dr-ui/src/develop_ui.rs#L725), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:284`](../../ui/dr-ui/src/labels.rs#L284), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2157`](../../ui/dr-ui/src/lib.rs#L2157), [`ui/dr-ui/src/lib.rs:2162`](../../ui/dr-ui/src/lib.rs#L2162), [`ui/dr-ui/src/lib.rs:2194`](../../ui/dr-ui/src/lib.rs#L2194), [`ui/dr-ui/src/lib.rs:2203`](../../ui/dr-ui/src/lib.rs#L2203), [`ui/dr-ui/src/lib.rs:2220`](../../ui/dr-ui/src/lib.rs#L2220), [`ui/dr-ui/src/library/sidecar.rs:350`](../../ui/dr-ui/src/library/sidecar.rs#L350), [`ui/dr-ui/src/library/sidecar.rs:60`](../../ui/dr-ui/src/library/sidecar.rs#L60), [`ui/dr-ui/src/presets.rs:1680`](../../ui/dr-ui/src/presets.rs#L1680), [`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:192`](../../ui/dr-ui/ui/history.slint#L192), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:219`](../../ui/dr-ui/ui/history.slint#L219), [`ui/dr-ui/ui/history.slint:304`](../../ui/dr-ui/ui/history.slint#L304), [`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:1291`](../../core/dr-pipeline/src/preset.rs#L1291), [`core/dr-pipeline/src/preset.rs:1301`](../../core/dr-pipeline/src/preset.rs#L1301), [`core/dr-pipeline/src/preset.rs:1310`](../../core/dr-pipeline/src/preset.rs#L1310), [`core/dr-pipeline/src/preset.rs:1326`](../../core/dr-pipeline/src/preset.rs#L1326), [`core/dr-pipeline/src/preset.rs:1338`](../../core/dr-pipeline/src/preset.rs#L1338), [`core/dr-pipeline/src/preset.rs:1354`](../../core/dr-pipeline/src/preset.rs#L1354), [`core/dr-pipeline/src/preset.rs:1380`](../../core/dr-pipeline/src/preset.rs#L1380), [`core/dr-pipeline/src/preset.rs:1402`](../../core/dr-pipeline/src/preset.rs#L1402), [`core/dr-pipeline/src/preset.rs:1416`](../../core/dr-pipeline/src/preset.rs#L1416), [`core/dr-pipeline/src/preset.rs:1434`](../../core/dr-pipeline/src/preset.rs#L1434), [`core/dr-pipeline/src/preset.rs:1527`](../../core/dr-pipeline/src/preset.rs#L1527), [`core/dr-pipeline/src/preset.rs:1542`](../../core/dr-pipeline/src/preset.rs#L1542), [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:501`](../../core/dr-pipeline/src/preset.rs#L501), [`core/dr-pipeline/src/preset.rs:534`](../../core/dr-pipeline/src/preset.rs#L534), [`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:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`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:1246`](../../ui/dr-ui/src/lib.rs#L1246), [`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/controller.rs:671`](../../ui/dr-ui/src/library_ui/controller.rs#L671), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:488`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L488), [`ui/dr-ui/src/library_ui/window.rs:659`](../../ui/dr-ui/src/library_ui/window.rs#L659), [`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:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1081`](../../ui/dr-ui/ui/app.slint#L1081), [`ui/dr-ui/ui/app.slint:1497`](../../ui/dr-ui/ui/app.slint#L1497), [`ui/dr-ui/ui/app.slint:2470`](../../ui/dr-ui/ui/app.slint#L2470), [`ui/dr-ui/ui/app.slint:3408`](../../ui/dr-ui/ui/app.slint#L3408), [`ui/dr-ui/ui/app.slint:3452`](../../ui/dr-ui/ui/app.slint#L3452), [`ui/dr-ui/ui/app.slint:520`](../../ui/dr-ui/ui/app.slint#L520), [`ui/dr-ui/ui/app.slint:531`](../../ui/dr-ui/ui/app.slint#L531), [`ui/dr-ui/ui/app.slint:685`](../../ui/dr-ui/ui/app.slint#L685), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/library.slint:1995`](../../ui/dr-ui/ui/library.slint#L1995), [`ui/dr-ui/ui/library.slint:2001`](../../ui/dr-ui/ui/library.slint#L2001), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:5126`](../../ui/dr-ui/ui/library.slint#L5126), [`ui/dr-ui/ui/library.slint:5144`](../../ui/dr-ui/ui/library.slint#L5144), [`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:332`](../../ui/dr-ui/ui/presets.slint#L332), [`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:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690) | +| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`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:1067`](../../core/dr-pipeline/src/sidecar.rs#L1067), [`core/dr-pipeline/src/sidecar.rs:147`](../../core/dr-pipeline/src/sidecar.rs#L147), [`core/dr-pipeline/src/sidecar.rs:2513`](../../core/dr-pipeline/src/sidecar.rs#L2513), [`core/dr-pipeline/src/sidecar.rs:2549`](../../core/dr-pipeline/src/sidecar.rs#L2549), [`core/dr-pipeline/src/sidecar.rs:2561`](../../core/dr-pipeline/src/sidecar.rs#L2561), [`core/dr-pipeline/src/sidecar.rs:318`](../../core/dr-pipeline/src/sidecar.rs#L318), [`core/dr-pipeline/src/sidecar.rs:713`](../../core/dr-pipeline/src/sidecar.rs#L713), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`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:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`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:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:889`](../../ui/dr-ui/src/develop/render.rs#L889), [`ui/dr-ui/src/develop/render.rs:925`](../../ui/dr-ui/src/develop/render.rs#L925), [`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:705`](../../ui/dr-ui/src/develop_ui.rs#L705), [`ui/dr-ui/src/develop_ui.rs:725`](../../ui/dr-ui/src/develop_ui.rs#L725), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:284`](../../ui/dr-ui/src/labels.rs#L284), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2157`](../../ui/dr-ui/src/lib.rs#L2157), [`ui/dr-ui/src/lib.rs:2162`](../../ui/dr-ui/src/lib.rs#L2162), [`ui/dr-ui/src/lib.rs:2194`](../../ui/dr-ui/src/lib.rs#L2194), [`ui/dr-ui/src/lib.rs:2203`](../../ui/dr-ui/src/lib.rs#L2203), [`ui/dr-ui/src/lib.rs:2220`](../../ui/dr-ui/src/lib.rs#L2220), [`ui/dr-ui/src/library/sidecar.rs:350`](../../ui/dr-ui/src/library/sidecar.rs#L350), [`ui/dr-ui/src/library/sidecar.rs:60`](../../ui/dr-ui/src/library/sidecar.rs#L60), [`ui/dr-ui/src/presets.rs:1742`](../../ui/dr-ui/src/presets.rs#L1742), [`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:192`](../../ui/dr-ui/ui/history.slint#L192), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:219`](../../ui/dr-ui/ui/history.slint#L219), [`ui/dr-ui/ui/history.slint:304`](../../ui/dr-ui/ui/history.slint#L304), [`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/bundled.rs:1`](../../core/dr-pipeline/src/bundled.rs#L1), [`core/dr-pipeline/src/preset.rs:1291`](../../core/dr-pipeline/src/preset.rs#L1291), [`core/dr-pipeline/src/preset.rs:1301`](../../core/dr-pipeline/src/preset.rs#L1301), [`core/dr-pipeline/src/preset.rs:1310`](../../core/dr-pipeline/src/preset.rs#L1310), [`core/dr-pipeline/src/preset.rs:1326`](../../core/dr-pipeline/src/preset.rs#L1326), [`core/dr-pipeline/src/preset.rs:1338`](../../core/dr-pipeline/src/preset.rs#L1338), [`core/dr-pipeline/src/preset.rs:1354`](../../core/dr-pipeline/src/preset.rs#L1354), [`core/dr-pipeline/src/preset.rs:1380`](../../core/dr-pipeline/src/preset.rs#L1380), [`core/dr-pipeline/src/preset.rs:1402`](../../core/dr-pipeline/src/preset.rs#L1402), [`core/dr-pipeline/src/preset.rs:1416`](../../core/dr-pipeline/src/preset.rs#L1416), [`core/dr-pipeline/src/preset.rs:1434`](../../core/dr-pipeline/src/preset.rs#L1434), [`core/dr-pipeline/src/preset.rs:1527`](../../core/dr-pipeline/src/preset.rs#L1527), [`core/dr-pipeline/src/preset.rs:1542`](../../core/dr-pipeline/src/preset.rs#L1542), [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:501`](../../core/dr-pipeline/src/preset.rs#L501), [`core/dr-pipeline/src/preset.rs:534`](../../core/dr-pipeline/src/preset.rs#L534), [`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:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`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:1246`](../../ui/dr-ui/src/lib.rs#L1246), [`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/controller.rs:671`](../../ui/dr-ui/src/library_ui/controller.rs#L671), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:488`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L488), [`ui/dr-ui/src/library_ui/window.rs:659`](../../ui/dr-ui/src/library_ui/window.rs#L659), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1558`](../../ui/dr-ui/src/presets.rs#L1558), [`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:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1081`](../../ui/dr-ui/ui/app.slint#L1081), [`ui/dr-ui/ui/app.slint:1497`](../../ui/dr-ui/ui/app.slint#L1497), [`ui/dr-ui/ui/app.slint:2470`](../../ui/dr-ui/ui/app.slint#L2470), [`ui/dr-ui/ui/app.slint:3408`](../../ui/dr-ui/ui/app.slint#L3408), [`ui/dr-ui/ui/app.slint:3452`](../../ui/dr-ui/ui/app.slint#L3452), [`ui/dr-ui/ui/app.slint:520`](../../ui/dr-ui/ui/app.slint#L520), [`ui/dr-ui/ui/app.slint:531`](../../ui/dr-ui/ui/app.slint#L531), [`ui/dr-ui/ui/app.slint:685`](../../ui/dr-ui/ui/app.slint#L685), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/library.slint:1995`](../../ui/dr-ui/ui/library.slint#L1995), [`ui/dr-ui/ui/library.slint:2001`](../../ui/dr-ui/ui/library.slint#L2001), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:5126`](../../ui/dr-ui/ui/library.slint#L5126), [`ui/dr-ui/ui/library.slint:5144`](../../ui/dr-ui/ui/library.slint#L5144), [`ui/dr-ui/ui/presets.slint:131`](../../ui/dr-ui/ui/presets.slint#L131), [`ui/dr-ui/ui/presets.slint:139`](../../ui/dr-ui/ui/presets.slint#L139), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:308`](../../ui/dr-ui/ui/presets.slint#L308), [`ui/dr-ui/ui/presets.slint:31`](../../ui/dr-ui/ui/presets.slint#L31), [`ui/dr-ui/ui/presets.slint:329`](../../ui/dr-ui/ui/presets.slint#L329), [`ui/dr-ui/ui/presets.slint:364`](../../ui/dr-ui/ui/presets.slint#L364), [`ui/dr-ui/ui/presets.slint:44`](../../ui/dr-ui/ui/presets.slint#L44), [`ui/dr-ui/ui/presets.slint:5`](../../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:85`](../../ui/dr-ui/ui/presets.slint#L85), [`ui/dr-ui/ui/settings.slint:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690) | | 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:493`](../../ui/dr-ui/src/develop/render.rs#L493), [`ui/dr-ui/src/develop/render.rs:534`](../../ui/dr-ui/src/develop/render.rs#L534), [`ui/dr-ui/src/develop/render.rs:925`](../../ui/dr-ui/src/develop/render.rs#L925), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/develop_ui.rs:646`](../../ui/dr-ui/src/develop_ui.rs#L646), [`ui/dr-ui/src/develop_ui.rs:705`](../../ui/dr-ui/src/develop_ui.rs#L705), [`ui/dr-ui/src/develop_ui.rs:777`](../../ui/dr-ui/src/develop_ui.rs#L777), [`ui/dr-ui/src/lib.rs:2203`](../../ui/dr-ui/src/lib.rs#L2203), [`ui/dr-ui/src/lib.rs:2280`](../../ui/dr-ui/src/lib.rs#L2280), [`ui/dr-ui/src/lib.rs:2294`](../../ui/dr-ui/src/lib.rs#L2294), [`ui/dr-ui/src/lib.rs:442`](../../ui/dr-ui/src/lib.rs#L442), [`ui/dr-ui/ui/app.slint:221`](../../ui/dr-ui/ui/app.slint#L221), [`ui/dr-ui/ui/app.slint:2337`](../../ui/dr-ui/ui/app.slint#L2337), [`ui/dr-ui/ui/app.slint:2383`](../../ui/dr-ui/ui/app.slint#L2383), [`ui/dr-ui/ui/app.slint:2952`](../../ui/dr-ui/ui/app.slint#L2952), [`ui/dr-ui/ui/history.slint:103`](../../ui/dr-ui/ui/history.slint#L103), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:227`](../../ui/dr-ui/ui/history.slint#L227), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | | 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:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:487`](../../core/dr-pipeline/src/detail.rs#L487), [`core/dr-pipeline/src/detail.rs:573`](../../core/dr-pipeline/src/detail.rs#L573), [`core/dr-pipeline/src/graph.rs:1098`](../../core/dr-pipeline/src/graph.rs#L1098), [`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:749`](../../core/dr-pipeline/src/operation.rs#L749), [`core/dr-pipeline/src/sidecar.rs:1104`](../../core/dr-pipeline/src/sidecar.rs#L1104), [`core/dr-pipeline/src/sidecar.rs:1158`](../../core/dr-pipeline/src/sidecar.rs#L1158), [`core/dr-pipeline/src/sidecar.rs:1188`](../../core/dr-pipeline/src/sidecar.rs#L1188), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:910`](../../core/dr-pipeline/src/sidecar.rs#L910), [`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:216`](../../ui/dr-ui/src/develop/session.rs#L216), [`ui/dr-ui/src/develop_ui.rs:932`](../../ui/dr-ui/src/develop_ui.rs#L932), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2238`](../../ui/dr-ui/src/lib.rs#L2238), [`ui/dr-ui/src/lib.rs:485`](../../ui/dr-ui/src/lib.rs#L485), [`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:2190`](../../ui/dr-ui/ui/app.slint#L2190), [`ui/dr-ui/ui/app.slint:2433`](../../ui/dr-ui/ui/app.slint#L2433), [`ui/dr-ui/ui/app.slint:3301`](../../ui/dr-ui/ui/app.slint#L3301), [`ui/dr-ui/ui/icons.slint:378`](../../ui/dr-ui/ui/icons.slint#L378), [`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:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/adjust.rs:2698`](../../core/dr-gpu/src/adjust.rs#L2698), [`core/dr-gpu/src/adjust.rs:2775`](../../core/dr-gpu/src/adjust.rs#L2775), [`core/dr-gpu/src/adjust.rs:2860`](../../core/dr-gpu/src/adjust.rs#L2860), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`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:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/graph.rs:928`](../../core/dr-pipeline/src/graph.rs#L928), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`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:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:1016`](../../ui/dr-ui/src/develop/render.rs#L1016), [`ui/dr-ui/src/develop/render.rs:1154`](../../ui/dr-ui/src/develop/render.rs#L1154), [`ui/dr-ui/src/develop/render.rs:217`](../../ui/dr-ui/src/develop/render.rs#L217), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop/render.rs:982`](../../ui/dr-ui/src/develop/render.rs#L982), [`ui/dr-ui/src/lib.rs:1066`](../../ui/dr-ui/src/lib.rs#L1066), [`ui/dr-ui/src/lib.rs:110`](../../ui/dr-ui/src/lib.rs#L110) | @@ -149,10 +149,10 @@ _None._ | 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:287`](../../ui/dr-ui/src/lib.rs#L287) | | FR-RAW-5 | [`core/dr-decode/src/lib.rs:189`](../../core/dr-decode/src/lib.rs#L189), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:758`](../../core/dr-gpu/src/demosaic.rs#L758), [`core/dr-gpu/src/demosaic.rs:837`](../../core/dr-gpu/src/demosaic.rs#L837), [`core/dr-gpu/src/demosaic.rs:961`](../../core/dr-gpu/src/demosaic.rs#L961) | | 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:118`](../../ui/dr-ui/src/lib.rs#L118), [`ui/dr-ui/src/lib.rs:133`](../../ui/dr-ui/src/lib.rs#L133), [`ui/dr-ui/src/lib.rs:155`](../../ui/dr-ui/src/lib.rs#L155), [`ui/dr-ui/src/lib.rs:3292`](../../ui/dr-ui/src/lib.rs#L3292), [`ui/dr-ui/src/lib.rs:3311`](../../ui/dr-ui/src/lib.rs#L3311), [`ui/dr-ui/src/lib.rs:3338`](../../ui/dr-ui/src/lib.rs#L3338), [`ui/dr-ui/src/lib.rs:3389`](../../ui/dr-ui/src/lib.rs#L3389), [`ui/dr-ui/src/lib.rs:3422`](../../ui/dr-ui/src/lib.rs#L3422), [`ui/dr-ui/src/library_ui/grid.rs:232`](../../ui/dr-ui/src/library_ui/grid.rs#L232), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/settings_ui.rs:179`](../../ui/dr-ui/src/settings_ui.rs#L179), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:3072`](../../ui/dr-ui/ui/app.slint#L3072), [`ui/dr-ui/ui/app.slint:657`](../../ui/dr-ui/ui/app.slint#L657), [`ui/dr-ui/ui/app.slint:802`](../../ui/dr-ui/ui/app.slint#L802), [`ui/dr-ui/ui/identity.slint:249`](../../ui/dr-ui/ui/identity.slint#L249), [`ui/dr-ui/ui/library.slint:1442`](../../ui/dr-ui/ui/library.slint#L1442), [`ui/dr-ui/ui/settings.slint:129`](../../ui/dr-ui/ui/settings.slint#L129), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`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), [`ui/dr-ui/ui/widgets.slint:1117`](../../ui/dr-ui/ui/widgets.slint#L1117) | -| FR-UI-2 | [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:118`](../../ui/dr-ui/src/lib.rs#L118), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/window.rs:981`](../../ui/dr-ui/src/library_ui/window.rs#L981), [`ui/dr-ui/ui/app.slint:134`](../../ui/dr-ui/ui/app.slint#L134), [`ui/dr-ui/ui/app.slint:3468`](../../ui/dr-ui/ui/app.slint#L3468), [`ui/dr-ui/ui/app.slint:786`](../../ui/dr-ui/ui/app.slint#L786), [`ui/dr-ui/ui/collections.slint:1340`](../../ui/dr-ui/ui/collections.slint#L1340), [`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:1329`](../../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1686`](../../ui/dr-ui/ui/library.slint#L1686), [`ui/dr-ui/ui/library.slint:1693`](../../ui/dr-ui/ui/library.slint#L1693), [`ui/dr-ui/ui/library.slint:1794`](../../ui/dr-ui/ui/library.slint#L1794), [`ui/dr-ui/ui/library.slint:3129`](../../ui/dr-ui/ui/library.slint#L3129), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:143`](../../ui/dr-ui/ui/settings.slint#L143) | +| FR-UI-2 | [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:118`](../../ui/dr-ui/src/lib.rs#L118), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/window.rs:981`](../../ui/dr-ui/src/library_ui/window.rs#L981), [`ui/dr-ui/ui/app.slint:134`](../../ui/dr-ui/ui/app.slint#L134), [`ui/dr-ui/ui/app.slint:3468`](../../ui/dr-ui/ui/app.slint#L3468), [`ui/dr-ui/ui/app.slint:786`](../../ui/dr-ui/ui/app.slint#L786), [`ui/dr-ui/ui/collections.slint:1340`](../../ui/dr-ui/ui/collections.slint#L1340), [`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:1329`](../../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1686`](../../ui/dr-ui/ui/library.slint#L1686), [`ui/dr-ui/ui/library.slint:1693`](../../ui/dr-ui/ui/library.slint#L1693), [`ui/dr-ui/ui/library.slint:1794`](../../ui/dr-ui/ui/library.slint#L1794), [`ui/dr-ui/ui/library.slint:3129`](../../ui/dr-ui/ui/library.slint#L3129), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/presets.slint:44`](../../ui/dr-ui/ui/presets.slint#L44), [`ui/dr-ui/ui/settings.slint:143`](../../ui/dr-ui/ui/settings.slint#L143) | | FR-UI-3 | [`ui/dr-ui/src/collections_ui/controller.rs:138`](../../ui/dr-ui/src/collections_ui/controller.rs#L138), [`ui/dr-ui/src/collections_ui/controller.rs:343`](../../ui/dr-ui/src/collections_ui/controller.rs#L343), [`ui/dr-ui/src/collections_ui/controller.rs:356`](../../ui/dr-ui/src/collections_ui/controller.rs#L356), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/masks_ui.rs:1700`](../../ui/dr-ui/src/masks_ui.rs#L1700), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`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:2276`](../../ui/dr-ui/ui/app.slint#L2276), [`ui/dr-ui/ui/collections.slint:1214`](../../ui/dr-ui/ui/collections.slint#L1214), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:77`](../../ui/dr-ui/ui/collections.slint#L77), [`ui/dr-ui/ui/collections.slint:964`](../../ui/dr-ui/ui/collections.slint#L964), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | | FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:138`](../../ui/dr-ui/src/collections_ui/controller.rs#L138), [`ui/dr-ui/src/collections_ui/controller.rs:343`](../../ui/dr-ui/src/collections_ui/controller.rs#L343), [`ui/dr-ui/src/collections_ui/controller.rs:356`](../../ui/dr-ui/src/collections_ui/controller.rs#L356), [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/controller.rs:55`](../../ui/dr-ui/src/collections_ui/controller.rs#L55), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:278`](../../ui/dr-ui/src/collections_ui/press.rs#L278), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/tree_sync.rs:339`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L339), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:577`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L577), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/develop/framing.rs:257`](../../ui/dr-ui/src/develop/framing.rs#L257), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/framing.rs:619`](../../ui/dr-ui/src/develop/framing.rs#L619), [`ui/dr-ui/src/develop/framing.rs:630`](../../ui/dr-ui/src/develop/framing.rs#L630), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:818`](../../ui/dr-ui/src/develop/framing.rs#L818), [`ui/dr-ui/src/develop/framing.rs:849`](../../ui/dr-ui/src/develop/framing.rs#L849), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/framing.rs:995`](../../ui/dr-ui/src/develop/framing.rs#L995), [`ui/dr-ui/src/develop/render.rs:1067`](../../ui/dr-ui/src/develop/render.rs#L1067), [`ui/dr-ui/src/develop/render.rs:1092`](../../ui/dr-ui/src/develop/render.rs#L1092), [`ui/dr-ui/src/develop/render.rs:1115`](../../ui/dr-ui/src/develop/render.rs#L1115), [`ui/dr-ui/src/develop/render.rs:1154`](../../ui/dr-ui/src/develop/render.rs#L1154), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/render.rs:66`](../../ui/dr-ui/src/develop/render.rs#L66), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop_ui.rs:852`](../../ui/dr-ui/src/develop_ui.rs#L852), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:127`](../../ui/dr-ui/src/gestures.rs#L127), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1287`](../../ui/dr-ui/src/lib.rs#L1287), [`ui/dr-ui/src/lib.rs:1578`](../../ui/dr-ui/src/lib.rs#L1578), [`ui/dr-ui/src/lib.rs:2598`](../../ui/dr-ui/src/lib.rs#L2598), [`ui/dr-ui/src/lib.rs:2883`](../../ui/dr-ui/src/lib.rs#L2883), [`ui/dr-ui/src/lib.rs:2943`](../../ui/dr-ui/src/lib.rs#L2943), [`ui/dr-ui/src/lib.rs:3102`](../../ui/dr-ui/src/lib.rs#L3102), [`ui/dr-ui/src/lib.rs:3504`](../../ui/dr-ui/src/lib.rs#L3504), [`ui/dr-ui/src/lib.rs:3548`](../../ui/dr-ui/src/lib.rs#L3548), [`ui/dr-ui/src/lib.rs:492`](../../ui/dr-ui/src/lib.rs#L492), [`ui/dr-ui/src/lib.rs:521`](../../ui/dr-ui/src/lib.rs#L521), [`ui/dr-ui/src/library/thumbnails_fetch.rs:759`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L759), [`ui/dr-ui/src/library/thumbnails_fetch.rs:774`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L774), [`ui/dr-ui/src/library_ui/controller.rs:398`](../../ui/dr-ui/src/library_ui/controller.rs#L398), [`ui/dr-ui/src/library_ui/controller.rs:540`](../../ui/dr-ui/src/library_ui/controller.rs#L540), [`ui/dr-ui/src/library_ui/controller.rs:645`](../../ui/dr-ui/src/library_ui/controller.rs#L645), [`ui/dr-ui/src/library_ui/filter_bar.rs:500`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L500), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/library_ui/grid.rs:241`](../../ui/dr-ui/src/library_ui/grid.rs#L241), [`ui/dr-ui/src/library_ui/grid.rs:262`](../../ui/dr-ui/src/library_ui/grid.rs#L262), [`ui/dr-ui/src/library_ui/grid.rs:336`](../../ui/dr-ui/src/library_ui/grid.rs#L336), [`ui/dr-ui/src/library_ui/grid.rs:479`](../../ui/dr-ui/src/library_ui/grid.rs#L479), [`ui/dr-ui/src/library_ui/grid.rs:509`](../../ui/dr-ui/src/library_ui/grid.rs#L509), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/manual.rs:1`](../../ui/dr-ui/src/manual.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:616`](../../ui/dr-ui/src/settings_ui.rs#L616), [`ui/dr-ui/ui/app.slint:1797`](../../ui/dr-ui/ui/app.slint#L1797), [`ui/dr-ui/ui/app.slint:1964`](../../ui/dr-ui/ui/app.slint#L1964), [`ui/dr-ui/ui/app.slint:2090`](../../ui/dr-ui/ui/app.slint#L2090), [`ui/dr-ui/ui/app.slint:2376`](../../ui/dr-ui/ui/app.slint#L2376), [`ui/dr-ui/ui/app.slint:2538`](../../ui/dr-ui/ui/app.slint#L2538), [`ui/dr-ui/ui/app.slint:2568`](../../ui/dr-ui/ui/app.slint#L2568), [`ui/dr-ui/ui/app.slint:261`](../../ui/dr-ui/ui/app.slint#L261), [`ui/dr-ui/ui/app.slint:2713`](../../ui/dr-ui/ui/app.slint#L2713), [`ui/dr-ui/ui/app.slint:2913`](../../ui/dr-ui/ui/app.slint#L2913), [`ui/dr-ui/ui/app.slint:3468`](../../ui/dr-ui/ui/app.slint#L3468), [`ui/dr-ui/ui/app.slint:3495`](../../ui/dr-ui/ui/app.slint#L3495), [`ui/dr-ui/ui/app.slint:689`](../../ui/dr-ui/ui/app.slint#L689), [`ui/dr-ui/ui/collections.slint:1340`](../../ui/dr-ui/ui/collections.slint#L1340), [`ui/dr-ui/ui/collections.slint:77`](../../ui/dr-ui/ui/collections.slint#L77), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:332`](../../ui/dr-ui/ui/develop.slint#L332), [`ui/dr-ui/ui/develop.slint:66`](../../ui/dr-ui/ui/develop.slint#L66), [`ui/dr-ui/ui/duplicates.slint:1`](../../ui/dr-ui/ui/duplicates.slint#L1), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/icons.slint:367`](../../ui/dr-ui/ui/icons.slint#L367), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1329`](../../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:1356`](../../ui/dr-ui/ui/library.slint#L1356), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1417`](../../ui/dr-ui/ui/library.slint#L1417), [`ui/dr-ui/ui/library.slint:1612`](../../ui/dr-ui/ui/library.slint#L1612), [`ui/dr-ui/ui/library.slint:1663`](../../ui/dr-ui/ui/library.slint#L1663), [`ui/dr-ui/ui/library.slint:1686`](../../ui/dr-ui/ui/library.slint#L1686), [`ui/dr-ui/ui/library.slint:1693`](../../ui/dr-ui/ui/library.slint#L1693), [`ui/dr-ui/ui/library.slint:1729`](../../ui/dr-ui/ui/library.slint#L1729), [`ui/dr-ui/ui/library.slint:1787`](../../ui/dr-ui/ui/library.slint#L1787), [`ui/dr-ui/ui/library.slint:1794`](../../ui/dr-ui/ui/library.slint#L1794), [`ui/dr-ui/ui/library.slint:3129`](../../ui/dr-ui/ui/library.slint#L3129), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:5032`](../../ui/dr-ui/ui/library.slint#L5032), [`ui/dr-ui/ui/library.slint:5070`](../../ui/dr-ui/ui/library.slint#L5070), [`ui/dr-ui/ui/library.slint:5174`](../../ui/dr-ui/ui/library.slint#L5174), [`ui/dr-ui/ui/library.slint:562`](../../ui/dr-ui/ui/library.slint#L562), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572), [`ui/dr-ui/ui/library.slint:666`](../../ui/dr-ui/ui/library.slint#L666), [`ui/dr-ui/ui/library.slint:735`](../../ui/dr-ui/ui/library.slint#L735), [`ui/dr-ui/ui/settings.slint:121`](../../ui/dr-ui/ui/settings.slint#L121), [`ui/dr-ui/ui/settings.slint:172`](../../ui/dr-ui/ui/settings.slint#L172), [`ui/dr-ui/ui/settings.slint:635`](../../ui/dr-ui/ui/settings.slint#L635) | -| FR-UI-5 | [`core/dr-types/src/place.rs:118`](../../core/dr-types/src/place.rs#L118), [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/develop_ui.rs:897`](../../ui/dr-ui/src/develop_ui.rs#L897), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/identity_ui.rs:1643`](../../ui/dr-ui/src/identity_ui.rs#L1643), [`ui/dr-ui/src/identity_ui.rs:802`](../../ui/dr-ui/src/identity_ui.rs#L802), [`ui/dr-ui/src/lib.rs:3486`](../../ui/dr-ui/src/lib.rs#L3486), [`ui/dr-ui/src/lib.rs:3504`](../../ui/dr-ui/src/lib.rs#L3504), [`ui/dr-ui/src/lib.rs:3518`](../../ui/dr-ui/src/lib.rs#L3518), [`ui/dr-ui/src/lib.rs:3699`](../../ui/dr-ui/src/lib.rs#L3699), [`ui/dr-ui/src/library/filters.rs:79`](../../ui/dr-ui/src/library/filters.rs#L79), [`ui/dr-ui/src/library_ui/filter_bar.rs:57`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L57), [`ui/dr-ui/src/library_ui/grid.rs:356`](../../ui/dr-ui/src/library_ui/grid.rs#L356), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1205`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1205), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1275`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1275), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/src/masks_ui.rs:1655`](../../ui/dr-ui/src/masks_ui.rs#L1655), [`ui/dr-ui/src/place.rs:331`](../../ui/dr-ui/src/place.rs#L331), [`ui/dr-ui/src/settings_ui.rs:502`](../../ui/dr-ui/src/settings_ui.rs#L502), [`ui/dr-ui/ui/app.slint:1081`](../../ui/dr-ui/ui/app.slint#L1081), [`ui/dr-ui/ui/app.slint:1661`](../../ui/dr-ui/ui/app.slint#L1661), [`ui/dr-ui/ui/app.slint:2376`](../../ui/dr-ui/ui/app.slint#L2376), [`ui/dr-ui/ui/app.slint:2526`](../../ui/dr-ui/ui/app.slint#L2526), [`ui/dr-ui/ui/app.slint:2668`](../../ui/dr-ui/ui/app.slint#L2668), [`ui/dr-ui/ui/app.slint:2713`](../../ui/dr-ui/ui/app.slint#L2713), [`ui/dr-ui/ui/app.slint:2766`](../../ui/dr-ui/ui/app.slint#L2766), [`ui/dr-ui/ui/app.slint:2797`](../../ui/dr-ui/ui/app.slint#L2797), [`ui/dr-ui/ui/app.slint:3433`](../../ui/dr-ui/ui/app.slint#L3433), [`ui/dr-ui/ui/app.slint:3452`](../../ui/dr-ui/ui/app.slint#L3452), [`ui/dr-ui/ui/app.slint:419`](../../ui/dr-ui/ui/app.slint#L419), [`ui/dr-ui/ui/app.slint:678`](../../ui/dr-ui/ui/app.slint#L678), [`ui/dr-ui/ui/app.slint:685`](../../ui/dr-ui/ui/app.slint#L685), [`ui/dr-ui/ui/app.slint:689`](../../ui/dr-ui/ui/app.slint#L689), [`ui/dr-ui/ui/app.slint:695`](../../ui/dr-ui/ui/app.slint#L695), [`ui/dr-ui/ui/app.slint:751`](../../ui/dr-ui/ui/app.slint#L751), [`ui/dr-ui/ui/collections.slint:110`](../../ui/dr-ui/ui/collections.slint#L110), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:384`](../../ui/dr-ui/ui/export.slint#L384), [`ui/dr-ui/ui/identity.slint:294`](../../ui/dr-ui/ui/identity.slint#L294), [`ui/dr-ui/ui/identity.slint:400`](../../ui/dr-ui/ui/identity.slint#L400), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1609`](../../ui/dr-ui/ui/library.slint#L1609), [`ui/dr-ui/ui/library.slint:1618`](../../ui/dr-ui/ui/library.slint#L1618), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:1635`](../../ui/dr-ui/ui/library.slint#L1635), [`ui/dr-ui/ui/library.slint:1883`](../../ui/dr-ui/ui/library.slint#L1883), [`ui/dr-ui/ui/library.slint:2012`](../../ui/dr-ui/ui/library.slint#L2012), [`ui/dr-ui/ui/library.slint:2652`](../../ui/dr-ui/ui/library.slint#L2652), [`ui/dr-ui/ui/library.slint:3095`](../../ui/dr-ui/ui/library.slint#L3095), [`ui/dr-ui/ui/library.slint:3171`](../../ui/dr-ui/ui/library.slint#L3171), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3203`](../../ui/dr-ui/ui/library.slint#L3203), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:3992`](../../ui/dr-ui/ui/library.slint#L3992), [`ui/dr-ui/ui/library.slint:5087`](../../ui/dr-ui/ui/library.slint#L5087), [`ui/dr-ui/ui/library.slint:642`](../../ui/dr-ui/ui/library.slint#L642), [`ui/dr-ui/ui/library.slint:660`](../../ui/dr-ui/ui/library.slint#L660), [`ui/dr-ui/ui/library.slint:681`](../../ui/dr-ui/ui/library.slint#L681), [`ui/dr-ui/ui/presets.slint:332`](../../ui/dr-ui/ui/presets.slint#L332), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) | +| FR-UI-5 | [`core/dr-types/src/place.rs:118`](../../core/dr-types/src/place.rs#L118), [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/develop_ui.rs:897`](../../ui/dr-ui/src/develop_ui.rs#L897), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/identity_ui.rs:1643`](../../ui/dr-ui/src/identity_ui.rs#L1643), [`ui/dr-ui/src/identity_ui.rs:802`](../../ui/dr-ui/src/identity_ui.rs#L802), [`ui/dr-ui/src/lib.rs:3486`](../../ui/dr-ui/src/lib.rs#L3486), [`ui/dr-ui/src/lib.rs:3504`](../../ui/dr-ui/src/lib.rs#L3504), [`ui/dr-ui/src/lib.rs:3518`](../../ui/dr-ui/src/lib.rs#L3518), [`ui/dr-ui/src/lib.rs:3699`](../../ui/dr-ui/src/lib.rs#L3699), [`ui/dr-ui/src/library/filters.rs:79`](../../ui/dr-ui/src/library/filters.rs#L79), [`ui/dr-ui/src/library_ui/filter_bar.rs:57`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L57), [`ui/dr-ui/src/library_ui/grid.rs:356`](../../ui/dr-ui/src/library_ui/grid.rs#L356), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1205`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1205), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1275`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1275), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/src/masks_ui.rs:1655`](../../ui/dr-ui/src/masks_ui.rs#L1655), [`ui/dr-ui/src/place.rs:331`](../../ui/dr-ui/src/place.rs#L331), [`ui/dr-ui/src/settings_ui.rs:502`](../../ui/dr-ui/src/settings_ui.rs#L502), [`ui/dr-ui/ui/app.slint:1081`](../../ui/dr-ui/ui/app.slint#L1081), [`ui/dr-ui/ui/app.slint:1661`](../../ui/dr-ui/ui/app.slint#L1661), [`ui/dr-ui/ui/app.slint:2376`](../../ui/dr-ui/ui/app.slint#L2376), [`ui/dr-ui/ui/app.slint:2526`](../../ui/dr-ui/ui/app.slint#L2526), [`ui/dr-ui/ui/app.slint:2668`](../../ui/dr-ui/ui/app.slint#L2668), [`ui/dr-ui/ui/app.slint:2713`](../../ui/dr-ui/ui/app.slint#L2713), [`ui/dr-ui/ui/app.slint:2766`](../../ui/dr-ui/ui/app.slint#L2766), [`ui/dr-ui/ui/app.slint:2797`](../../ui/dr-ui/ui/app.slint#L2797), [`ui/dr-ui/ui/app.slint:3433`](../../ui/dr-ui/ui/app.slint#L3433), [`ui/dr-ui/ui/app.slint:3452`](../../ui/dr-ui/ui/app.slint#L3452), [`ui/dr-ui/ui/app.slint:419`](../../ui/dr-ui/ui/app.slint#L419), [`ui/dr-ui/ui/app.slint:678`](../../ui/dr-ui/ui/app.slint#L678), [`ui/dr-ui/ui/app.slint:685`](../../ui/dr-ui/ui/app.slint#L685), [`ui/dr-ui/ui/app.slint:689`](../../ui/dr-ui/ui/app.slint#L689), [`ui/dr-ui/ui/app.slint:695`](../../ui/dr-ui/ui/app.slint#L695), [`ui/dr-ui/ui/app.slint:751`](../../ui/dr-ui/ui/app.slint#L751), [`ui/dr-ui/ui/collections.slint:110`](../../ui/dr-ui/ui/collections.slint#L110), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:384`](../../ui/dr-ui/ui/export.slint#L384), [`ui/dr-ui/ui/identity.slint:294`](../../ui/dr-ui/ui/identity.slint#L294), [`ui/dr-ui/ui/identity.slint:400`](../../ui/dr-ui/ui/identity.slint#L400), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1609`](../../ui/dr-ui/ui/library.slint#L1609), [`ui/dr-ui/ui/library.slint:1618`](../../ui/dr-ui/ui/library.slint#L1618), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:1635`](../../ui/dr-ui/ui/library.slint#L1635), [`ui/dr-ui/ui/library.slint:1883`](../../ui/dr-ui/ui/library.slint#L1883), [`ui/dr-ui/ui/library.slint:2012`](../../ui/dr-ui/ui/library.slint#L2012), [`ui/dr-ui/ui/library.slint:2652`](../../ui/dr-ui/ui/library.slint#L2652), [`ui/dr-ui/ui/library.slint:3095`](../../ui/dr-ui/ui/library.slint#L3095), [`ui/dr-ui/ui/library.slint:3171`](../../ui/dr-ui/ui/library.slint#L3171), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3203`](../../ui/dr-ui/ui/library.slint#L3203), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:3992`](../../ui/dr-ui/ui/library.slint#L3992), [`ui/dr-ui/ui/library.slint:5087`](../../ui/dr-ui/ui/library.slint#L5087), [`ui/dr-ui/ui/library.slint:642`](../../ui/dr-ui/ui/library.slint#L642), [`ui/dr-ui/ui/library.slint:660`](../../ui/dr-ui/ui/library.slint#L660), [`ui/dr-ui/ui/library.slint:681`](../../ui/dr-ui/ui/library.slint#L681), [`ui/dr-ui/ui/presets.slint:364`](../../ui/dr-ui/ui/presets.slint#L364), [`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:555`](../../core/dr-pipeline/src/framing.rs#L555), [`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:3311`](../../ui/dr-ui/src/lib.rs#L3311), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:2214`](../../ui/dr-ui/ui/app.slint#L2214), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`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:226`](../../ui/dr-ui/src/derived_sync.rs#L226), [`ui/dr-ui/src/derived_sync.rs:84`](../../ui/dr-ui/src/derived_sync.rs#L84), [`ui/dr-ui/src/derived_sync.rs:905`](../../ui/dr-ui/src/derived_sync.rs#L905), [`ui/dr-ui/src/derived_sync.rs:915`](../../ui/dr-ui/src/derived_sync.rs#L915), [`ui/dr-ui/src/derived_sync.rs:997`](../../ui/dr-ui/src/derived_sync.rs#L997), [`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/controller.rs:205`](../../ui/dr-ui/src/library_ui/controller.rs#L205), [`ui/dr-ui/src/library_ui/controller.rs:970`](../../ui/dr-ui/src/library_ui/controller.rs#L970), [`ui/dr-ui/src/library_ui/filter_bar.rs:793`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L793), [`ui/dr-ui/src/library_ui/grid.rs:172`](../../ui/dr-ui/src/library_ui/grid.rs#L172), [`ui/dr-ui/src/library_ui/grid.rs:285`](../../ui/dr-ui/src/library_ui/grid.rs#L285), [`ui/dr-ui/src/library_ui/grid.rs:327`](../../ui/dr-ui/src/library_ui/grid.rs#L327), [`ui/dr-ui/src/library_ui/grid.rs:668`](../../ui/dr-ui/src/library_ui/grid.rs#L668), [`ui/dr-ui/src/library_ui/grid.rs:794`](../../ui/dr-ui/src/library_ui/grid.rs#L794), [`ui/dr-ui/src/library_ui/open.rs:118`](../../ui/dr-ui/src/library_ui/open.rs#L118), [`ui/dr-ui/src/library_ui/open.rs:36`](../../ui/dr-ui/src/library_ui/open.rs#L36), [`ui/dr-ui/src/library_ui/open.rs:71`](../../ui/dr-ui/src/library_ui/open.rs#L71), [`ui/dr-ui/src/library_ui/open.rs:842`](../../ui/dr-ui/src/library_ui/open.rs#L842), [`ui/dr-ui/src/library_ui/open.rs:859`](../../ui/dr-ui/src/library_ui/open.rs#L859), [`ui/dr-ui/src/library_ui/open.rs:957`](../../ui/dr-ui/src/library_ui/open.rs#L957), [`ui/dr-ui/src/place.rs:1`](../../ui/dr-ui/src/place.rs#L1) | diff --git a/ui/dr-ui/src/presets.rs b/ui/dr-ui/src/presets.rs index 6d7ec31..d8edc25 100644 --- a/ui/dr-ui/src/presets.rs +++ b/ui/dr-ui/src/presets.rs @@ -739,26 +739,19 @@ impl NamedPresets { fn at(store: PresetStore) -> Rc { // TRACES: FR-DEV-6 - // Seed the starter set on a device that has never had a library, and - // only then. See `preset_starter` for why this is not a merge: - // re-adding on every start would resurrect one deleted on purpose. - // - // "Never had one" is the *file* not existing rather than the library - // being empty — a photographer who deleted all six must not be handed - // them again on the next launch. - let seeding = !store.path().exists(); - let library = if seeding { - let starters = dr_pipeline::starter::library(); - if let Err(e) = store.save(&starters) { - // Not fatal. The presets are in memory and usable this - // session; what is lost is their persistence, and refusing to - // start over it would be absurd. - log::warn!("could not write the starter presets: {e}"); - } - starters - } else { - store.load() - }; + // Nothing is seeded any more: the shipped presets are read from the + // binary every time (`dr_pipeline::bundled`). What a first run of an + // older build copied into this file is forgotten here where it is + // still exactly as copied — otherwise all six would list as changed + // and stay frozen at their old values. In memory only; the next save + // writes the file without them, and until then nothing is lost, + // because each one is identical to the preset now shipped in its + // place. + let mut library = store.load(); + let forgotten = dr_pipeline::bundled::forget_unchanged_copies(&mut library); + if forgotten > 0 { + log::info!("{forgotten} seeded preset copies are now shipped presets"); + } Rc::new(Self { store, library: RefCell::new(library), @@ -808,14 +801,45 @@ impl NamedPresets { message } - /// The stored names, in the order they are written. + /// The photographer's own stored names, in the order they are written. + #[cfg(test)] pub fn names(&self) -> Vec { self.library.borrow().names().map(String::from).collect() } - /// The preset stored under `name`. + /// The preset `name` applies: the photographer's if they have one under + /// that name, the shipped one otherwise. pub fn get(&self, name: &str) -> Option { - self.library.borrow().get(name).cloned() + dr_pipeline::bundled::lookup(&self.library.borrow(), name) + } + + /// Every row the sheet lists, headings included. + /// + /// The photographer's own section is left out while it is empty: a + /// heading over nothing reads as a list that failed to load. + pub fn rows(&self) -> Vec { + use dr_pipeline::bundled::Origin; + let mut rows = Vec::new(); + for section in dr_pipeline::bundled::listing(&self.library.borrow()) { + if section.rows.is_empty() { + continue; + } + rows.push(crate::PresetRow { + heading: section.title.into(), + name: Default::default(), + origin: crate::PresetOrigin::Shipped, + }); + rows.extend(section.rows.into_iter().map(|row| crate::PresetRow { + heading: Default::default(), + name: row.name.into(), + origin: match row.origin { + Origin::Yours => crate::PresetOrigin::Yours, + Origin::Shipped => crate::PresetOrigin::Shipped, + Origin::Changed => crate::PresetOrigin::Changed, + }, + })); + } + rows } /// Whether a preset is stored under `name`. @@ -898,10 +922,9 @@ impl SaveError { } } -/// Push the stored names onto the window. +/// Push the listing onto the window. pub fn render_named(window: &AppWindow, named: &Rc) { - let names: Vec = named.names().into_iter().map(Into::into).collect(); - window.set_preset_names(slint::ModelRc::new(slint::VecModel::from(names))); + window.set_preset_rows(slint::ModelRc::new(slint::VecModel::from(named.rows()))); } /// Wire saving, applying, renaming and deleting named presets. @@ -1452,69 +1475,112 @@ mod tests { // Named presets // ----------------------------------------------------------------------- - /// A library that has *already been started*, so the starter presets are - /// not in the way. - /// - /// Writing an empty library first is what makes the file exist, which is - /// the condition seeding tests — see `NamedPresets::at`. The seeding - /// itself has its own tests below rather than being switched off with a - /// flag that only tests would set. + /// A library in a fresh directory of its own. fn named(name: &str) -> (Rc, PathBuf) { - let dir = tempdir(name); - let path = dir.join("presets.drpl"); - PresetStore::open_at(path.clone()) - .save(&PresetLibrary::default()) - .unwrap(); - (NamedPresets::open_at(path), dir) - } - - /// A device that has never had a preset library. - fn fresh(name: &str) -> (Rc, PathBuf) { let dir = tempdir(name); (NamedPresets::open_at(dir.join("presets.drpl")), dir) } - #[test] - fn a_first_run_is_handed_the_starter_presets() { - // A sheet that opens on "No presets yet" teaches the photographer that - // the feature is homework. - let (presets, _dir) = fresh("first-run"); - assert_eq!(presets.names().len(), dr_pipeline::starter::library().len()); - assert!(!presets.names().is_empty()); + /// The rows under the heading `title`, as (name, origin). + fn section(presets: &NamedPresets, title: &str) -> Vec<(String, crate::PresetOrigin)> { + presets + .rows() + .into_iter() + .skip_while(|r| r.heading != title) + .skip(1) + .take_while(|r| r.heading.is_empty()) + .map(|r| (r.name.to_string(), r.origin)) + .collect() } #[test] - fn the_starters_are_written_out_so_they_survive_a_restart() { - let (_presets, dir) = fresh("starters-persist"); - let path = dir.join("presets.drpl"); - assert!(path.exists(), "the starter library was never written"); - assert!(!PresetStore::open_at(path).load().is_empty()); + fn a_first_run_lists_the_shipped_presets_and_writes_nothing() { + // A sheet that opens on "No presets yet" teaches the photographer + // that the feature is homework — and a copy written into their file + // would be frozen at this release's values. + let (presets, dir) = named("first-run"); + assert!(presets.names().is_empty()); + assert!(!section(&presets, "Essentials").is_empty()); + assert!(presets.get("Punch").is_some()); + assert!(!dir.join("presets.drpl").exists()); } #[test] - fn a_starter_the_photographer_deleted_stays_deleted() { - // The reason seeding keys on the file existing rather than on the - // library being empty: deleting all six must not hand them back on the - // next launch. - let dir = tempdir("deleted-starters"); - let path = dir.join("presets.drpl"); - { - let presets = NamedPresets::open_at(path.clone()); - for name in presets.names() { - let mut library = presets.library.borrow_mut(); - library.remove(&name); - } - presets.persist(|_| {}).unwrap(); - } - - let again = NamedPresets::open_at(path); - assert!( - again.names().is_empty(), - "the starters came back: {:?}", - again.names() + fn an_empty_section_of_your_own_is_not_listed() { + let (presets, _dir) = named("no-yours"); + assert!(presets.rows().iter().all(|r| r.heading != "Yours")); + presets.insert("Mine", Preset::default()).unwrap(); + assert_eq!( + section(&presets, "Yours"), + vec![("Mine".to_string(), crate::PresetOrigin::Yours)] ); } + #[test] + fn the_seeded_copies_of_an_older_build_become_the_shipped_presets() { + // A library as an older first run wrote it: the six, verbatim. + let dir = tempdir("seeded-copies"); + let path = dir.join("presets.drpl"); + let essentials = dr_pipeline::bundled::sections() + .into_iter() + .find(|s| s.id == "essentials") + .unwrap() + .presets; + let mut seeded = PresetLibrary::default(); + for (name, preset) in essentials.iter() { + seeded + .insert(name, preset.clone().with_reach(dr_pipeline::Reach::Whole)) + .unwrap(); + } + PresetStore::open_at(path.clone()).save(&seeded).unwrap(); + + let presets = NamedPresets::open_at(path); + assert!(presets.names().is_empty(), "{:?}", presets.names()); + assert!(section(&presets, "Essentials") + .iter() + .all(|(_, origin)| *origin == crate::PresetOrigin::Shipped)); + } + + #[test] + fn saving_over_a_shipped_name_changes_it_and_deleting_reverts() { + let (presets, _dir) = named("override"); + let shipped = presets.get("Punch").unwrap(); + + presets.insert("Punch", Preset::capture(&edited())).unwrap(); + assert_eq!(presets.get("Punch"), Some(Preset::capture(&edited()))); + assert!(section(&presets, "Essentials") + .contains(&("Punch".to_string(), crate::PresetOrigin::Changed))); + + presets.library.borrow_mut().remove("Punch"); + assert_eq!(presets.get("Punch"), Some(shipped)); + } + + /// TRACES: FR-DEV-6 | FR-DEV-3f + /// Every stock a shipped preset names is one this build can bake, on the + /// paper the preset says. + /// + /// Here rather than beside the presets: `dr-pipeline` does not link the + /// profile database, and a misspelt stock there would ship a preset that + /// logs a warning and develops without a film. The paper is checked too, + /// because the session prints on a stock's *own* paper and reads only + /// whether one was asked for — a preset naming another would say one + /// thing and render another. + #[test] + fn every_shipped_film_is_a_stock_this_build_can_bake() { + for section in dr_pipeline::bundled::sections() { + for (name, preset) in section.presets.iter() { + let Some(film) = preset.film() else { continue }; + let profile = dr_film::find(&film.stock) + .unwrap_or_else(|| panic!("{name}: no stock {:?}", film.stock)); + if let Some(print) = &film.print { + let paper = dr_film::default_print(profile) + .unwrap_or_else(|| panic!("{name}: {} has no paper", film.stock)); + assert_eq!(&paper.stock, print, "{name}"); + } + } + } + } + #[test] fn importing_a_folder_stores_what_it_finds() { let (presets, dir) = named("import-folder"); @@ -1613,11 +1679,7 @@ mod tests { let presets = NamedPresets::open_at(blocked.join("presets.drpl")); assert!(presets.insert("Warm", Preset::default()).is_err()); - // Not "the library is empty": the starter set is held in memory even - // on a device whose config directory cannot be written, which is the - // right behaviour — the presets are usable this session and only their - // persistence is lost. What must not survive is the preset whose write - // failed. + // What must not survive is the preset whose write failed. assert!( !presets.contains("Warm"), "the failed save left a preset behind: {:?}", diff --git a/ui/dr-ui/ui/app.slint b/ui/dr-ui/ui/app.slint index 49e18d5..842b1e0 100644 --- a/ui/dr-ui/ui/app.slint +++ b/ui/dr-ui/ui/app.slint @@ -14,7 +14,7 @@ import { CollectionsPanel, CollectionRow, Collections, OfflinePrompt, Collection MembershipSheet, MembershipRow } from "collections.slint"; import { HistogramPanel, HistogramView, Levels } from "histogram.slint"; import { RecoveryPrompt } from "recovery.slint"; -import { CopySheet, PresetSheet, ScopeChips, ScopeKind } from "presets.slint"; +import { CopySheet, PresetOrigin, PresetRow, PresetSheet, ScopeChips, ScopeKind } from "presets.slint"; import { ExportOptions, ExportSheet } from "export.slint"; import { FocusMarks, FocusPanel, Peaking } from "peaking.slint"; import { SettingsPage } from "settings.slint"; @@ -499,7 +499,7 @@ export component AppWindow inherits Window { // where there is an edit to capture, and applied most often in the grid, // where there is a selection to apply it to. in-out property presets-open: false; - in property <[string]> preset-names; + in property <[PresetRow]> preset-rows; /// Whether there is an edit in hand to save, set by whichever view opened /// the sheet. `in-out` because that is where the answer is known. in-out property preset-can-save: false; @@ -3412,7 +3412,7 @@ in property panel-visible: true; if root.presets-open: PresetSheet { width: 100%; height: 100%; - names: root.preset-names; + rows: root.preset-rows; can-save: root.preset-can-save; apply-count: root.preset-apply-count; capture-summary: root.preset-capture-summary; diff --git a/ui/dr-ui/ui/presets.slint b/ui/dr-ui/ui/presets.slint index b009b9f..73b8de1 100644 --- a/ui/dr-ui/ui/presets.slint +++ b/ui/dr-ui/ui/presets.slint @@ -16,6 +16,31 @@ export struct ScopeKind { on: bool, } +// TRACES: FR-DEV-6 +// Where a listed preset comes from, which decides what can be done to it. +export enum PresetOrigin { + // The photographer's own. + yours, + // Shipped with the application. Applied, never renamed or deleted. + shipped, + // The photographer's copy under a shipped preset's name, standing in for + // it. Deleting it reverts to the shipped one; renaming it makes it theirs. + changed, +} + +// TRACES: FR-DEV-6 +// One row of the preset sheet: a section heading, or a preset. +// +// One flat list with the headings in it rather than a list of sections, for +// the reason the rows are placed by index below: a nested `for` inside a +// `Flickable` has no layout to size it, and the arithmetic stays one line. +export struct PresetRow { + // Non-empty on a heading row, which names no preset. + heading: string, + name: string, + origin: PresetOrigin, +} + // TRACES: FR-DEV-6 | FR-UI-2 // Which kinds of edit travel: a multi-select block of chips that wraps. // @@ -78,8 +103,9 @@ export component ScopeChips inherits Rectangle { // second idiom for the same gesture would be a second thing to learn for no // gain. export component PresetSheet inherits Rectangle { - /// The saved names, in the order they are stored. - in property <[string]> names; + /// The photographer's presets and the shipped ones, sectioned, in the + /// order they are listed. + in property <[PresetRow]> rows; /// Whether there is an edit in hand to save. False in the library, where /// nothing is open, and with an image that failed to decode. in property can-save: false; @@ -198,14 +224,21 @@ export component PresetSheet inherits Rectangle { // A floor, so the list is not squeezed out of existence by the // field and the button around it on a short window. min-height: 120px; - viewport-height: root.names.length * (Theme.touch-target + 2px); + viewport-height: root.rows.length * (Theme.touch-target + 2px); - for entry[i] in root.names: Rectangle { + for entry[i] in root.rows: Rectangle { y: i * (Theme.touch-target + 2px); width: parent.width; height: Theme.touch-target; - if root.renaming != entry: HorizontalLayout { + if entry.heading != "": PanelHeading { + text: entry.heading; + width: parent.width; + height: parent.height; + vertical-alignment: bottom; + } + + if entry.heading == "" && root.renaming != entry.name: HorizontalLayout { spacing: Theme.gap-sm; // The name is the apply button rather than a label @@ -213,33 +246,42 @@ export component PresetSheet inherits Rectangle { // row whose largest target does nothing is a row that // gets pressed by accident and then distrusted. Button { - text: entry; + text: entry.origin == PresetOrigin.changed + ? entry.name + " · changed" + : entry.name; horizontal-stretch: 1; - clicked => { root.apply(entry); } + clicked => { root.apply(entry.name); } } - Button { + // A shipped preset is applied and nothing else: it is + // not the photographer's to rename or delete, and a + // button that did either would be one that lied. + // Saving over its name is how it is changed. + if entry.origin != PresetOrigin.shipped: Button { text: "Rename"; - clicked => { root.renaming = entry; } + clicked => { root.renaming = entry.name; } } - Button { - text: "Delete"; - clicked => { root.remove(entry); } + // "Revert" on a changed one, because that is what + // deleting the copy does: the shipped preset is + // still there underneath it. + if entry.origin != PresetOrigin.shipped: Button { + text: entry.origin == PresetOrigin.changed ? "Revert" : "Delete"; + clicked => { root.remove(entry.name); } } } // Renaming in place rather than in a second sheet: a // dialogue over a dialogue is where a user loses track of // which one Escape closes. - if root.renaming == entry: HorizontalLayout { + if entry.heading == "" && root.renaming == entry.name: HorizontalLayout { spacing: Theme.gap-sm; rename-field := Field { - text: entry; + text: entry.name; horizontal-stretch: 1; accepted(text) => { - root.rename(entry, text); + root.rename(entry.name, text); root.renaming = ""; } } @@ -252,20 +294,10 @@ export component PresetSheet inherits Rectangle { } } - if root.names.length == 0: Text { - text: root.can-save - ? "No presets yet. Name the edit above to make the first." - : "No presets yet. Open a photograph and save one from the develop panel."; - color: Theme.ink-faint; - font-size: Theme.text-sm; - wrap: word-wrap; - width: parent.width; - } - // Says what applying would do *before* it is done, the same way // the grid's "Paste to 40" does — a count in the label is worth // more than a confirmation asking the same question afterwards. - if root.names.length > 0 && root.apply-count > 0: Caption { + if root.apply-count > 0: Caption { text: root.apply-count == 1 ? "Applies to 1 selected photograph" : "Applies to " + root.apply-count + " selected photographs";