List the steps, and let a photographer step straight to one
Build and test / Desktop (Linux) (push) Successful in 19m30s
Build and test / Layer separation (push) Successful in 25s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
Traceability / Requirement traces (push) Successful in 23s
Build and test / Android (aarch64) (push) Failing after 33m5s
Build and test / Desktop (Linux) (push) Successful in 19m30s
Build and test / Layer separation (push) Successful in 25s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
Traceability / Requirement traces (push) Successful in 23s
Build and test / Android (aarch64) (push) Failing after 33m5s
Undo answers "take back the last thing", which is the question asked about a mistake just noticed. It is the wrong instrument for one noticed six adjustments later: eight presses, each changing the picture, with no way to see how far back the mistake is without passing through it. A step is a whole state, so arriving from six away costs what arriving from one does — which is what makes a row worth making clickable rather than decorative. `Edit::Discrete` had to go for the list to be worth drawing. Seventeen call sites recorded the same anonymous step, which is fine for deciding whether two changes are one gesture and useless for a panel: seventeen rows reading "Discrete" is not a history. Every variant now carries enough to name itself, and the compiler enumerated the sites that had to start saying so. A step that moved a parameter is still named out of the descriptor, so an operation added as a YAML declaration appears in the history correctly named with nothing written for it (FR-DEV-3c). Choosing a film stock was not undoable at all. The pick went straight to `choose_film`, which nothing on the history's path ever sees. `pick_film` records it, and is separate because the same call is also how a *restored* edit gets its tables back — recording that would push a step for the undo the photographer had just asked for. The list is rebuilt off a revision rather than off every redraw. A drag ends in a redraw per frame while folding into one step, so the unconditional version would tear down and recreate every row sixty times a second to arrive back at the list already on screen. The counter is process-wide: a per-instance one starts every photograph at the same number, so a frontend holding "the revision I last drew" would keep the previous image's steps on screen — invisible while every image opens with one identical row, and a wrong-photograph bug the moment persisted history means it does not. The step names that no descriptor can supply are constants with a roll, and a test walks the roll rather than a second copy of it. `resolve` splits so that "is this catalogued?" can be asked: `derive` turns `history.mask_toggled` into "Mask Toggled", which names a field rather than an act and, being perfectly readable, is a mistake nobody would look at twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+482
-31
@@ -75,10 +75,12 @@
|
|||||||
//! automatically saved mis-drag had no way back at all, and that is what this
|
//! automatically saved mis-drag had no way back at all, and that is what this
|
||||||
//! closes.
|
//! closes.
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::descriptor::{OpId, ParamId};
|
use crate::descriptor::{LocalizedKey, OpId, ParamId};
|
||||||
use crate::graph::EditGraph;
|
use crate::graph::EditGraph;
|
||||||
|
use crate::graph::OpCapability;
|
||||||
use crate::state::{EditState, FilmRebake};
|
use crate::state::{EditState, FilmRebake};
|
||||||
|
|
||||||
/// TRACES: FR-DEV-5 | NFR-RES-1
|
/// TRACES: FR-DEV-5 | NFR-RES-1
|
||||||
@@ -106,22 +108,52 @@ pub const DEPTH: usize = 64;
|
|||||||
pub const COALESCE_WINDOW: Duration = Duration::from_millis(700);
|
pub const COALESCE_WINDOW: Duration = Duration::from_millis(700);
|
||||||
|
|
||||||
/// TRACES: FR-DEV-5
|
/// TRACES: FR-DEV-5
|
||||||
/// Which control a change came from, for deciding whether two changes are one
|
/// What the state a photograph opened in is called.
|
||||||
/// gesture.
|
|
||||||
///
|
///
|
||||||
/// Not "what changed" — the snapshot already carries that. This exists purely
|
/// A step like any other in the list, because it is one: it is where undo
|
||||||
/// so that consecutive changes can be told apart from one continuing change.
|
/// stops, and a list whose first row is blank would leave the floor looking
|
||||||
|
/// like a missing entry rather than the beginning.
|
||||||
|
pub const OPENED: LocalizedKey = LocalizedKey("history.opened");
|
||||||
|
|
||||||
|
/// The fallback for a step naming an operation or parameter this build no
|
||||||
|
/// longer has.
|
||||||
|
///
|
||||||
|
/// Reachable only across a version skew, and it resolves to a readable word
|
||||||
|
/// rather than to nothing: a row the user cannot identify is still better than
|
||||||
|
/// a row that is not there, since the step exists and undo will pass through
|
||||||
|
/// it either way.
|
||||||
|
pub const UNNAMED: LocalizedKey = LocalizedKey("history.edit");
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// Which control a change came from.
|
||||||
|
///
|
||||||
|
/// Two jobs, and they used to be one. Deciding whether consecutive changes are
|
||||||
|
/// one continuing gesture needs only an *identity* — that was the whole of
|
||||||
|
/// this type, and `Discrete` was a perfectly good name for "some control, no
|
||||||
|
/// gesture". Listing the steps needs each one to say what it **was**, and
|
||||||
|
/// seventeen rows reading "Discrete" is not a history. So every variant now
|
||||||
|
/// carries enough to name itself, and the compiler enumerated the call sites
|
||||||
|
/// that had to start saying so.
|
||||||
|
///
|
||||||
|
/// It is still not "what changed" — the snapshot carries that.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Edit {
|
pub enum Edit {
|
||||||
/// One parameter's own control, dragged.
|
/// One parameter's own control, dragged. Named by its descriptor.
|
||||||
Param(OpId, ParamId),
|
Param(OpId, ParamId),
|
||||||
/// A whole operation, where one gesture moves several of its parameters at
|
/// A whole operation, where one gesture moves several of its parameters at
|
||||||
/// once — a curve point, or a crop rectangle's four edges.
|
/// once — a curve point, or a crop rectangle's four edges. Named by its
|
||||||
|
/// descriptor.
|
||||||
Op(OpId),
|
Op(OpId),
|
||||||
|
/// A dragged control the graph does not own: a mask layer's feather, its
|
||||||
|
/// opacity, a gradient handle. Coalesces like a slider, because it is one
|
||||||
|
/// — but there is no descriptor to ask for a name, so it carries its own.
|
||||||
|
Control(LocalizedKey),
|
||||||
/// A change with no gesture behind it: a reset, a paste, a flip, a quarter
|
/// A change with no gesture behind it: a reset, a paste, a flip, a quarter
|
||||||
/// turn. Never coalesces, not even with an identical one, because two
|
/// turn, a layer added. Never coalesces, not even with an identical one,
|
||||||
/// clicks are two decisions however quickly they follow each other.
|
/// because two clicks are two decisions however quickly they follow each
|
||||||
Discrete,
|
/// other — which is also why the key is not enough to tell two of them
|
||||||
|
/// apart and does not have to be.
|
||||||
|
Action(LocalizedKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Edit {
|
impl Edit {
|
||||||
@@ -146,8 +178,57 @@ impl Edit {
|
|||||||
|
|
||||||
/// Whether a second change to this same control continues the first.
|
/// Whether a second change to this same control continues the first.
|
||||||
fn is_gesture(self) -> bool {
|
fn is_gesture(self) -> bool {
|
||||||
!matches!(self, Self::Discrete)
|
!matches!(self, Self::Action(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// What to call this step in a list of them.
|
||||||
|
///
|
||||||
|
/// A [`LocalizedKey`], never a string: resolving one needs a localiser and
|
||||||
|
/// `core/` must not depend on one (NFR-A11Y-1). The frontend has the
|
||||||
|
/// catalogue, and an uncatalogued key derives a readable fallback — so an
|
||||||
|
/// operation added as a YAML declaration appears in the history under a
|
||||||
|
/// sensible name with no code written for it, on the same terms it appears
|
||||||
|
/// in the panel (FR-DEV-3c).
|
||||||
|
///
|
||||||
|
/// Takes the capabilities rather than the graph so that listing a whole
|
||||||
|
/// stack walks the chain once instead of once per step.
|
||||||
|
pub fn label(self, caps: &[OpCapability]) -> LocalizedKey {
|
||||||
|
match self {
|
||||||
|
Self::Param(op, param) => caps
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.id == op)
|
||||||
|
.and_then(|c| c.params.iter().find(|p| p.id == param))
|
||||||
|
.map(|p| p.label)
|
||||||
|
.unwrap_or(UNNAMED),
|
||||||
|
Self::Op(op) => caps
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.id == op)
|
||||||
|
.map(|c| c.label)
|
||||||
|
.unwrap_or(UNNAMED),
|
||||||
|
Self::Control(key) | Self::Action(key) => key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
/// One row of the history, for a frontend that lists them.
|
||||||
|
///
|
||||||
|
/// Carries its own [`Self::index`] rather than leaving the caller to infer one
|
||||||
|
/// from a position in the returned list. The list a photographer reads runs
|
||||||
|
/// newest-first — the same order the trash is reviewed in, and for the same
|
||||||
|
/// reason: it is consulted to undo a recent mistake rather than browsed
|
||||||
|
/// chronologically — so the row's position and its position in the stack are
|
||||||
|
/// deliberately not the same number, and a frontend doing that arithmetic
|
||||||
|
/// itself is a frontend that will one day jump to the wrong state.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Entry {
|
||||||
|
/// Where this state sits in the stack. What [`History::go_to`] takes.
|
||||||
|
pub index: usize,
|
||||||
|
/// What to call it. A localisation key; the frontend resolves it.
|
||||||
|
pub label: LocalizedKey,
|
||||||
|
/// Whether this is the state the graph is showing now.
|
||||||
|
pub current: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-DEV-5
|
/// TRACES: FR-DEV-5
|
||||||
@@ -186,6 +267,29 @@ impl Step {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hands out revisions, shared by every [`History`] in the process.
|
||||||
|
///
|
||||||
|
/// See [`History::revision`]. `Relaxed` because the only thing required of
|
||||||
|
/// these values is that they differ: nothing is published through the counter,
|
||||||
|
/// and a frontend comparing two of them is doing so on its own thread.
|
||||||
|
static REVISIONS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
fn next_revision() -> u64 {
|
||||||
|
REVISIONS.fetch_add(1, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A state, and what put the graph into it.
|
||||||
|
///
|
||||||
|
/// The edit is kept for the *list*, not for the stepping: undo restores by
|
||||||
|
/// replacing the whole state, so it never needs to know what the change was.
|
||||||
|
/// It needs to know only in order to say so.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
struct Snapshot {
|
||||||
|
state: EditState,
|
||||||
|
/// `None` for the floor, which nothing in this session produced.
|
||||||
|
edit: Option<Edit>,
|
||||||
|
}
|
||||||
|
|
||||||
/// TRACES: FR-DEV-5
|
/// TRACES: FR-DEV-5
|
||||||
/// One image's undo stack.
|
/// One image's undo stack.
|
||||||
///
|
///
|
||||||
@@ -194,8 +298,36 @@ impl Step {
|
|||||||
/// where redo goes. `states` is never empty — the state the history was opened
|
/// where redo goes. `states` is never empty — the state the history was opened
|
||||||
/// on is the floor, and undo stops there rather than at nothing.
|
/// on is the floor, and undo stops there rather than at nothing.
|
||||||
pub struct History {
|
pub struct History {
|
||||||
states: Vec<EditState>,
|
states: Vec<Snapshot>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
/// A value that changes whenever [`Self::entries`] would read differently,
|
||||||
|
/// and that **no other history will ever report**.
|
||||||
|
///
|
||||||
|
/// For a frontend that draws the list. Rebuilding it costs a walk of the
|
||||||
|
/// chain, sixty-odd label lookups and — the part that actually hurts — a
|
||||||
|
/// model reset that makes the toolkit tear down and rebuild every row. A
|
||||||
|
/// drag emits a change per frame and coalesces into the step already on
|
||||||
|
/// top, so during the one gesture where that cost would be paid sixty
|
||||||
|
/// times a second, the list is not changing at all.
|
||||||
|
///
|
||||||
|
/// A counter rather than the `(depth, cursor)` pair it is tempting to
|
||||||
|
/// compare instead: stepping back one and then editing truncates the tail
|
||||||
|
/// and pushes a replacement, which can land on the same depth and the same
|
||||||
|
/// cursor with a different step on top. The pair would call that unchanged
|
||||||
|
/// and the panel would name the branch the photographer just abandoned.
|
||||||
|
///
|
||||||
|
/// **Drawn from a counter shared by every history in the process**, which
|
||||||
|
/// is the part that is easy to leave out and expensive to add back. A
|
||||||
|
/// per-instance counter starts each photograph at the same number, so a
|
||||||
|
/// frontend holding "the revision I last drew" sees a *stale* value match
|
||||||
|
/// a *fresh* history and keeps the previous photograph's list on screen.
|
||||||
|
/// Today that is invisible, because a freshly opened image always has the
|
||||||
|
/// same one row — and it stops being invisible the moment FR-DEV-5's
|
||||||
|
/// persisted history means an image opens with steps already in it, at
|
||||||
|
/// which point the panel shows another photograph's work. A shared counter
|
||||||
|
/// is two lines and the failure cannot occur; the alternative is every
|
||||||
|
/// frontend remembering to invalidate on open.
|
||||||
|
revision: u64,
|
||||||
/// What produced `states[cursor]`, and when — the pair that decides
|
/// What produced `states[cursor]`, and when — the pair that decides
|
||||||
/// whether the next change amends it. `None` means the current step is
|
/// whether the next change amends it. `None` means the current step is
|
||||||
/// closed: nothing may be folded into it.
|
/// closed: nothing may be folded into it.
|
||||||
@@ -206,8 +338,12 @@ impl History {
|
|||||||
/// Start from the state `graph` is in.
|
/// Start from the state `graph` is in.
|
||||||
pub fn new(graph: &EditGraph) -> Self {
|
pub fn new(graph: &EditGraph) -> Self {
|
||||||
Self {
|
Self {
|
||||||
states: vec![graph.state()],
|
states: vec![Snapshot {
|
||||||
|
state: graph.state(),
|
||||||
|
edit: None,
|
||||||
|
}],
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
|
revision: next_revision(),
|
||||||
last: None,
|
last: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,14 +370,21 @@ impl History {
|
|||||||
/// without sleeping.
|
/// without sleeping.
|
||||||
pub fn record_at(&mut self, graph: &EditGraph, edit: Edit, at: Instant) -> bool {
|
pub fn record_at(&mut self, graph: &EditGraph, edit: Edit, at: Instant) -> bool {
|
||||||
let state = graph.state();
|
let state = graph.state();
|
||||||
if self.states.get(self.cursor) == Some(&state) {
|
if self.states.get(self.cursor).map(|s| &s.state) == Some(&state) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.coalesces(edit, at) {
|
if self.coalesces(edit, at) {
|
||||||
if let Some(top) = self.states.get_mut(self.cursor) {
|
if let Some(top) = self.states.get_mut(self.cursor) {
|
||||||
*top = state;
|
// The label stays whatever opened the step. It is the same
|
||||||
|
// control by definition — that is what coalescing decided —
|
||||||
|
// and rewriting it every frame of a drag would be work to
|
||||||
|
// arrive back at the word already there.
|
||||||
|
top.state = state;
|
||||||
self.last = Some((edit, at));
|
self.last = Some((edit, at));
|
||||||
|
// No bump. The row is the same row with the same name; only
|
||||||
|
// the state behind it moved, and nothing drawing the list can
|
||||||
|
// tell. This is the case the counter exists for.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +393,10 @@ impl History {
|
|||||||
// is no longer reachable from here, and keeping it would let redo jump
|
// is no longer reachable from here, and keeping it would let redo jump
|
||||||
// to a state this one was never derived from.
|
// to a state this one was never derived from.
|
||||||
self.states.truncate(self.cursor + 1);
|
self.states.truncate(self.cursor + 1);
|
||||||
self.states.push(state);
|
self.states.push(Snapshot {
|
||||||
|
state,
|
||||||
|
edit: Some(edit),
|
||||||
|
});
|
||||||
self.cursor = self.states.len().saturating_sub(1);
|
self.cursor = self.states.len().saturating_sub(1);
|
||||||
|
|
||||||
// Oldest first, so what is lost is the part furthest from where the
|
// Oldest first, so what is lost is the part furthest from where the
|
||||||
@@ -262,6 +408,7 @@ impl History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.last = Some((edit, at));
|
self.last = Some((edit, at));
|
||||||
|
self.revision = next_revision();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,16 +454,17 @@ impl History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn restore(&mut self, graph: &mut EditGraph, target: usize) -> Step {
|
fn restore(&mut self, graph: &mut EditGraph, target: usize) -> Step {
|
||||||
let Some(state) = self.states.get(target).cloned() else {
|
let Some(snapshot) = self.states.get(target).cloned() else {
|
||||||
return Step::Nowhere;
|
return Step::Nowhere;
|
||||||
};
|
};
|
||||||
let rebake = graph.set_state(&state);
|
let rebake = graph.set_state(&snapshot.state);
|
||||||
self.cursor = target;
|
self.cursor = target;
|
||||||
// Closes the current step. Without this, a slider moved immediately
|
// Closes the current step. Without this, a slider moved immediately
|
||||||
// after an undo would fold into the step it was just undone out of —
|
// after an undo would fold into the step it was just undone out of —
|
||||||
// and the state the user had just recovered would be overwritten by
|
// and the state the user had just recovered would be overwritten by
|
||||||
// the very edit they made from it.
|
// the very edit they made from it.
|
||||||
self.last = None;
|
self.last = None;
|
||||||
|
self.revision = next_revision();
|
||||||
Step::Took(rebake)
|
Step::Took(rebake)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +475,71 @@ impl History {
|
|||||||
pub fn depth(&self) -> usize {
|
pub fn depth(&self) -> usize {
|
||||||
self.states.len()
|
self.states.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// Where in the stack the graph currently stands.
|
||||||
|
///
|
||||||
|
/// Everything below it is undo; everything above it is redo. Exposed so a
|
||||||
|
/// frontend can mark the row rather than infer it from a run of
|
||||||
|
/// [`Self::can_undo`] calls.
|
||||||
|
pub fn cursor(&self) -> usize {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// A number that changes exactly when [`Self::entries`] would.
|
||||||
|
///
|
||||||
|
/// What a frontend compares against the last list it drew, so that a drag
|
||||||
|
/// — which amends the step on top sixty times a second without changing
|
||||||
|
/// a single row — does not rebuild the panel once a frame.
|
||||||
|
pub fn revision(&self) -> u64 {
|
||||||
|
self.revision
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
/// Every step, oldest first, each saying what it was.
|
||||||
|
///
|
||||||
|
/// **Oldest first, though a photographer reads the list the other way
|
||||||
|
/// round.** The order a list is *displayed* in is a frontend decision — it
|
||||||
|
/// depends on where the panel is and which end has the room — whereas the
|
||||||
|
/// order the stack is in is a fact. Reversing here would bake one
|
||||||
|
/// interface's choice into the core and leave every [`Entry::index`]
|
||||||
|
/// counting backwards for everyone else.
|
||||||
|
///
|
||||||
|
/// Walks the chain once for the whole list rather than once per step:
|
||||||
|
/// resolving a label asks the descriptors, and sixty-four steps against a
|
||||||
|
/// twenty-operation chain is otherwise a thousand lookups for one panel
|
||||||
|
/// refresh.
|
||||||
|
pub fn entries(&self, graph: &EditGraph) -> Vec<Entry> {
|
||||||
|
let caps = graph.capabilities();
|
||||||
|
self.states
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, snapshot)| Entry {
|
||||||
|
index,
|
||||||
|
label: snapshot.edit.map_or(OPENED, |edit| edit.label(&caps)),
|
||||||
|
current: index == self.cursor,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
/// Jump straight to one step, however far away it is.
|
||||||
|
///
|
||||||
|
/// The same restore undo and redo make — a step is a whole state, so
|
||||||
|
/// arriving at one from six steps away costs exactly what arriving from
|
||||||
|
/// one does, and there is no sequence of intermediate states to replay.
|
||||||
|
/// That is the property that makes a clickable list worth having rather
|
||||||
|
/// than a decoration over the buttons.
|
||||||
|
///
|
||||||
|
/// Jumping to where the graph already is reports [`Step::Nowhere`], so a
|
||||||
|
/// frontend can click the current row without paying for a redraw.
|
||||||
|
pub fn go_to(&mut self, graph: &mut EditGraph, index: usize) -> Step {
|
||||||
|
if index == self.cursor {
|
||||||
|
return Step::Nowhere;
|
||||||
|
}
|
||||||
|
self.restore(graph, index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -393,6 +606,237 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_photographs_never_report_the_same_revision() {
|
||||||
|
// A frontend holds the revision it last drew and rebuilds when it
|
||||||
|
// moves. Per-instance counters start every photograph at the same
|
||||||
|
// number, so a stale value matches a fresh history and the panel keeps
|
||||||
|
// the previous image's steps on screen — invisible while every image
|
||||||
|
// opens with one identical row, and a wrong-photograph bug the moment
|
||||||
|
// FR-DEV-5's persisted history means it does not.
|
||||||
|
let g = EditGraph::default_chain();
|
||||||
|
let first = History::new(&g);
|
||||||
|
let second = History::new(&g);
|
||||||
|
assert_ne!(first.revision(), second.revision());
|
||||||
|
|
||||||
|
// Including across the reset that opening an image makes.
|
||||||
|
let mut reused = History::new(&g);
|
||||||
|
let before = reused.revision();
|
||||||
|
reused.reset(&g);
|
||||||
|
assert_ne!(reused.revision(), before);
|
||||||
|
assert_ne!(reused.revision(), first.revision());
|
||||||
|
assert_ne!(reused.revision(), second.revision());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_drag_does_not_make_the_list_look_different() {
|
||||||
|
// The whole reason the counter exists. A drag emits a change per frame
|
||||||
|
// and folds every one into the step already on top: the rows do not
|
||||||
|
// move, and a panel that rebuilt itself anyway would tear down and
|
||||||
|
// recreate sixty rows sixty times a second for no visible change.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
let base = Instant::now();
|
||||||
|
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, 0.1);
|
||||||
|
h.record_at(
|
||||||
|
&g,
|
||||||
|
Edit::for_param(&g, exposure::ID, exposure::EXPOSURE),
|
||||||
|
base,
|
||||||
|
);
|
||||||
|
let opened = h.revision();
|
||||||
|
|
||||||
|
drag(&mut h, &mut g, base, 0.1, 1.5, 40);
|
||||||
|
assert_eq!(
|
||||||
|
h.revision(),
|
||||||
|
opened,
|
||||||
|
"coalescing into one row still moved the counter"
|
||||||
|
);
|
||||||
|
assert_eq!(h.entries(&g).len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_counter_moves_for_everything_that_shows() {
|
||||||
|
// The complement, and each of these is a case that would otherwise
|
||||||
|
// leave the panel drawing a list the photograph is no longer in.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
|
||||||
|
let mut seen = vec![h.revision()];
|
||||||
|
let note = |h: &History, seen: &mut Vec<u64>, what: &str| {
|
||||||
|
assert!(
|
||||||
|
!seen.contains(&h.revision()),
|
||||||
|
"{what} did not move the counter"
|
||||||
|
);
|
||||||
|
seen.push(h.revision());
|
||||||
|
};
|
||||||
|
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, 0.1);
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
note(&h, &mut seen, "recording a step");
|
||||||
|
|
||||||
|
assert!(h.undo(&mut g).moved());
|
||||||
|
note(&h, &mut seen, "an undo");
|
||||||
|
|
||||||
|
assert!(h.redo(&mut g).moved());
|
||||||
|
note(&h, &mut seen, "a redo");
|
||||||
|
|
||||||
|
assert!(h.go_to(&mut g, 0).moved());
|
||||||
|
note(&h, &mut seen, "a jump");
|
||||||
|
|
||||||
|
h.reset(&EditGraph::default_chain());
|
||||||
|
note(&h, &mut seen, "opening another photograph");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_step_that_moves_nothing_leaves_the_counter_alone() {
|
||||||
|
// `record` returning false means no row appeared, so nothing that
|
||||||
|
// draws rows has anything to do.
|
||||||
|
let g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
let before = h.revision();
|
||||||
|
assert!(!h.record(&g, Edit::Action(LocalizedKey("history.test"))));
|
||||||
|
assert_eq!(h.revision(), before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_step_says_what_it_was() {
|
||||||
|
// The reason `Discrete` had to go. A list is only worth drawing if the
|
||||||
|
// rows are distinguishable, and a parameter names itself out of the
|
||||||
|
// descriptor rather than out of a table here — so an operation added
|
||||||
|
// as a YAML declaration appears in the history with no code written
|
||||||
|
// for it, exactly as it appears in the panel (FR-DEV-3c).
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
||||||
|
h.record(&g, Edit::for_param(&g, exposure::ID, exposure::EXPOSURE));
|
||||||
|
g.set_param(curve::ID, curve::P1_X, 0.4);
|
||||||
|
h.record(&g, Edit::for_param(&g, curve::ID, curve::P1_X));
|
||||||
|
g.masks_mut().push(layer("l1"));
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.mask_added")));
|
||||||
|
|
||||||
|
let labels: Vec<&str> = h.entries(&g).iter().map(|e| e.label.0).collect();
|
||||||
|
assert_eq!(
|
||||||
|
labels,
|
||||||
|
vec![
|
||||||
|
"history.opened",
|
||||||
|
"param.exposure",
|
||||||
|
// The curve point is one widget over two parameters, so the
|
||||||
|
// step is the operation and it is named as one.
|
||||||
|
"op.tone_curve",
|
||||||
|
"history.mask_added",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_list_marks_where_the_photograph_stands() {
|
||||||
|
// What a panel draws the highlight from. Everything below the mark is
|
||||||
|
// undo and everything above it is redo, so a mark in the wrong place
|
||||||
|
// is a list that lies about which way the photograph will move.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
for v in [0.1, 0.2, 0.3] {
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, v);
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(current(&h, &g), 3);
|
||||||
|
assert!(h.undo(&mut g).moved());
|
||||||
|
assert_eq!(current(&h, &g), 2);
|
||||||
|
assert_eq!(h.cursor(), 2);
|
||||||
|
|
||||||
|
// Exactly one row is ever marked.
|
||||||
|
assert_eq!(h.entries(&g).iter().filter(|e| e.current).count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_row_can_be_jumped_to_from_any_distance() {
|
||||||
|
// The property that makes the list clickable rather than decorative:
|
||||||
|
// a step is a whole state, so arriving from six steps away costs what
|
||||||
|
// arriving from one does and there is nothing to replay in between.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
for v in [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] {
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, v);
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
}
|
||||||
|
assert_eq!(h.depth(), 7, "the floor and one step per value");
|
||||||
|
|
||||||
|
assert!(h.go_to(&mut g, 1).moved());
|
||||||
|
assert_eq!(g.param(exposure::ID, exposure::EXPOSURE), Some(0.1));
|
||||||
|
|
||||||
|
// And forward again, over the same distance.
|
||||||
|
assert!(h.go_to(&mut g, 6).moved());
|
||||||
|
assert_eq!(g.param(exposure::ID, exposure::EXPOSURE), Some(0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn jumping_to_the_row_already_showing_does_nothing() {
|
||||||
|
// A panel highlights the current row, and a row is a thing people
|
||||||
|
// click. Reported as `Nowhere` so the frontend can skip the redraw
|
||||||
|
// rather than re-rendering the frame it is already looking at.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
|
||||||
|
assert!(!h.go_to(&mut g, h.cursor()).moved());
|
||||||
|
assert!(
|
||||||
|
!h.go_to(&mut g, 99).moved(),
|
||||||
|
"a row off the end is not a step"
|
||||||
|
);
|
||||||
|
assert_eq!(h.cursor(), 1, "and neither moved the cursor");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn editing_from_a_row_in_the_middle_drops_the_rows_above_it() {
|
||||||
|
// Jumping back and then working is the branch the user chose. The
|
||||||
|
// steps that were above are gone from the list as well as from redo,
|
||||||
|
// because a row that cannot be reached is a row that must not be
|
||||||
|
// offered.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
for v in [0.1, 0.2, 0.3] {
|
||||||
|
g.set_param(exposure::ID, exposure::EXPOSURE, v);
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(h.go_to(&mut g, 1).moved());
|
||||||
|
g.set_param(saturation::ID, saturation::SATURATION, 20.0);
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.other")));
|
||||||
|
|
||||||
|
let entries = h.entries(&g);
|
||||||
|
assert_eq!(entries.len(), 3, "the abandoned branch is still listed");
|
||||||
|
assert!(entries.last().is_some_and(|e| e.current));
|
||||||
|
assert!(!h.can_redo());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_drag_stays_one_row_named_once() {
|
||||||
|
// Coalescing has to hold for the list as well as for the stepping: a
|
||||||
|
// drag that amended its step must not also rewrite its own label
|
||||||
|
// forty times, and must not appear forty times.
|
||||||
|
let mut g = EditGraph::default_chain();
|
||||||
|
let mut h = History::new(&g);
|
||||||
|
let base = Instant::now();
|
||||||
|
drag(&mut h, &mut g, base, 0.0, 1.5, 40);
|
||||||
|
|
||||||
|
let entries = h.entries(&g);
|
||||||
|
assert_eq!(entries.len(), 2);
|
||||||
|
assert_eq!(entries[1].label.0, "param.exposure");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The index the list says the photograph is standing on.
|
||||||
|
fn current(h: &History, g: &EditGraph) -> usize {
|
||||||
|
h.entries(g)
|
||||||
|
.into_iter()
|
||||||
|
.find(|e| e.current)
|
||||||
|
.expect("some row is always current")
|
||||||
|
.index
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_mask_the_user_just_drew_can_be_taken_back() {
|
fn a_mask_the_user_just_drew_can_be_taken_back() {
|
||||||
// The bug this module was rewritten for. A snapshot used to be a
|
// The bug this module was rewritten for. A snapshot used to be a
|
||||||
@@ -405,7 +849,7 @@ mod tests {
|
|||||||
|
|
||||||
g.masks_mut().push(layer("l1"));
|
g.masks_mut().push(layer("l1"));
|
||||||
assert!(
|
assert!(
|
||||||
h.record(&g, Edit::Discrete),
|
h.record(&g, Edit::Action(LocalizedKey("history.test"))),
|
||||||
"drawing a mask has to open a step"
|
"drawing a mask has to open a step"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -453,7 +897,10 @@ mod tests {
|
|||||||
let mut h = History::new(&g);
|
let mut h = History::new(&g);
|
||||||
|
|
||||||
g.set_film(Some(film("ilford_hp5")));
|
g.set_film(Some(film("ilford_hp5")));
|
||||||
assert!(h.record(&g, Edit::Discrete), "a change of stock is a step");
|
assert!(
|
||||||
|
h.record(&g, Edit::Action(LocalizedKey("history.test"))),
|
||||||
|
"a change of stock is a step"
|
||||||
|
);
|
||||||
|
|
||||||
let step = h.undo(&mut g);
|
let step = h.undo(&mut g);
|
||||||
assert!(step.moved());
|
assert!(step.moved());
|
||||||
@@ -473,7 +920,7 @@ mod tests {
|
|||||||
let mut h = History::new(&g);
|
let mut h = History::new(&g);
|
||||||
|
|
||||||
g.set_film(Some(film("kodak_portra_400")));
|
g.set_film(Some(film("kodak_portra_400")));
|
||||||
assert!(h.record(&g, Edit::Discrete));
|
assert!(h.record(&g, Edit::Action(LocalizedKey("history.test"))));
|
||||||
|
|
||||||
let step = h.undo(&mut g);
|
let step = h.undo(&mut g);
|
||||||
assert!(step.moved());
|
assert!(step.moved());
|
||||||
@@ -649,9 +1096,9 @@ mod tests {
|
|||||||
let base = Instant::now();
|
let base = Instant::now();
|
||||||
|
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
||||||
h.record_at(&g, Edit::Discrete, base);
|
h.record_at(&g, Edit::Action(LocalizedKey("history.test")), base);
|
||||||
g.set_param(saturation::ID, saturation::SATURATION, 20.0);
|
g.set_param(saturation::ID, saturation::SATURATION, 20.0);
|
||||||
h.record_at(&g, Edit::Discrete, at(base, 5));
|
h.record_at(&g, Edit::Action(LocalizedKey("history.test")), at(base, 5));
|
||||||
|
|
||||||
assert_eq!(h.depth(), 3);
|
assert_eq!(h.depth(), 3);
|
||||||
}
|
}
|
||||||
@@ -666,10 +1113,10 @@ mod tests {
|
|||||||
let mut h = History::new(&g);
|
let mut h = History::new(&g);
|
||||||
|
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 99.0);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 99.0);
|
||||||
assert!(h.record(&g, Edit::Discrete));
|
assert!(h.record(&g, Edit::Action(LocalizedKey("history.test"))));
|
||||||
// Clamped at the descriptor's ceiling, so this asks for no movement.
|
// Clamped at the descriptor's ceiling, so this asks for no movement.
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 120.0);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 120.0);
|
||||||
assert!(!h.record(&g, Edit::Discrete));
|
assert!(!h.record(&g, Edit::Action(LocalizedKey("history.test"))));
|
||||||
assert_eq!(h.depth(), 2);
|
assert_eq!(h.depth(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,7 +1132,7 @@ mod tests {
|
|||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 1.25);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.25);
|
||||||
g.set_param(saturation::ID, saturation::SATURATION, -40.0);
|
g.set_param(saturation::ID, saturation::SATURATION, -40.0);
|
||||||
g.set_param(framing::ID, framing::ANGLE, 3.0);
|
g.set_param(framing::ID, framing::ANGLE, 3.0);
|
||||||
h.record(&g, Edit::Discrete);
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
let after = g.state();
|
let after = g.state();
|
||||||
|
|
||||||
assert!(h.undo(&mut g).moved());
|
assert!(h.undo(&mut g).moved());
|
||||||
@@ -704,11 +1151,11 @@ mod tests {
|
|||||||
let mut h = History::new(&g);
|
let mut h = History::new(&g);
|
||||||
|
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
||||||
h.record(&g, Edit::Discrete);
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
assert!(h.undo(&mut g).moved());
|
assert!(h.undo(&mut g).moved());
|
||||||
|
|
||||||
g.set_param(saturation::ID, saturation::SATURATION, 25.0);
|
g.set_param(saturation::ID, saturation::SATURATION, 25.0);
|
||||||
h.record(&g, Edit::Discrete);
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
|
||||||
assert!(!h.can_redo());
|
assert!(!h.can_redo());
|
||||||
assert_eq!(h.depth(), 2);
|
assert_eq!(h.depth(), 2);
|
||||||
@@ -763,7 +1210,11 @@ mod tests {
|
|||||||
|
|
||||||
for i in 1..=(DEPTH as u32 * 2) {
|
for i in 1..=(DEPTH as u32 * 2) {
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, i as f32 * 0.01);
|
g.set_param(exposure::ID, exposure::EXPOSURE, i as f32 * 0.01);
|
||||||
h.record_at(&g, Edit::Discrete, at(base, u64::from(i) * 1000));
|
h.record_at(
|
||||||
|
&g,
|
||||||
|
Edit::Action(LocalizedKey("history.test")),
|
||||||
|
at(base, u64::from(i) * 1000),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(h.depth(), DEPTH);
|
assert_eq!(h.depth(), DEPTH);
|
||||||
@@ -784,7 +1235,7 @@ mod tests {
|
|||||||
let mut g = EditGraph::default_chain();
|
let mut g = EditGraph::default_chain();
|
||||||
let mut h = History::new(&g);
|
let mut h = History::new(&g);
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
||||||
h.record(&g, Edit::Discrete);
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
|
||||||
let mut next = EditGraph::default_chain();
|
let mut next = EditGraph::default_chain();
|
||||||
next.set_param(saturation::ID, saturation::SATURATION, 10.0);
|
next.set_param(saturation::ID, saturation::SATURATION, 10.0);
|
||||||
@@ -803,7 +1254,7 @@ mod tests {
|
|||||||
let mut g = EditGraph::default_chain();
|
let mut g = EditGraph::default_chain();
|
||||||
let mut h = History::new(&g);
|
let mut h = History::new(&g);
|
||||||
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
g.set_param(exposure::ID, exposure::EXPOSURE, 1.0);
|
||||||
h.record(&g, Edit::Discrete);
|
h.record(&g, Edit::Action(LocalizedKey("history.test")));
|
||||||
|
|
||||||
g.framing_mut().set_view(CropRect {
|
g.framing_mut().set_view(CropRect {
|
||||||
x: 0.25,
|
x: 0.25,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub use detail::{
|
|||||||
};
|
};
|
||||||
pub use framing::{CropRect, Framing};
|
pub use framing::{CropRect, Framing};
|
||||||
pub use graph::{EditGraph, OpCapability, ParamCapability};
|
pub use graph::{EditGraph, OpCapability, ParamCapability};
|
||||||
pub use history::{Edit, History, Step};
|
pub use history::{Edit, Entry as HistoryEntry, History, Step};
|
||||||
pub use lens::{compose_warps, ComposedWarp, Warp};
|
pub use lens::{compose_warps, ComposedWarp, Warp};
|
||||||
pub use operation::{
|
pub use operation::{
|
||||||
compose, compose_with_framing, Affects, ComposedShader, Helper, Invalidation, Operation,
|
compose, compose_with_framing, Affects, ComposedShader, Helper, Invalidation, Operation,
|
||||||
|
|||||||
@@ -256,7 +256,10 @@ fn undo_takes_a_repair_back() {
|
|||||||
let mut history = History::new(&graph);
|
let mut history = History::new(&graph);
|
||||||
|
|
||||||
graph.spots_mut().place(spot_at((0.5, 0.5), (0.08, 0.0)));
|
graph.spots_mut().place(spot_at((0.5, 0.5), (0.08, 0.0)));
|
||||||
assert!(history.record(&graph, Edit::Discrete));
|
assert!(history.record(
|
||||||
|
&graph,
|
||||||
|
Edit::Action(dr_pipeline::LocalizedKey("history.spot"))
|
||||||
|
));
|
||||||
assert_eq!(graph.spots().len(), 1);
|
assert_eq!(graph.spots().len(), 1);
|
||||||
|
|
||||||
assert!(history.undo(&mut graph).moved());
|
assert!(history.undo(&mut graph).moved());
|
||||||
@@ -285,7 +288,10 @@ fn undo_steps_back_through_a_moved_source() {
|
|||||||
.get_mut(&id)
|
.get_mut(&id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_offset((0.2, 0.1));
|
.set_offset((0.2, 0.1));
|
||||||
history.record(&graph, Edit::Discrete);
|
history.record(
|
||||||
|
&graph,
|
||||||
|
Edit::Action(dr_pipeline::LocalizedKey("history.spot")),
|
||||||
|
);
|
||||||
|
|
||||||
assert!(history.undo(&mut graph).moved());
|
assert!(history.undo(&mut graph).moved());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
+50
-50
@@ -9,17 +9,17 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
|
|||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Source files scanned | 228 |
|
| Source files scanned | 237 |
|
||||||
| TRACES tags found | 631 |
|
| TRACES tags found | 711 |
|
||||||
| Requirements defined | 177 |
|
| Requirements defined | 177 |
|
||||||
| Requirements covered | 91 |
|
| Requirements covered | 92 |
|
||||||
| **Coverage** | **51.4%** (91/177) |
|
| **Coverage** | **52.0%** (92/177) |
|
||||||
|
|
||||||
### By type
|
### By type
|
||||||
|
|
||||||
| Type | Covered | Defined |
|
| Type | Covered | Defined |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| FR | 72 | 122 |
|
| FR | 73 | 122 |
|
||||||
| NFR | 17 | 49 |
|
| NFR | 17 | 49 |
|
||||||
| R | 2 | 6 |
|
| R | 2 | 6 |
|
||||||
|
|
||||||
@@ -34,79 +34,80 @@ _None._
|
|||||||
| ID | Tagged in |
|
| ID | Tagged in |
|
||||||
|---|---|
|
|---|---|
|
||||||
| FR-CAT-1 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:109`](../core/dr-catalog/src/walk.rs#L109), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`core/dr-types/src/lib.rs:200`](../core/dr-types/src/lib.rs#L200), [`core/dr-types/src/lib.rs:269`](../core/dr-types/src/lib.rs#L269), [`core/dr-types/src/lib.rs:302`](../core/dr-types/src/lib.rs#L302), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479), [`tools/traceability/src/lib.rs:511`](../tools/traceability/src/lib.rs#L511), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1) |
|
| FR-CAT-1 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:109`](../core/dr-catalog/src/walk.rs#L109), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`core/dr-types/src/lib.rs:200`](../core/dr-types/src/lib.rs#L200), [`core/dr-types/src/lib.rs:269`](../core/dr-types/src/lib.rs#L269), [`core/dr-types/src/lib.rs:302`](../core/dr-types/src/lib.rs#L302), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479), [`tools/traceability/src/lib.rs:511`](../tools/traceability/src/lib.rs#L511), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1) |
|
||||||
| FR-CAT-10 | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-types/src/settings.rs:116`](../core/dr-types/src/settings.rs#L116), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:337`](../ui/dr-ui/src/import.rs#L337), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1002`](../ui/dr-ui/src/lib.rs#L1002), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1007`](../ui/dr-ui/ui/library.slint#L1007), [`ui/dr-ui/ui/library.slint:1161`](../ui/dr-ui/ui/library.slint#L1161), [`ui/dr-ui/ui/library.slint:857`](../ui/dr-ui/ui/library.slint#L857) |
|
| FR-CAT-10 | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-types/src/settings.rs:116`](../core/dr-types/src/settings.rs#L116), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:337`](../ui/dr-ui/src/import.rs#L337), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1048`](../ui/dr-ui/src/lib.rs#L1048), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1007`](../ui/dr-ui/ui/library.slint#L1007), [`ui/dr-ui/ui/library.slint:1161`](../ui/dr-ui/ui/library.slint#L1161), [`ui/dr-ui/ui/library.slint:857`](../ui/dr-ui/ui/library.slint#L857) |
|
||||||
| FR-CAT-11 | [`core/dr-catalog/src/dedup.rs:1`](../core/dr-catalog/src/dedup.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:392`](../core/dr-ingest/src/lib.rs#L392), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1002`](../ui/dr-ui/src/lib.rs#L1002), [`ui/dr-ui/src/library.rs:152`](../ui/dr-ui/src/library.rs#L152), [`ui/dr-ui/src/library.rs:2122`](../ui/dr-ui/src/library.rs#L2122), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) |
|
| FR-CAT-11 | [`core/dr-catalog/src/dedup.rs:1`](../core/dr-catalog/src/dedup.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:392`](../core/dr-ingest/src/lib.rs#L392), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1048`](../ui/dr-ui/src/lib.rs#L1048), [`ui/dr-ui/src/library.rs:152`](../ui/dr-ui/src/library.rs#L152), [`ui/dr-ui/src/library.rs:2122`](../ui/dr-ui/src/library.rs#L2122), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) |
|
||||||
| FR-CAT-12 | [`core/dr-pipeline/src/sidecar.rs:117`](../core/dr-pipeline/src/sidecar.rs#L117) |
|
| FR-CAT-12 | [`core/dr-pipeline/src/sidecar.rs:118`](../core/dr-pipeline/src/sidecar.rs#L118) |
|
||||||
| FR-CAT-13 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1) |
|
| FR-CAT-13 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1) |
|
||||||
| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:422`](../core/dr-catalog/src/schema.rs#L422), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:447`](../core/dr-sync-nextcloud/src/lib.rs#L447), [`core/dr-sync/src/lib.rs:124`](../core/dr-sync/src/lib.rs#L124), [`core/dr-sync/src/scan.rs:426`](../core/dr-sync/src/scan.rs#L426), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/collections_ui.rs:1225`](../ui/dr-ui/src/collections_ui.rs#L1225), [`ui/dr-ui/src/collections_ui.rs:1995`](../ui/dr-ui/src/collections_ui.rs#L1995), [`ui/dr-ui/src/library.rs:152`](../ui/dr-ui/src/library.rs#L152), [`ui/dr-ui/src/library.rs:169`](../ui/dr-ui/src/library.rs#L169), [`ui/dr-ui/src/library.rs:198`](../ui/dr-ui/src/library.rs#L198), [`ui/dr-ui/src/library.rs:3007`](../ui/dr-ui/src/library.rs#L3007), [`ui/dr-ui/src/library.rs:3041`](../ui/dr-ui/src/library.rs#L3041), [`ui/dr-ui/src/library_ui.rs:184`](../ui/dr-ui/src/library_ui.rs#L184), [`ui/dr-ui/src/library_ui.rs:757`](../ui/dr-ui/src/library_ui.rs#L757), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:572`](../ui/dr-ui/ui/collections.slint#L572) |
|
| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:422`](../core/dr-catalog/src/schema.rs#L422), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:447`](../core/dr-sync-nextcloud/src/lib.rs#L447), [`core/dr-sync/src/lib.rs:124`](../core/dr-sync/src/lib.rs#L124), [`core/dr-sync/src/scan.rs:426`](../core/dr-sync/src/scan.rs#L426), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/collections_ui.rs:1225`](../ui/dr-ui/src/collections_ui.rs#L1225), [`ui/dr-ui/src/collections_ui.rs:1995`](../ui/dr-ui/src/collections_ui.rs#L1995), [`ui/dr-ui/src/library.rs:152`](../ui/dr-ui/src/library.rs#L152), [`ui/dr-ui/src/library.rs:169`](../ui/dr-ui/src/library.rs#L169), [`ui/dr-ui/src/library.rs:198`](../ui/dr-ui/src/library.rs#L198), [`ui/dr-ui/src/library.rs:3007`](../ui/dr-ui/src/library.rs#L3007), [`ui/dr-ui/src/library.rs:3041`](../ui/dr-ui/src/library.rs#L3041), [`ui/dr-ui/src/library_ui.rs:184`](../ui/dr-ui/src/library_ui.rs#L184), [`ui/dr-ui/src/library_ui.rs:757`](../ui/dr-ui/src/library_ui.rs#L757), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:572`](../ui/dr-ui/ui/collections.slint#L572) |
|
||||||
| FR-CAT-1a | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:53`](../core/dr-types/src/lib.rs#L53) |
|
| FR-CAT-1a | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:53`](../core/dr-types/src/lib.rs#L53) |
|
||||||
| FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479) |
|
| FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479) |
|
||||||
| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:464`](../ui/dr-ui/src/import.rs#L464), [`ui/dr-ui/src/import.rs:489`](../ui/dr-ui/src/import.rs#L489), [`ui/dr-ui/src/library.rs:2598`](../ui/dr-ui/src/library.rs#L2598), [`ui/dr-ui/src/library.rs:2624`](../ui/dr-ui/src/library.rs#L2624), [`ui/dr-ui/src/library_ui.rs:171`](../ui/dr-ui/src/library_ui.rs#L171), [`ui/dr-ui/src/library_ui.rs:3608`](../ui/dr-ui/src/library_ui.rs#L3608), [`ui/dr-ui/src/library_ui.rs:4574`](../ui/dr-ui/src/library_ui.rs#L4574), [`ui/dr-ui/ui/app.slint:513`](../ui/dr-ui/ui/app.slint#L513), [`ui/dr-ui/ui/settings.slint:340`](../ui/dr-ui/ui/settings.slint#L340), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) |
|
| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:464`](../ui/dr-ui/src/import.rs#L464), [`ui/dr-ui/src/import.rs:489`](../ui/dr-ui/src/import.rs#L489), [`ui/dr-ui/src/library.rs:2598`](../ui/dr-ui/src/library.rs#L2598), [`ui/dr-ui/src/library.rs:2624`](../ui/dr-ui/src/library.rs#L2624), [`ui/dr-ui/src/library_ui.rs:171`](../ui/dr-ui/src/library_ui.rs#L171), [`ui/dr-ui/src/library_ui.rs:3608`](../ui/dr-ui/src/library_ui.rs#L3608), [`ui/dr-ui/src/library_ui.rs:4574`](../ui/dr-ui/src/library_ui.rs#L4574), [`ui/dr-ui/ui/app.slint:526`](../ui/dr-ui/ui/app.slint#L526), [`ui/dr-ui/ui/settings.slint:340`](../ui/dr-ui/ui/settings.slint#L340), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) |
|
||||||
| FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:292`](../core/dr-catalog/src/schema.rs#L292), [`ui/dr-ui/src/library.rs:179`](../ui/dr-ui/src/library.rs#L179), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1) |
|
| FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:292`](../core/dr-catalog/src/schema.rs#L292), [`ui/dr-ui/src/library.rs:179`](../ui/dr-ui/src/library.rs#L179), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1) |
|
||||||
| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-catalog/src/schema.rs:840`](../core/dr-catalog/src/schema.rs#L840), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:134`](../core/dr-pipeline/src/sidecar.rs#L134), [`ui/dr-ui/src/collections_ui.rs:1578`](../ui/dr-ui/src/collections_ui.rs#L1578), [`ui/dr-ui/src/collections_ui.rs:1597`](../ui/dr-ui/src/collections_ui.rs#L1597), [`ui/dr-ui/src/collections_ui.rs:243`](../ui/dr-ui/src/collections_ui.rs#L243), [`ui/dr-ui/src/collections_ui.rs:340`](../ui/dr-ui/src/collections_ui.rs#L340), [`ui/dr-ui/src/collections_ui.rs:89`](../ui/dr-ui/src/collections_ui.rs#L89), [`ui/dr-ui/src/library.rs:3055`](../ui/dr-ui/src/library.rs#L3055), [`ui/dr-ui/src/library_ui.rs:6255`](../ui/dr-ui/src/library_ui.rs#L6255), [`ui/dr-ui/src/library_ui.rs:6266`](../ui/dr-ui/src/library_ui.rs#L6266), [`ui/dr-ui/src/library_ui.rs:6279`](../ui/dr-ui/src/library_ui.rs#L6279), [`ui/dr-ui/src/library_ui.rs:628`](../ui/dr-ui/src/library_ui.rs#L628), [`ui/dr-ui/src/library_ui.rs:6294`](../ui/dr-ui/src/library_ui.rs#L6294), [`ui/dr-ui/src/library_ui.rs:6303`](../ui/dr-ui/src/library_ui.rs#L6303), [`ui/dr-ui/ui/app.slint:458`](../ui/dr-ui/ui/app.slint#L458), [`ui/dr-ui/ui/app.slint:587`](../ui/dr-ui/ui/app.slint#L587), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1212`](../ui/dr-ui/ui/library.slint#L1212), [`ui/dr-ui/ui/library.slint:18`](../ui/dr-ui/ui/library.slint#L18), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:901`](../ui/dr-ui/ui/library.slint#L901) |
|
| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-catalog/src/schema.rs:840`](../core/dr-catalog/src/schema.rs#L840), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:135`](../core/dr-pipeline/src/sidecar.rs#L135), [`ui/dr-ui/src/collections_ui.rs:1578`](../ui/dr-ui/src/collections_ui.rs#L1578), [`ui/dr-ui/src/collections_ui.rs:1597`](../ui/dr-ui/src/collections_ui.rs#L1597), [`ui/dr-ui/src/collections_ui.rs:243`](../ui/dr-ui/src/collections_ui.rs#L243), [`ui/dr-ui/src/collections_ui.rs:340`](../ui/dr-ui/src/collections_ui.rs#L340), [`ui/dr-ui/src/collections_ui.rs:89`](../ui/dr-ui/src/collections_ui.rs#L89), [`ui/dr-ui/src/library.rs:3055`](../ui/dr-ui/src/library.rs#L3055), [`ui/dr-ui/src/library_ui.rs:6255`](../ui/dr-ui/src/library_ui.rs#L6255), [`ui/dr-ui/src/library_ui.rs:6266`](../ui/dr-ui/src/library_ui.rs#L6266), [`ui/dr-ui/src/library_ui.rs:6279`](../ui/dr-ui/src/library_ui.rs#L6279), [`ui/dr-ui/src/library_ui.rs:628`](../ui/dr-ui/src/library_ui.rs#L628), [`ui/dr-ui/src/library_ui.rs:6294`](../ui/dr-ui/src/library_ui.rs#L6294), [`ui/dr-ui/src/library_ui.rs:6303`](../ui/dr-ui/src/library_ui.rs#L6303), [`ui/dr-ui/ui/app.slint:471`](../ui/dr-ui/ui/app.slint#L471), [`ui/dr-ui/ui/app.slint:600`](../ui/dr-ui/ui/app.slint#L600), [`ui/dr-ui/ui/library.slint:1209`](../ui/dr-ui/ui/library.slint#L1209), [`ui/dr-ui/ui/library.slint:1212`](../ui/dr-ui/ui/library.slint#L1212), [`ui/dr-ui/ui/library.slint:18`](../ui/dr-ui/ui/library.slint#L18), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:901`](../ui/dr-ui/ui/library.slint#L901) |
|
||||||
| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:63`](../core/dr-types/src/settings.rs#L63), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:203`](../ui/dr-ui/src/library.rs#L203), [`ui/dr-ui/src/library.rs:3301`](../ui/dr-ui/src/library.rs#L3301), [`ui/dr-ui/src/library_ui.rs:315`](../ui/dr-ui/src/library_ui.rs#L315), [`ui/dr-ui/src/library_ui.rs:387`](../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:5124`](../ui/dr-ui/src/library_ui.rs#L5124), [`ui/dr-ui/src/library_ui.rs:5177`](../ui/dr-ui/src/library_ui.rs#L5177), [`ui/dr-ui/src/library_ui.rs:6395`](../ui/dr-ui/src/library_ui.rs#L6395), [`ui/dr-ui/ui/app.slint:461`](../ui/dr-ui/ui/app.slint#L461), [`ui/dr-ui/ui/app.slint:587`](../ui/dr-ui/ui/app.slint#L587), [`ui/dr-ui/ui/app.slint:826`](../ui/dr-ui/ui/app.slint#L826), [`ui/dr-ui/ui/library.slint:109`](../ui/dr-ui/ui/library.slint#L109), [`ui/dr-ui/ui/library.slint:1278`](../ui/dr-ui/ui/library.slint#L1278), [`ui/dr-ui/ui/library.slint:901`](../ui/dr-ui/ui/library.slint#L901), [`ui/dr-ui/ui/settings.slint:115`](../ui/dr-ui/ui/settings.slint#L115) |
|
| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:63`](../core/dr-types/src/settings.rs#L63), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:203`](../ui/dr-ui/src/library.rs#L203), [`ui/dr-ui/src/library.rs:3301`](../ui/dr-ui/src/library.rs#L3301), [`ui/dr-ui/src/library_ui.rs:315`](../ui/dr-ui/src/library_ui.rs#L315), [`ui/dr-ui/src/library_ui.rs:387`](../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:5124`](../ui/dr-ui/src/library_ui.rs#L5124), [`ui/dr-ui/src/library_ui.rs:5177`](../ui/dr-ui/src/library_ui.rs#L5177), [`ui/dr-ui/src/library_ui.rs:6395`](../ui/dr-ui/src/library_ui.rs#L6395), [`ui/dr-ui/ui/app.slint:474`](../ui/dr-ui/ui/app.slint#L474), [`ui/dr-ui/ui/app.slint:600`](../ui/dr-ui/ui/app.slint#L600), [`ui/dr-ui/ui/app.slint:839`](../ui/dr-ui/ui/app.slint#L839), [`ui/dr-ui/ui/library.slint:109`](../ui/dr-ui/ui/library.slint#L109), [`ui/dr-ui/ui/library.slint:1278`](../ui/dr-ui/ui/library.slint#L1278), [`ui/dr-ui/ui/library.slint:901`](../ui/dr-ui/ui/library.slint#L901), [`ui/dr-ui/ui/settings.slint:115`](../ui/dr-ui/ui/settings.slint#L115) |
|
||||||
| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1693`](../ui/dr-ui/src/collections_ui.rs#L1693), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library_ui.rs:3466`](../ui/dr-ui/src/library_ui.rs#L3466), [`ui/dr-ui/src/library_ui.rs:4164`](../ui/dr-ui/src/library_ui.rs#L4164), [`ui/dr-ui/ui/app.slint:582`](../ui/dr-ui/ui/app.slint#L582), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2516`](../ui/dr-ui/ui/library.slint#L2516), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:891`](../ui/dr-ui/ui/library.slint#L891) |
|
| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1693`](../ui/dr-ui/src/collections_ui.rs#L1693), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library_ui.rs:3466`](../ui/dr-ui/src/library_ui.rs#L3466), [`ui/dr-ui/src/library_ui.rs:4164`](../ui/dr-ui/src/library_ui.rs#L4164), [`ui/dr-ui/ui/app.slint:595`](../ui/dr-ui/ui/app.slint#L595), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2516`](../ui/dr-ui/ui/library.slint#L2516), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:891`](../ui/dr-ui/ui/library.slint#L891) |
|
||||||
| FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:324`](../core/dr-pipeline/src/graph.rs#L324), [`core/dr-pipeline/src/graph.rs:361`](../core/dr-pipeline/src/graph.rs#L361), [`core/dr-pipeline/src/ops/curve.rs:136`](../core/dr-pipeline/src/ops/curve.rs#L136), [`core/dr-pipeline/src/ops/curve.rs:652`](../core/dr-pipeline/src/ops/curve.rs#L652), [`core/dr-pipeline/src/sidecar.rs:1431`](../core/dr-pipeline/src/sidecar.rs#L1431), [`core/dr-pipeline/src/sidecar.rs:91`](../core/dr-pipeline/src/sidecar.rs#L91), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:70`](../core/dr-pipeline/src/state.rs#L70), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:2762`](../ui/dr-ui/src/develop.rs#L2762), [`ui/dr-ui/src/develop.rs:2790`](../ui/dr-ui/src/develop.rs#L2790), [`ui/dr-ui/src/export.rs:752`](../ui/dr-ui/src/export.rs#L752), [`ui/dr-ui/src/lib.rs:1239`](../ui/dr-ui/src/lib.rs#L1239), [`ui/dr-ui/src/lib.rs:1579`](../ui/dr-ui/src/lib.rs#L1579), [`ui/dr-ui/src/lib.rs:1684`](../ui/dr-ui/src/lib.rs#L1684), [`ui/dr-ui/src/lib.rs:464`](../ui/dr-ui/src/lib.rs#L464), [`ui/dr-ui/src/lib.rs:849`](../ui/dr-ui/src/lib.rs#L849), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library_ui.rs:4866`](../ui/dr-ui/src/library_ui.rs#L4866), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
| FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:345`](../core/dr-pipeline/src/graph.rs#L345), [`core/dr-pipeline/src/graph.rs:384`](../core/dr-pipeline/src/graph.rs#L384), [`core/dr-pipeline/src/ops/curve.rs:136`](../core/dr-pipeline/src/ops/curve.rs#L136), [`core/dr-pipeline/src/ops/curve.rs:652`](../core/dr-pipeline/src/ops/curve.rs#L652), [`core/dr-pipeline/src/sidecar.rs:1627`](../core/dr-pipeline/src/sidecar.rs#L1627), [`core/dr-pipeline/src/sidecar.rs:92`](../core/dr-pipeline/src/sidecar.rs#L92), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:3154`](../ui/dr-ui/src/develop.rs#L3154), [`ui/dr-ui/src/develop.rs:3183`](../ui/dr-ui/src/develop.rs#L3183), [`ui/dr-ui/src/export.rs:752`](../ui/dr-ui/src/export.rs#L752), [`ui/dr-ui/src/lib.rs:1285`](../ui/dr-ui/src/lib.rs#L1285), [`ui/dr-ui/src/lib.rs:1658`](../ui/dr-ui/src/lib.rs#L1658), [`ui/dr-ui/src/lib.rs:1763`](../ui/dr-ui/src/lib.rs#L1763), [`ui/dr-ui/src/lib.rs:470`](../ui/dr-ui/src/lib.rs#L470), [`ui/dr-ui/src/lib.rs:895`](../ui/dr-ui/src/lib.rs#L895), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library_ui.rs:4866`](../ui/dr-ui/src/library_ui.rs#L4866), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
||||||
| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:704`](../core/dr-catalog/src/walk.rs#L704), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`ui/dr-ui/src/develop.rs:2325`](../ui/dr-ui/src/develop.rs#L2325), [`ui/dr-ui/src/library.rs:138`](../ui/dr-ui/src/library.rs#L138), [`ui/dr-ui/src/library.rs:1505`](../ui/dr-ui/src/library.rs#L1505), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:220`](../ui/dr-ui/src/library.rs#L220), [`ui/dr-ui/src/library.rs:3408`](../ui/dr-ui/src/library.rs#L3408), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:680`](../ui/dr-ui/src/library.rs#L680), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library_ui.rs:1564`](../ui/dr-ui/src/library_ui.rs#L1564), [`ui/dr-ui/src/library_ui.rs:1590`](../ui/dr-ui/src/library_ui.rs#L1590), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:1700`](../ui/dr-ui/src/library_ui.rs#L1700), [`ui/dr-ui/src/library_ui.rs:226`](../ui/dr-ui/src/library_ui.rs#L226), [`ui/dr-ui/src/library_ui.rs:2316`](../ui/dr-ui/src/library_ui.rs#L2316), [`ui/dr-ui/src/library_ui.rs:259`](../ui/dr-ui/src/library_ui.rs#L259), [`ui/dr-ui/src/library_ui.rs:2754`](../ui/dr-ui/src/library_ui.rs#L2754), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:3257`](../ui/dr-ui/src/library_ui.rs#L3257), [`ui/dr-ui/src/library_ui.rs:3329`](../ui/dr-ui/src/library_ui.rs#L3329), [`ui/dr-ui/src/library_ui.rs:3517`](../ui/dr-ui/src/library_ui.rs#L3517), [`ui/dr-ui/src/library_ui.rs:3635`](../ui/dr-ui/src/library_ui.rs#L3635), [`ui/dr-ui/src/library_ui.rs:440`](../ui/dr-ui/src/library_ui.rs#L440), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/library_ui.rs:5222`](../ui/dr-ui/src/library_ui.rs#L5222), [`ui/dr-ui/src/library_ui.rs:5337`](../ui/dr-ui/src/library_ui.rs#L5337), [`ui/dr-ui/src/presets.rs:326`](../ui/dr-ui/src/presets.rs#L326), [`ui/dr-ui/src/presets.rs:338`](../ui/dr-ui/src/presets.rs#L338), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:704`](../core/dr-catalog/src/walk.rs#L704), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`ui/dr-ui/src/develop.rs:2679`](../ui/dr-ui/src/develop.rs#L2679), [`ui/dr-ui/src/library.rs:138`](../ui/dr-ui/src/library.rs#L138), [`ui/dr-ui/src/library.rs:1505`](../ui/dr-ui/src/library.rs#L1505), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:220`](../ui/dr-ui/src/library.rs#L220), [`ui/dr-ui/src/library.rs:3408`](../ui/dr-ui/src/library.rs#L3408), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:680`](../ui/dr-ui/src/library.rs#L680), [`ui/dr-ui/src/library.rs:696`](../ui/dr-ui/src/library.rs#L696), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library_ui.rs:1564`](../ui/dr-ui/src/library_ui.rs#L1564), [`ui/dr-ui/src/library_ui.rs:1590`](../ui/dr-ui/src/library_ui.rs#L1590), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:1700`](../ui/dr-ui/src/library_ui.rs#L1700), [`ui/dr-ui/src/library_ui.rs:226`](../ui/dr-ui/src/library_ui.rs#L226), [`ui/dr-ui/src/library_ui.rs:2316`](../ui/dr-ui/src/library_ui.rs#L2316), [`ui/dr-ui/src/library_ui.rs:259`](../ui/dr-ui/src/library_ui.rs#L259), [`ui/dr-ui/src/library_ui.rs:2754`](../ui/dr-ui/src/library_ui.rs#L2754), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:3257`](../ui/dr-ui/src/library_ui.rs#L3257), [`ui/dr-ui/src/library_ui.rs:3329`](../ui/dr-ui/src/library_ui.rs#L3329), [`ui/dr-ui/src/library_ui.rs:3517`](../ui/dr-ui/src/library_ui.rs#L3517), [`ui/dr-ui/src/library_ui.rs:3635`](../ui/dr-ui/src/library_ui.rs#L3635), [`ui/dr-ui/src/library_ui.rs:440`](../ui/dr-ui/src/library_ui.rs#L440), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/library_ui.rs:5222`](../ui/dr-ui/src/library_ui.rs#L5222), [`ui/dr-ui/src/library_ui.rs:5337`](../ui/dr-ui/src/library_ui.rs#L5337), [`ui/dr-ui/src/presets.rs:326`](../ui/dr-ui/src/presets.rs#L326), [`ui/dr-ui/src/presets.rs:338`](../ui/dr-ui/src/presets.rs#L338), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
||||||
| FR-CULL-1 | [`core/dr-decode/src/preview.rs:134`](../core/dr-decode/src/preview.rs#L134) |
|
| FR-CULL-1 | [`core/dr-decode/src/preview.rs:134`](../core/dr-decode/src/preview.rs#L134) |
|
||||||
| FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:161`](../core/dr-decode/src/preview.rs#L161), [`ui/dr-ui/src/import.rs:464`](../ui/dr-ui/src/import.rs#L464) |
|
| FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:161`](../core/dr-decode/src/preview.rs#L161), [`ui/dr-ui/src/import.rs:464`](../ui/dr-ui/src/import.rs#L464) |
|
||||||
| FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:134`](../core/dr-pipeline/src/sidecar.rs#L134), [`ui/dr-ui/src/library.rs:203`](../ui/dr-ui/src/library.rs#L203), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364) |
|
| FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:135`](../core/dr-pipeline/src/sidecar.rs#L135), [`ui/dr-ui/src/library.rs:203`](../ui/dr-ui/src/library.rs#L203), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364) |
|
||||||
| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:352`](../core/dr-pipeline/src/operation.rs#L352) |
|
| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:352`](../core/dr-pipeline/src/operation.rs#L352) |
|
||||||
| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2167`](../core/dr-gpu/src/adjust.rs#L2167), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:364`](../core/dr-pipeline/src/detail.rs#L364), [`core/dr-pipeline/src/detail.rs:439`](../core/dr-pipeline/src/detail.rs#L439), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/framing.rs:602`](../core/dr-pipeline/src/framing.rs#L602), [`core/dr-pipeline/src/graph.rs:158`](../core/dr-pipeline/src/graph.rs#L158), [`core/dr-pipeline/src/graph.rs:547`](../core/dr-pipeline/src/graph.rs#L547), [`core/dr-pipeline/src/mask.rs:120`](../core/dr-pipeline/src/mask.rs#L120), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:473`](../core/dr-pipeline/src/operation.rs#L473), [`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:207`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L207), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:218`](../core/dr-pipeline/src/ops/curve.rs#L218), [`core/dr-pipeline/src/ops/curve.rs:631`](../core/dr-pipeline/src/ops/curve.rs#L631), [`core/dr-pipeline/src/ops/curve.rs:99`](../core/dr-pipeline/src/ops/curve.rs#L99), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:270`](../core/dr-pipeline/src/ops/noise_reduction.rs#L270), [`core/dr-pipeline/src/sidecar.rs:1431`](../core/dr-pipeline/src/sidecar.rs#L1431), [`core/dr-pipeline/src/sidecar.rs:1491`](../core/dr-pipeline/src/sidecar.rs#L1491), [`core/dr-pipeline/src/sidecar.rs:155`](../core/dr-pipeline/src/sidecar.rs#L155), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:101`](../ui/dr-ui/src/develop.rs#L101), [`ui/dr-ui/src/develop.rs:1111`](../ui/dr-ui/src/develop.rs#L1111), [`ui/dr-ui/src/develop.rs:132`](../ui/dr-ui/src/develop.rs#L132), [`ui/dr-ui/src/develop.rs:1584`](../ui/dr-ui/src/develop.rs#L1584), [`ui/dr-ui/src/develop.rs:1599`](../ui/dr-ui/src/develop.rs#L1599), [`ui/dr-ui/src/develop.rs:1621`](../ui/dr-ui/src/develop.rs#L1621), [`ui/dr-ui/src/develop.rs:1753`](../ui/dr-ui/src/develop.rs#L1753), [`ui/dr-ui/src/develop.rs:1831`](../ui/dr-ui/src/develop.rs#L1831), [`ui/dr-ui/src/develop.rs:238`](../ui/dr-ui/src/develop.rs#L238), [`ui/dr-ui/src/develop.rs:270`](../ui/dr-ui/src/develop.rs#L270), [`ui/dr-ui/src/develop.rs:2762`](../ui/dr-ui/src/develop.rs#L2762), [`ui/dr-ui/src/develop.rs:3237`](../ui/dr-ui/src/develop.rs#L3237), [`ui/dr-ui/src/develop.rs:3291`](../ui/dr-ui/src/develop.rs#L3291), [`ui/dr-ui/src/develop.rs:3335`](../ui/dr-ui/src/develop.rs#L3335), [`ui/dr-ui/src/develop.rs:3385`](../ui/dr-ui/src/develop.rs#L3385), [`ui/dr-ui/src/develop.rs:506`](../ui/dr-ui/src/develop.rs#L506), [`ui/dr-ui/src/develop.rs:544`](../ui/dr-ui/src/develop.rs#L544), [`ui/dr-ui/src/lib.rs:1275`](../ui/dr-ui/src/lib.rs#L1275), [`ui/dr-ui/src/lib.rs:1934`](../ui/dr-ui/src/lib.rs#L1934), [`ui/dr-ui/src/lib.rs:290`](../ui/dr-ui/src/lib.rs#L290), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:218`](../ui/dr-ui/src/segmentation.rs#L218), [`ui/dr-ui/ui/app.slint:1915`](../ui/dr-ui/ui/app.slint#L1915), [`ui/dr-ui/ui/app.slint:913`](../ui/dr-ui/ui/app.slint#L913) |
|
| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/framing.rs:362`](../core/dr-pipeline/src/framing.rs#L362), [`core/dr-pipeline/src/framing.rs:617`](../core/dr-pipeline/src/framing.rs#L617), [`core/dr-pipeline/src/graph.rs:169`](../core/dr-pipeline/src/graph.rs#L169), [`core/dr-pipeline/src/graph.rs:573`](../core/dr-pipeline/src/graph.rs#L573), [`core/dr-pipeline/src/mask.rs:120`](../core/dr-pipeline/src/mask.rs#L120), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:479`](../core/dr-pipeline/src/operation.rs#L479), [`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:207`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L207), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:218`](../core/dr-pipeline/src/ops/curve.rs#L218), [`core/dr-pipeline/src/ops/curve.rs:631`](../core/dr-pipeline/src/ops/curve.rs#L631), [`core/dr-pipeline/src/ops/curve.rs:99`](../core/dr-pipeline/src/ops/curve.rs#L99), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:270`](../core/dr-pipeline/src/ops/noise_reduction.rs#L270), [`core/dr-pipeline/src/sidecar.rs:156`](../core/dr-pipeline/src/sidecar.rs#L156), [`core/dr-pipeline/src/sidecar.rs:1627`](../core/dr-pipeline/src/sidecar.rs#L1627), [`core/dr-pipeline/src/sidecar.rs:1687`](../core/dr-pipeline/src/sidecar.rs#L1687), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:101`](../ui/dr-ui/src/develop.rs#L101), [`ui/dr-ui/src/develop.rs:1196`](../ui/dr-ui/src/develop.rs#L1196), [`ui/dr-ui/src/develop.rs:144`](../ui/dr-ui/src/develop.rs#L144), [`ui/dr-ui/src/develop.rs:1674`](../ui/dr-ui/src/develop.rs#L1674), [`ui/dr-ui/src/develop.rs:1690`](../ui/dr-ui/src/develop.rs#L1690), [`ui/dr-ui/src/develop.rs:1712`](../ui/dr-ui/src/develop.rs#L1712), [`ui/dr-ui/src/develop.rs:1845`](../ui/dr-ui/src/develop.rs#L1845), [`ui/dr-ui/src/develop.rs:1923`](../ui/dr-ui/src/develop.rs#L1923), [`ui/dr-ui/src/develop.rs:258`](../ui/dr-ui/src/develop.rs#L258), [`ui/dr-ui/src/develop.rs:295`](../ui/dr-ui/src/develop.rs#L295), [`ui/dr-ui/src/develop.rs:3154`](../ui/dr-ui/src/develop.rs#L3154), [`ui/dr-ui/src/develop.rs:3718`](../ui/dr-ui/src/develop.rs#L3718), [`ui/dr-ui/src/develop.rs:3772`](../ui/dr-ui/src/develop.rs#L3772), [`ui/dr-ui/src/develop.rs:3816`](../ui/dr-ui/src/develop.rs#L3816), [`ui/dr-ui/src/develop.rs:3866`](../ui/dr-ui/src/develop.rs#L3866), [`ui/dr-ui/src/develop.rs:546`](../ui/dr-ui/src/develop.rs#L546), [`ui/dr-ui/src/develop.rs:593`](../ui/dr-ui/src/develop.rs#L593), [`ui/dr-ui/src/lib.rs:1342`](../ui/dr-ui/src/lib.rs#L1342), [`ui/dr-ui/src/lib.rs:2013`](../ui/dr-ui/src/lib.rs#L2013), [`ui/dr-ui/src/lib.rs:291`](../ui/dr-ui/src/lib.rs#L291), [`ui/dr-ui/src/library.rs:411`](../ui/dr-ui/src/library.rs#L411), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:219`](../ui/dr-ui/src/segmentation.rs#L219), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322), [`ui/dr-ui/src/segmentation.rs:367`](../ui/dr-ui/src/segmentation.rs#L367), [`ui/dr-ui/ui/app.slint:1997`](../ui/dr-ui/ui/app.slint#L1997), [`ui/dr-ui/ui/app.slint:926`](../ui/dr-ui/ui/app.slint#L926) |
|
||||||
| FR-DEV-3a | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:117`](../core/dr-pipeline/src/descriptor.rs#L117), [`core/dr-pipeline/src/descriptor.rs:157`](../core/dr-pipeline/src/descriptor.rs#L157), [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/descriptor.rs:232`](../core/dr-pipeline/src/descriptor.rs#L232), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:229`](../core/dr-pipeline/src/graph.rs#L229), [`core/dr-pipeline/src/graph.rs:22`](../core/dr-pipeline/src/graph.rs#L22), [`core/dr-pipeline/src/graph.rs:44`](../core/dr-pipeline/src/graph.rs#L44), [`core/dr-pipeline/src/graph.rs:57`](../core/dr-pipeline/src/graph.rs#L57), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328), [`core/dr-pipeline/src/ops/curve.rs:318`](../core/dr-pipeline/src/ops/curve.rs#L318), [`ui/dr-ui/src/develop.rs:1008`](../ui/dr-ui/src/develop.rs#L1008), [`ui/dr-ui/src/lib.rs:579`](../ui/dr-ui/src/lib.rs#L579) |
|
| FR-DEV-3a | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:117`](../core/dr-pipeline/src/descriptor.rs#L117), [`core/dr-pipeline/src/descriptor.rs:157`](../core/dr-pipeline/src/descriptor.rs#L157), [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/descriptor.rs:232`](../core/dr-pipeline/src/descriptor.rs#L232), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:23`](../core/dr-pipeline/src/graph.rs#L23), [`core/dr-pipeline/src/graph.rs:250`](../core/dr-pipeline/src/graph.rs#L250), [`core/dr-pipeline/src/graph.rs:45`](../core/dr-pipeline/src/graph.rs#L45), [`core/dr-pipeline/src/graph.rs:58`](../core/dr-pipeline/src/graph.rs#L58), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328), [`core/dr-pipeline/src/ops/curve.rs:318`](../core/dr-pipeline/src/ops/curve.rs#L318), [`ui/dr-ui/src/develop.rs:1093`](../ui/dr-ui/src/develop.rs#L1093), [`ui/dr-ui/src/lib.rs:585`](../ui/dr-ui/src/lib.rs#L585) |
|
||||||
| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:57`](../core/dr-pipeline/src/graph.rs#L57), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328) |
|
| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259), [`core/dr-pipeline/src/graph.rs:58`](../core/dr-pipeline/src/graph.rs#L58), [`core/dr-pipeline/src/operation.rs:328`](../core/dr-pipeline/src/operation.rs#L328) |
|
||||||
| FR-DEV-3c | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:229`](../core/dr-pipeline/src/graph.rs#L229), [`core/dr-pipeline/src/graph.rs:44`](../core/dr-pipeline/src/graph.rs#L44), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`ui/dr-ui/src/develop.rs:3964`](../ui/dr-ui/src/develop.rs#L3964) |
|
| FR-DEV-3c | [`core/dr-pipeline/build.rs:1807`](../core/dr-pipeline/build.rs#L1807), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:250`](../core/dr-pipeline/src/graph.rs#L250), [`core/dr-pipeline/src/graph.rs:45`](../core/dr-pipeline/src/graph.rs#L45), [`core/dr-pipeline/src/mask.rs:954`](../core/dr-pipeline/src/mask.rs#L954), [`ui/dr-ui/src/develop.rs:4445`](../ui/dr-ui/src/develop.rs#L4445) |
|
||||||
| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1041`](../core/dr-gpu/src/adjust.rs#L1041), [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:433`](../core/dr-gpu/tests/capture_sharpen.rs#L433), [`core/dr-gpu/tests/detail_stage.rs:241`](../core/dr-gpu/tests/detail_stage.rs#L241), [`core/dr-gpu/tests/local_contrast.rs:475`](../core/dr-gpu/tests/local_contrast.rs#L475), [`core/dr-gpu/tests/noise_reduction.rs:555`](../core/dr-gpu/tests/noise_reduction.rs#L555), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/graph.rs:576`](../core/dr-pipeline/src/graph.rs#L576), [`core/dr-pipeline/src/operation.rs:31`](../core/dr-pipeline/src/operation.rs#L31), [`core/dr-pipeline/src/operation.rs:352`](../core/dr-pipeline/src/operation.rs#L352), [`core/dr-pipeline/src/operation.rs:52`](../core/dr-pipeline/src/operation.rs#L52), [`core/dr-pipeline/src/operation.rs:70`](../core/dr-pipeline/src/operation.rs#L70) |
|
| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1041`](../core/dr-gpu/src/adjust.rs#L1041), [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`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:476`](../core/dr-gpu/tests/local_contrast.rs#L476), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:188`](../core/dr-pipeline/src/framing.rs#L188), [`core/dr-pipeline/src/graph.rs:612`](../core/dr-pipeline/src/graph.rs#L612), [`core/dr-pipeline/src/operation.rs:31`](../core/dr-pipeline/src/operation.rs#L31), [`core/dr-pipeline/src/operation.rs:352`](../core/dr-pipeline/src/operation.rs#L352), [`core/dr-pipeline/src/operation.rs:52`](../core/dr-pipeline/src/operation.rs#L52), [`core/dr-pipeline/src/operation.rs:70`](../core/dr-pipeline/src/operation.rs#L70) |
|
||||||
| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`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:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1410`](../core/dr-pipeline/src/operation.rs#L1410), [`core/dr-pipeline/src/operation.rs:1491`](../core/dr-pipeline/src/operation.rs#L1491), [`core/dr-pipeline/src/operation.rs:1516`](../core/dr-pipeline/src/operation.rs#L1516), [`core/dr-pipeline/src/operation.rs:1531`](../core/dr-pipeline/src/operation.rs#L1531), [`core/dr-pipeline/src/operation.rs:1555`](../core/dr-pipeline/src/operation.rs#L1555), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:413`](../core/dr-pipeline/src/operation.rs#L413), [`core/dr-pipeline/src/operation.rs:549`](../core/dr-pipeline/src/operation.rs#L549) |
|
| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`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:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1424`](../core/dr-pipeline/src/operation.rs#L1424), [`core/dr-pipeline/src/operation.rs:1505`](../core/dr-pipeline/src/operation.rs#L1505), [`core/dr-pipeline/src/operation.rs:1530`](../core/dr-pipeline/src/operation.rs#L1530), [`core/dr-pipeline/src/operation.rs:1545`](../core/dr-pipeline/src/operation.rs#L1545), [`core/dr-pipeline/src/operation.rs:1569`](../core/dr-pipeline/src/operation.rs#L1569), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:413`](../core/dr-pipeline/src/operation.rs#L413), [`core/dr-pipeline/src/operation.rs:563`](../core/dr-pipeline/src/operation.rs#L563) |
|
||||||
| 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/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:88`](../core/dr-film/src/grain.rs#L88), [`core/dr-film/src/lib.rs:158`](../core/dr-film/src/lib.rs#L158), [`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:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:100`](../core/dr-pipeline/src/graph.rs#L100), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:303`](../core/dr-pipeline/src/graph.rs#L303), [`core/dr-pipeline/src/operation.rs:1014`](../core/dr-pipeline/src/operation.rs#L1014), [`core/dr-pipeline/src/operation.rs:1043`](../core/dr-pipeline/src/operation.rs#L1043), [`core/dr-pipeline/src/operation.rs:1410`](../core/dr-pipeline/src/operation.rs#L1410), [`core/dr-pipeline/src/operation.rs:265`](../core/dr-pipeline/src/operation.rs#L265), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`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:126`](../core/dr-pipeline/src/ops/film_sim.rs#L126), [`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:300`](../core/dr-pipeline/src/ops/film_sim.rs#L300), [`core/dr-pipeline/src/ops/film_sim.rs:68`](../core/dr-pipeline/src/ops/film_sim.rs#L68), [`core/dr-pipeline/src/sidecar.rs:110`](../core/dr-pipeline/src/sidecar.rs#L110), [`core/dr-pipeline/src/sidecar.rs:166`](../core/dr-pipeline/src/sidecar.rs#L166), [`core/dr-pipeline/src/sidecar.rs:1752`](../core/dr-pipeline/src/sidecar.rs#L1752), [`core/dr-pipeline/src/sidecar.rs:1828`](../core/dr-pipeline/src/sidecar.rs#L1828), [`core/dr-pipeline/src/sidecar.rs:434`](../core/dr-pipeline/src/sidecar.rs#L434), [`core/dr-pipeline/src/sidecar.rs:560`](../core/dr-pipeline/src/sidecar.rs#L560), [`core/dr-pipeline/src/sidecar.rs:683`](../core/dr-pipeline/src/sidecar.rs#L683), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:55`](../core/dr-pipeline/src/state.rs#L55), [`core/dr-pipeline/src/state.rs:95`](../core/dr-pipeline/src/state.rs#L95), [`ui/dr-ui/src/develop.rs:2340`](../ui/dr-ui/src/develop.rs#L2340), [`ui/dr-ui/src/develop.rs:2357`](../ui/dr-ui/src/develop.rs#L2357), [`ui/dr-ui/src/develop.rs:2388`](../ui/dr-ui/src/develop.rs#L2388), [`ui/dr-ui/src/develop.rs:2485`](../ui/dr-ui/src/develop.rs#L2485), [`ui/dr-ui/src/develop.rs:2798`](../ui/dr-ui/src/develop.rs#L2798), [`ui/dr-ui/src/develop.rs:2813`](../ui/dr-ui/src/develop.rs#L2813), [`ui/dr-ui/src/lib.rs:1908`](../ui/dr-ui/src/lib.rs#L1908), [`ui/dr-ui/src/lib.rs:512`](../ui/dr-ui/src/lib.rs#L512), [`ui/dr-ui/src/lib.rs:570`](../ui/dr-ui/src/lib.rs#L570), [`ui/dr-ui/src/library.rs:402`](../ui/dr-ui/src/library.rs#L402), [`ui/dr-ui/src/library.rs:651`](../ui/dr-ui/src/library.rs#L651), [`ui/dr-ui/src/presets.rs:275`](../ui/dr-ui/src/presets.rs#L275), [`ui/dr-ui/ui/adjust.slint:879`](../ui/dr-ui/ui/adjust.slint#L879), [`ui/dr-ui/ui/adjust.slint:949`](../ui/dr-ui/ui/adjust.slint#L949), [`ui/dr-ui/ui/app.slint:2370`](../ui/dr-ui/ui/app.slint#L2370), [`ui/dr-ui/ui/app.slint:707`](../ui/dr-ui/ui/app.slint#L707) |
|
| 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:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:101`](../core/dr-pipeline/src/graph.rs#L101), [`core/dr-pipeline/src/graph.rs:124`](../core/dr-pipeline/src/graph.rs#L124), [`core/dr-pipeline/src/graph.rs:324`](../core/dr-pipeline/src/graph.rs#L324), [`core/dr-pipeline/src/operation.rs:1028`](../core/dr-pipeline/src/operation.rs#L1028), [`core/dr-pipeline/src/operation.rs:1057`](../core/dr-pipeline/src/operation.rs#L1057), [`core/dr-pipeline/src/operation.rs:1424`](../core/dr-pipeline/src/operation.rs#L1424), [`core/dr-pipeline/src/operation.rs:265`](../core/dr-pipeline/src/operation.rs#L265), [`core/dr-pipeline/src/operation.rs:279`](../core/dr-pipeline/src/operation.rs#L279), [`core/dr-pipeline/src/ops/film_sim.rs:126`](../core/dr-pipeline/src/ops/film_sim.rs#L126), [`core/dr-pipeline/src/ops/film_sim.rs:150`](../core/dr-pipeline/src/ops/film_sim.rs#L150), [`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:328`](../core/dr-pipeline/src/ops/film_sim.rs#L328), [`core/dr-pipeline/src/ops/film_sim.rs:42`](../core/dr-pipeline/src/ops/film_sim.rs#L42), [`core/dr-pipeline/src/ops/film_sim.rs:85`](../core/dr-pipeline/src/ops/film_sim.rs#L85), [`core/dr-pipeline/src/ops/film_sim.rs:90`](../core/dr-pipeline/src/ops/film_sim.rs#L90), [`core/dr-pipeline/src/sidecar.rs:111`](../core/dr-pipeline/src/sidecar.rs#L111), [`core/dr-pipeline/src/sidecar.rs:167`](../core/dr-pipeline/src/sidecar.rs#L167), [`core/dr-pipeline/src/sidecar.rs:1948`](../core/dr-pipeline/src/sidecar.rs#L1948), [`core/dr-pipeline/src/sidecar.rs:2024`](../core/dr-pipeline/src/sidecar.rs#L2024), [`core/dr-pipeline/src/sidecar.rs:533`](../core/dr-pipeline/src/sidecar.rs#L533), [`core/dr-pipeline/src/sidecar.rs:660`](../core/dr-pipeline/src/sidecar.rs#L660), [`core/dr-pipeline/src/sidecar.rs:792`](../core/dr-pipeline/src/sidecar.rs#L792), [`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.rs:2694`](../ui/dr-ui/src/develop.rs#L2694), [`ui/dr-ui/src/develop.rs:2711`](../ui/dr-ui/src/develop.rs#L2711), [`ui/dr-ui/src/develop.rs:2723`](../ui/dr-ui/src/develop.rs#L2723), [`ui/dr-ui/src/develop.rs:2761`](../ui/dr-ui/src/develop.rs#L2761), [`ui/dr-ui/src/develop.rs:2770`](../ui/dr-ui/src/develop.rs#L2770), [`ui/dr-ui/src/develop.rs:2873`](../ui/dr-ui/src/develop.rs#L2873), [`ui/dr-ui/src/develop.rs:3191`](../ui/dr-ui/src/develop.rs#L3191), [`ui/dr-ui/src/develop.rs:3206`](../ui/dr-ui/src/develop.rs#L3206), [`ui/dr-ui/src/lib.rs:1987`](../ui/dr-ui/src/lib.rs#L1987), [`ui/dr-ui/src/lib.rs:518`](../ui/dr-ui/src/lib.rs#L518), [`ui/dr-ui/src/lib.rs:576`](../ui/dr-ui/src/lib.rs#L576), [`ui/dr-ui/src/library.rs:402`](../ui/dr-ui/src/library.rs#L402), [`ui/dr-ui/src/library.rs:651`](../ui/dr-ui/src/library.rs#L651), [`ui/dr-ui/src/presets.rs:275`](../ui/dr-ui/src/presets.rs#L275), [`ui/dr-ui/ui/adjust.slint:889`](../ui/dr-ui/ui/adjust.slint#L889), [`ui/dr-ui/ui/adjust.slint:959`](../ui/dr-ui/ui/adjust.slint#L959), [`ui/dr-ui/ui/app.slint:2609`](../ui/dr-ui/ui/app.slint#L2609), [`ui/dr-ui/ui/app.slint:720`](../ui/dr-ui/ui/app.slint#L720) |
|
||||||
| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:202`](../core/dr-pipeline/src/framing.rs#L202), [`core/dr-types/src/lib.rs:336`](../core/dr-types/src/lib.rs#L336) |
|
| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:202`](../core/dr-pipeline/src/framing.rs#L202), [`core/dr-pipeline/src/framing.rs:362`](../core/dr-pipeline/src/framing.rs#L362), [`core/dr-types/src/lib.rs:336`](../core/dr-types/src/lib.rs#L336), [`ui/dr-ui/src/develop.rs:119`](../ui/dr-ui/src/develop.rs#L119), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322) |
|
||||||
| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:217`](../core/dr-gpu/src/lib.rs#L217) |
|
| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:217`](../core/dr-gpu/src/lib.rs#L217) |
|
||||||
| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:324`](../core/dr-pipeline/src/graph.rs#L324), [`core/dr-pipeline/src/graph.rs:361`](../core/dr-pipeline/src/graph.rs#L361), [`core/dr-pipeline/src/history.rs:141`](../core/dr-pipeline/src/history.rs#L141), [`core/dr-pipeline/src/history.rs:177`](../core/dr-pipeline/src/history.rs#L177), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:72`](../core/dr-pipeline/src/history.rs#L72), [`core/dr-pipeline/src/history.rs:88`](../core/dr-pipeline/src/history.rs#L88), [`core/dr-pipeline/src/history.rs:96`](../core/dr-pipeline/src/history.rs#L96), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:70`](../core/dr-pipeline/src/state.rs#L70), [`ui/dr-ui/src/develop.rs:2813`](../ui/dr-ui/src/develop.rs#L2813), [`ui/dr-ui/src/develop.rs:2843`](../ui/dr-ui/src/develop.rs#L2843), [`ui/dr-ui/src/develop.rs:2856`](../ui/dr-ui/src/develop.rs#L2856), [`ui/dr-ui/src/develop.rs:2868`](../ui/dr-ui/src/develop.rs#L2868), [`ui/dr-ui/src/develop.rs:488`](../ui/dr-ui/src/develop.rs#L488), [`ui/dr-ui/src/lib.rs:1267`](../ui/dr-ui/src/lib.rs#L1267) |
|
| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:345`](../core/dr-pipeline/src/graph.rs#L345), [`core/dr-pipeline/src/graph.rs:384`](../core/dr-pipeline/src/graph.rs#L384), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:2723`](../ui/dr-ui/src/develop.rs#L2723), [`ui/dr-ui/src/develop.rs:3206`](../ui/dr-ui/src/develop.rs#L3206), [`ui/dr-ui/src/develop.rs:3236`](../ui/dr-ui/src/develop.rs#L3236), [`ui/dr-ui/src/develop.rs:3249`](../ui/dr-ui/src/develop.rs#L3249), [`ui/dr-ui/src/develop.rs:3261`](../ui/dr-ui/src/develop.rs#L3261), [`ui/dr-ui/src/develop.rs:3277`](../ui/dr-ui/src/develop.rs#L3277), [`ui/dr-ui/src/develop.rs:3309`](../ui/dr-ui/src/develop.rs#L3309), [`ui/dr-ui/src/develop.rs:3313`](../ui/dr-ui/src/develop.rs#L3313), [`ui/dr-ui/src/develop.rs:3332`](../ui/dr-ui/src/develop.rs#L3332), [`ui/dr-ui/src/develop.rs:3348`](../ui/dr-ui/src/develop.rs#L3348), [`ui/dr-ui/src/develop.rs:528`](../ui/dr-ui/src/develop.rs#L528), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:215`](../ui/dr-ui/src/labels.rs#L215), [`ui/dr-ui/src/lib.rs:1306`](../ui/dr-ui/src/lib.rs#L1306), [`ui/dr-ui/src/lib.rs:1320`](../ui/dr-ui/src/lib.rs#L1320), [`ui/dr-ui/src/lib.rs:1328`](../ui/dr-ui/src/lib.rs#L1328), [`ui/dr-ui/src/lib.rs:2173`](../ui/dr-ui/src/lib.rs#L2173), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) |
|
||||||
| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-types/src/settings.rs:182`](../core/dr-types/src/settings.rs#L182), [`ui/dr-ui/src/develop.rs:2756`](../ui/dr-ui/src/develop.rs#L2756), [`ui/dr-ui/src/develop.rs:2777`](../ui/dr-ui/src/develop.rs#L2777), [`ui/dr-ui/src/lib.rs:1239`](../ui/dr-ui/src/lib.rs#L1239), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:392`](../ui/dr-ui/src/library.rs#L392), [`ui/dr-ui/src/library_ui.rs:2576`](../ui/dr-ui/src/library_ui.rs#L2576), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:466`](../ui/dr-ui/src/library_ui.rs#L466), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/settings_ui.rs:535`](../ui/dr-ui/src/settings_ui.rs#L535), [`ui/dr-ui/ui/adjust.slint:598`](../ui/dr-ui/ui/adjust.slint#L598), [`ui/dr-ui/ui/library.slint:1305`](../ui/dr-ui/ui/library.slint#L1305), [`ui/dr-ui/ui/library.slint:831`](../ui/dr-ui/ui/library.slint#L831), [`ui/dr-ui/ui/library.slint:912`](../ui/dr-ui/ui/library.slint#L912), [`ui/dr-ui/ui/settings.slint:87`](../ui/dr-ui/ui/settings.slint#L87) |
|
| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-types/src/settings.rs:182`](../core/dr-types/src/settings.rs#L182), [`ui/dr-ui/src/develop.rs:3148`](../ui/dr-ui/src/develop.rs#L3148), [`ui/dr-ui/src/develop.rs:3169`](../ui/dr-ui/src/develop.rs#L3169), [`ui/dr-ui/src/lib.rs:1285`](../ui/dr-ui/src/lib.rs#L1285), [`ui/dr-ui/src/library.rs:1545`](../ui/dr-ui/src/library.rs#L1545), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library.rs:392`](../ui/dr-ui/src/library.rs#L392), [`ui/dr-ui/src/library_ui.rs:2576`](../ui/dr-ui/src/library_ui.rs#L2576), [`ui/dr-ui/src/library_ui.rs:2976`](../ui/dr-ui/src/library_ui.rs#L2976), [`ui/dr-ui/src/library_ui.rs:466`](../ui/dr-ui/src/library_ui.rs#L466), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/settings_ui.rs:535`](../ui/dr-ui/src/settings_ui.rs#L535), [`ui/dr-ui/ui/adjust.slint:598`](../ui/dr-ui/ui/adjust.slint#L598), [`ui/dr-ui/ui/library.slint:1305`](../ui/dr-ui/ui/library.slint#L1305), [`ui/dr-ui/ui/library.slint:831`](../ui/dr-ui/ui/library.slint#L831), [`ui/dr-ui/ui/library.slint:912`](../ui/dr-ui/ui/library.slint#L912), [`ui/dr-ui/ui/settings.slint:87`](../ui/dr-ui/ui/settings.slint#L87) |
|
||||||
| FR-DEV-8 | [`core/dr-pipeline/src/detail.rs:364`](../core/dr-pipeline/src/detail.rs#L364), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299) |
|
| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:3277`](../ui/dr-ui/src/develop.rs#L3277), [`ui/dr-ui/src/develop.rs:3309`](../ui/dr-ui/src/develop.rs#L3309), [`ui/dr-ui/src/lib.rs:1328`](../ui/dr-ui/src/lib.rs#L1328), [`ui/dr-ui/src/lib.rs:2173`](../ui/dr-ui/src/lib.rs#L2173), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) |
|
||||||
| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2090`](../core/dr-gpu/src/adjust.rs#L2090), [`core/dr-gpu/src/adjust.rs:2167`](../core/dr-gpu/src/adjust.rs#L2167), [`core/dr-gpu/src/adjust.rs:2252`](../core/dr-gpu/src/adjust.rs#L2252), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:54`](../core/dr-gpu/src/lib.rs#L54), [`core/dr-gpu/src/lib.rs:94`](../core/dr-gpu/src/lib.rs#L94), [`core/dr-gpu/tests/capture_sharpen.rs:199`](../core/dr-gpu/tests/capture_sharpen.rs#L199), [`core/dr-gpu/tests/detail_stage.rs:327`](../core/dr-gpu/tests/detail_stage.rs#L327), [`core/dr-gpu/tests/local_contrast.rs:262`](../core/dr-gpu/tests/local_contrast.rs#L262), [`core/dr-gpu/tests/noise_reduction.rs:377`](../core/dr-gpu/tests/noise_reduction.rs#L377), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:439`](../core/dr-pipeline/src/detail.rs#L439), [`core/dr-pipeline/src/graph.rs:517`](../core/dr-pipeline/src/graph.rs#L517), [`core/dr-pipeline/src/graph.rs:547`](../core/dr-pipeline/src/graph.rs#L547), [`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:647`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L647), [`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:658`](../core/dr-pipeline/src/ops/local_contrast.rs#L658), [`core/dr-pipeline/src/ops/noise_reduction.rs:691`](../core/dr-pipeline/src/ops/noise_reduction.rs#L691), [`ui/dr-ui/src/develop.rs:2161`](../ui/dr-ui/src/develop.rs#L2161), [`ui/dr-ui/src/develop.rs:3026`](../ui/dr-ui/src/develop.rs#L3026), [`ui/dr-ui/src/develop.rs:3509`](../ui/dr-ui/src/develop.rs#L3509), [`ui/dr-ui/src/develop.rs:3543`](../ui/dr-ui/src/develop.rs#L3543), [`ui/dr-ui/src/lib.rs:59`](../ui/dr-ui/src/lib.rs#L59), [`ui/dr-ui/src/lib.rs:720`](../ui/dr-ui/src/lib.rs#L720) |
|
| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:252`](../core/dr-gpu/src/detail.rs#L252), [`core/dr-gpu/src/detail.rs:434`](../core/dr-gpu/src/detail.rs#L434), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:363`](../core/dr-pipeline/src/detail.rs#L363), [`core/dr-pipeline/src/detail.rs:387`](../core/dr-pipeline/src/detail.rs#L387), [`core/dr-pipeline/src/detail.rs:422`](../core/dr-pipeline/src/detail.rs#L422), [`core/dr-pipeline/src/detail.rs:496`](../core/dr-pipeline/src/detail.rs#L496), [`core/dr-pipeline/src/graph.rs:113`](../core/dr-pipeline/src/graph.rs#L113), [`core/dr-pipeline/src/graph.rs:191`](../core/dr-pipeline/src/graph.rs#L191), [`core/dr-pipeline/src/graph.rs:681`](../core/dr-pipeline/src/graph.rs#L681), [`core/dr-pipeline/src/operation.rs:299`](../core/dr-pipeline/src/operation.rs#L299), [`core/dr-pipeline/src/operation.rs:517`](../core/dr-pipeline/src/operation.rs#L517), [`core/dr-pipeline/src/sidecar.rs:183`](../core/dr-pipeline/src/sidecar.rs#L183), [`core/dr-pipeline/src/sidecar.rs:352`](../core/dr-pipeline/src/sidecar.rs#L352), [`core/dr-pipeline/src/sidecar.rs:672`](../core/dr-pipeline/src/sidecar.rs#L672), [`core/dr-pipeline/src/sidecar.rs:808`](../core/dr-pipeline/src/sidecar.rs#L808), [`core/dr-pipeline/src/sidecar.rs:862`](../core/dr-pipeline/src/sidecar.rs#L862), [`core/dr-pipeline/src/sidecar.rs:892`](../core/dr-pipeline/src/sidecar.rs#L892), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:1994`](../ui/dr-ui/src/develop.rs#L1994), [`ui/dr-ui/src/develop.rs:2032`](../ui/dr-ui/src/develop.rs#L2032), [`ui/dr-ui/src/develop.rs:2097`](../ui/dr-ui/src/develop.rs#L2097), [`ui/dr-ui/src/develop.rs:2180`](../ui/dr-ui/src/develop.rs#L2180), [`ui/dr-ui/src/develop.rs:2194`](../ui/dr-ui/src/develop.rs#L2194), [`ui/dr-ui/src/develop.rs:576`](../ui/dr-ui/src/develop.rs#L576), [`ui/dr-ui/src/labels.rs:52`](../ui/dr-ui/src/labels.rs#L52), [`ui/dr-ui/src/lib.rs:1349`](../ui/dr-ui/src/lib.rs#L1349), [`ui/dr-ui/src/lib.rs:2270`](../ui/dr-ui/src/lib.rs#L2270), [`ui/dr-ui/src/lib.rs:296`](../ui/dr-ui/src/lib.rs#L296), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:676`](../ui/dr-ui/ui/adjust.slint#L676), [`ui/dr-ui/ui/app.slint:1732`](../ui/dr-ui/ui/app.slint#L1732), [`ui/dr-ui/ui/app.slint:2249`](../ui/dr-ui/ui/app.slint#L2249), [`ui/dr-ui/ui/app.slint:2515`](../ui/dr-ui/ui/app.slint#L2515), [`ui/dr-ui/ui/app.slint:318`](../ui/dr-ui/ui/app.slint#L318), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) |
|
||||||
|
| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2088`](../core/dr-gpu/src/adjust.rs#L2088), [`core/dr-gpu/src/adjust.rs:2165`](../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-gpu/src/adjust.rs:2250`](../core/dr-gpu/src/adjust.rs#L2250), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:54`](../core/dr-gpu/src/lib.rs#L54), [`core/dr-gpu/src/lib.rs:94`](../core/dr-gpu/src/lib.rs#L94), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:263`](../core/dr-gpu/tests/local_contrast.rs#L263), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:465`](../core/dr-pipeline/src/detail.rs#L465), [`core/dr-pipeline/src/graph.rs:543`](../core/dr-pipeline/src/graph.rs#L543), [`core/dr-pipeline/src/graph.rs:573`](../core/dr-pipeline/src/graph.rs#L573), [`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:654`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L654), [`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:662`](../core/dr-pipeline/src/ops/local_contrast.rs#L662), [`core/dr-pipeline/src/ops/noise_reduction.rs:695`](../core/dr-pipeline/src/ops/noise_reduction.rs#L695), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:2494`](../ui/dr-ui/src/develop.rs#L2494), [`ui/dr-ui/src/develop.rs:3507`](../ui/dr-ui/src/develop.rs#L3507), [`ui/dr-ui/src/develop.rs:3990`](../ui/dr-ui/src/develop.rs#L3990), [`ui/dr-ui/src/develop.rs:4024`](../ui/dr-ui/src/develop.rs#L4024), [`ui/dr-ui/src/lib.rs:60`](../ui/dr-ui/src/lib.rs#L60), [`ui/dr-ui/src/lib.rs:726`](../ui/dr-ui/src/lib.rs#L726), [`ui/dr-ui/src/lib.rs:785`](../ui/dr-ui/src/lib.rs#L785) |
|
||||||
| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:446`](../core/dr-pipeline/src/operation.rs#L446), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1) |
|
| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:446`](../core/dr-pipeline/src/operation.rs#L446), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1) |
|
||||||
| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:2206`](../ui/dr-ui/src/develop.rs#L2206), [`ui/dr-ui/src/develop.rs:4611`](../ui/dr-ui/src/develop.rs#L4611), [`ui/dr-ui/src/develop.rs:4643`](../ui/dr-ui/src/develop.rs#L4643), [`ui/dr-ui/src/develop.rs:499`](../ui/dr-ui/src/develop.rs#L499), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1326`](../ui/dr-ui/src/lib.rs#L1326), [`ui/dr-ui/src/lib.rs:285`](../ui/dr-ui/src/lib.rs#L285), [`ui/dr-ui/ui/app.slint:276`](../ui/dr-ui/ui/app.slint#L276), [`ui/dr-ui/ui/histogram.slint:122`](../ui/dr-ui/ui/histogram.slint#L122), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) |
|
| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:2560`](../ui/dr-ui/src/develop.rs#L2560), [`ui/dr-ui/src/develop.rs:5092`](../ui/dr-ui/src/develop.rs#L5092), [`ui/dr-ui/src/develop.rs:5124`](../ui/dr-ui/src/develop.rs#L5124), [`ui/dr-ui/src/develop.rs:539`](../ui/dr-ui/src/develop.rs#L539), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1405`](../ui/dr-ui/src/lib.rs#L1405), [`ui/dr-ui/src/lib.rs:286`](../ui/dr-ui/src/lib.rs#L286), [`ui/dr-ui/ui/app.slint:278`](../ui/dr-ui/ui/app.slint#L278), [`ui/dr-ui/ui/histogram.slint:122`](../ui/dr-ui/ui/histogram.slint#L122), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) |
|
||||||
| FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
| FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
||||||
| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2400`](../core/dr-gpu/src/adjust.rs#L2400), [`core/dr-pipeline/src/graph.rs:507`](../core/dr-pipeline/src/graph.rs#L507), [`core/dr-pipeline/src/graph.rs:560`](../core/dr-pipeline/src/graph.rs#L560), [`core/dr-pipeline/src/operation.rs:446`](../core/dr-pipeline/src/operation.rs#L446), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:595`](../core/dr-types/src/settings.rs#L595), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2398`](../core/dr-gpu/src/adjust.rs#L2398), [`core/dr-pipeline/src/graph.rs:533`](../core/dr-pipeline/src/graph.rs#L533), [`core/dr-pipeline/src/graph.rs:587`](../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/operation.rs:446`](../core/dr-pipeline/src/operation.rs#L446), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:595`](../core/dr-types/src/settings.rs#L595), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
||||||
| FR-EXP-3 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
| FR-EXP-3 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
||||||
| FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
| FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
||||||
| FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) |
|
| FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) |
|
||||||
| FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:328`](../ui/dr-ui/src/lib.rs#L328), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:48`](../ui/dr-ui/src/settings_ui.rs#L48), [`ui/dr-ui/src/settings_ui.rs:590`](../ui/dr-ui/src/settings_ui.rs#L590) |
|
| FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:48`](../ui/dr-ui/src/settings_ui.rs#L48), [`ui/dr-ui/src/settings_ui.rs:590`](../ui/dr-ui/src/settings_ui.rs#L590) |
|
||||||
| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:944`](../ui/dr-ui/src/export.rs#L944), [`ui/dr-ui/src/lib.rs:175`](../ui/dr-ui/src/lib.rs#L175), [`ui/dr-ui/src/lib.rs:1850`](../ui/dr-ui/src/lib.rs#L1850), [`ui/dr-ui/src/lib.rs:328`](../ui/dr-ui/src/lib.rs#L328), [`ui/dr-ui/src/lib.rs:363`](../ui/dr-ui/src/lib.rs#L363), [`ui/dr-ui/src/lib.rs:390`](../ui/dr-ui/src/lib.rs#L390), [`ui/dr-ui/src/library_ui.rs:3346`](../ui/dr-ui/src/library_ui.rs#L3346), [`ui/dr-ui/src/library_ui.rs:557`](../ui/dr-ui/src/library_ui.rs#L557), [`ui/dr-ui/src/library_ui.rs:6237`](../ui/dr-ui/src/library_ui.rs#L6237), [`ui/dr-ui/src/library_ui.rs:6314`](../ui/dr-ui/src/library_ui.rs#L6314), [`ui/dr-ui/src/library_ui.rs:6326`](../ui/dr-ui/src/library_ui.rs#L6326), [`ui/dr-ui/src/library_ui.rs:660`](../ui/dr-ui/src/library_ui.rs#L660), [`ui/dr-ui/src/library_ui.rs:717`](../ui/dr-ui/src/library_ui.rs#L717), [`ui/dr-ui/ui/app.slint:1004`](../ui/dr-ui/ui/app.slint#L1004), [`ui/dr-ui/ui/app.slint:1384`](../ui/dr-ui/ui/app.slint#L1384), [`ui/dr-ui/ui/library.slint:1313`](../ui/dr-ui/ui/library.slint#L1313), [`ui/dr-ui/ui/library.slint:835`](../ui/dr-ui/ui/library.slint#L835), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) |
|
| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:944`](../ui/dr-ui/src/export.rs#L944), [`ui/dr-ui/src/lib.rs:176`](../ui/dr-ui/src/lib.rs#L176), [`ui/dr-ui/src/lib.rs:1929`](../ui/dr-ui/src/lib.rs#L1929), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334), [`ui/dr-ui/src/lib.rs:369`](../ui/dr-ui/src/lib.rs#L369), [`ui/dr-ui/src/lib.rs:396`](../ui/dr-ui/src/lib.rs#L396), [`ui/dr-ui/src/library_ui.rs:3346`](../ui/dr-ui/src/library_ui.rs#L3346), [`ui/dr-ui/src/library_ui.rs:557`](../ui/dr-ui/src/library_ui.rs#L557), [`ui/dr-ui/src/library_ui.rs:6237`](../ui/dr-ui/src/library_ui.rs#L6237), [`ui/dr-ui/src/library_ui.rs:6314`](../ui/dr-ui/src/library_ui.rs#L6314), [`ui/dr-ui/src/library_ui.rs:6326`](../ui/dr-ui/src/library_ui.rs#L6326), [`ui/dr-ui/src/library_ui.rs:660`](../ui/dr-ui/src/library_ui.rs#L660), [`ui/dr-ui/src/library_ui.rs:717`](../ui/dr-ui/src/library_ui.rs#L717), [`ui/dr-ui/ui/app.slint:1062`](../ui/dr-ui/ui/app.slint#L1062), [`ui/dr-ui/ui/app.slint:1442`](../ui/dr-ui/ui/app.slint#L1442), [`ui/dr-ui/ui/library.slint:1313`](../ui/dr-ui/ui/library.slint#L1313), [`ui/dr-ui/ui/library.slint:835`](../ui/dr-ui/ui/library.slint#L835), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) |
|
||||||
| FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:136`](../core/dr-export/src/lib.rs#L136), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:444`](../core/dr-types/src/lib.rs#L444), [`core/dr-types/src/settings.rs:313`](../core/dr-types/src/settings.rs#L313), [`ui/dr-ui/src/export.rs:620`](../ui/dr-ui/src/export.rs#L620), [`ui/dr-ui/src/export.rs:648`](../ui/dr-ui/src/export.rs#L648), [`ui/dr-ui/src/export.rs:779`](../ui/dr-ui/src/export.rs#L779), [`ui/dr-ui/src/export.rs:796`](../ui/dr-ui/src/export.rs#L796), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
| FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:136`](../core/dr-export/src/lib.rs#L136), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:444`](../core/dr-types/src/lib.rs#L444), [`core/dr-types/src/settings.rs:313`](../core/dr-types/src/settings.rs#L313), [`ui/dr-ui/src/export.rs:620`](../ui/dr-ui/src/export.rs#L620), [`ui/dr-ui/src/export.rs:648`](../ui/dr-ui/src/export.rs#L648), [`ui/dr-ui/src/export.rs:779`](../ui/dr-ui/src/export.rs#L779), [`ui/dr-ui/src/export.rs:796`](../ui/dr-ui/src/export.rs#L796), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) |
|
||||||
| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1068`](../core/dr-gpu/src/adjust.rs#L1068), [`ui/dr-ui/src/develop.rs:2284`](../ui/dr-ui/src/develop.rs#L2284), [`ui/dr-ui/src/lib.rs:328`](../ui/dr-ui/src/lib.rs#L328) |
|
| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1068`](../core/dr-gpu/src/adjust.rs#L1068), [`ui/dr-ui/src/develop.rs:2638`](../ui/dr-ui/src/develop.rs#L2638), [`ui/dr-ui/src/lib.rs:334`](../ui/dr-ui/src/lib.rs#L334) |
|
||||||
| FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/session.rs:128`](../core/dr-sync-nextcloud/src/session.rs#L128), [`ui/dr-ui/src/launch.rs:256`](../ui/dr-ui/src/launch.rs#L256), [`ui/dr-ui/src/launch.rs:49`](../ui/dr-ui/src/launch.rs#L49), [`ui/dr-ui/src/launch_ui.rs:344`](../ui/dr-ui/src/launch_ui.rs#L344) |
|
| FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/session.rs:128`](../core/dr-sync-nextcloud/src/session.rs#L128), [`ui/dr-ui/src/launch.rs:256`](../ui/dr-ui/src/launch.rs#L256), [`ui/dr-ui/src/launch.rs:49`](../ui/dr-ui/src/launch.rs#L49), [`ui/dr-ui/src/launch_ui.rs:344`](../ui/dr-ui/src/launch_ui.rs#L344) |
|
||||||
| FR-NC-10 | [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:390`](../ui/dr-ui/src/lib.rs#L390), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library.rs:947`](../ui/dr-ui/src/library.rs#L947), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:3346`](../ui/dr-ui/src/library_ui.rs#L3346), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
| FR-NC-10 | [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:396`](../ui/dr-ui/src/lib.rs#L396), [`ui/dr-ui/src/library.rs:1582`](../ui/dr-ui/src/library.rs#L1582), [`ui/dr-ui/src/library.rs:448`](../ui/dr-ui/src/library.rs#L448), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library.rs:947`](../ui/dr-ui/src/library.rs#L947), [`ui/dr-ui/src/library_ui.rs:1606`](../ui/dr-ui/src/library_ui.rs#L1606), [`ui/dr-ui/src/library_ui.rs:3346`](../ui/dr-ui/src/library_ui.rs#L3346), [`ui/dr-ui/src/library_ui.rs:498`](../ui/dr-ui/src/library_ui.rs#L498), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) |
|
||||||
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:34`](../core/dr-sync-nextcloud/src/lib.rs#L34), [`core/dr-sync-nextcloud/src/lib.rs:892`](../core/dr-sync-nextcloud/src/lib.rs#L892), [`core/dr-sync/src/lib.rs:157`](../core/dr-sync/src/lib.rs#L157), [`core/dr-sync/src/lib.rs:40`](../core/dr-sync/src/lib.rs#L40), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1) |
|
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:34`](../core/dr-sync-nextcloud/src/lib.rs#L34), [`core/dr-sync-nextcloud/src/lib.rs:892`](../core/dr-sync-nextcloud/src/lib.rs#L892), [`core/dr-sync/src/lib.rs:157`](../core/dr-sync/src/lib.rs#L157), [`core/dr-sync/src/lib.rs:40`](../core/dr-sync/src/lib.rs#L40), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1) |
|
||||||
| FR-NC-2 | [`core/dr-sync-nextcloud/src/session.rs:128`](../core/dr-sync-nextcloud/src/session.rs#L128), [`core/dr-sync-nextcloud/src/session.rs:34`](../core/dr-sync-nextcloud/src/session.rs#L34) |
|
| FR-NC-2 | [`core/dr-sync-nextcloud/src/session.rs:128`](../core/dr-sync-nextcloud/src/session.rs#L128), [`core/dr-sync-nextcloud/src/session.rs:34`](../core/dr-sync-nextcloud/src/session.rs#L34) |
|
||||||
| FR-NC-3 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:161`](../core/dr-decode/src/preview.rs#L161), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:2598`](../ui/dr-ui/src/library.rs#L2598), [`ui/dr-ui/src/library.rs:2624`](../ui/dr-ui/src/library.rs#L2624), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:3608`](../ui/dr-ui/src/library_ui.rs#L3608), [`ui/dr-ui/src/library_ui.rs:4574`](../ui/dr-ui/src/library_ui.rs#L4574), [`ui/dr-ui/ui/app.slint:513`](../ui/dr-ui/ui/app.slint#L513), [`ui/dr-ui/ui/settings.slint:340`](../ui/dr-ui/ui/settings.slint#L340), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) |
|
| FR-NC-3 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:161`](../core/dr-decode/src/preview.rs#L161), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:2598`](../ui/dr-ui/src/library.rs#L2598), [`ui/dr-ui/src/library.rs:2624`](../ui/dr-ui/src/library.rs#L2624), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:3608`](../ui/dr-ui/src/library_ui.rs#L3608), [`ui/dr-ui/src/library_ui.rs:4574`](../ui/dr-ui/src/library_ui.rs#L4574), [`ui/dr-ui/ui/app.slint:526`](../ui/dr-ui/ui/app.slint#L526), [`ui/dr-ui/ui/settings.slint:340`](../ui/dr-ui/ui/settings.slint#L340), [`ui/dr-ui/ui/settings.slint:72`](../ui/dr-ui/ui/settings.slint#L72) |
|
||||||
| FR-NC-4 | [`core/dr-sync-nextcloud/src/propfind.rs:100`](../core/dr-sync-nextcloud/src/propfind.rs#L100), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:157`](../core/dr-sync/src/lib.rs#L157), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`ui/dr-ui/src/launch.rs:49`](../ui/dr-ui/src/launch.rs#L49) |
|
| FR-NC-4 | [`core/dr-sync-nextcloud/src/propfind.rs:100`](../core/dr-sync-nextcloud/src/propfind.rs#L100), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:157`](../core/dr-sync/src/lib.rs#L157), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`ui/dr-ui/src/launch.rs:49`](../ui/dr-ui/src/launch.rs#L49) |
|
||||||
| FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`ui/dr-ui/src/import.rs:489`](../ui/dr-ui/src/import.rs#L489) |
|
| FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`ui/dr-ui/src/import.rs:489`](../ui/dr-ui/src/import.rs#L489) |
|
||||||
| FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1) |
|
| FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1) |
|
||||||
| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/schema.rs:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-catalog/src/schema.rs:795`](../core/dr-catalog/src/schema.rs#L795), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3195`](../ui/dr-ui/src/collections_ui.rs#L3195), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/lib.rs:1610`](../ui/dr-ui/src/lib.rs#L1610), [`ui/dr-ui/src/lib.rs:2407`](../ui/dr-ui/src/lib.rs#L2407), [`ui/dr-ui/src/library.rs:1324`](../ui/dr-ui/src/library.rs#L1324), [`ui/dr-ui/src/library.rs:1347`](../ui/dr-ui/src/library.rs#L1347), [`ui/dr-ui/src/library.rs:1505`](../ui/dr-ui/src/library.rs#L1505), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/src/library_ui.rs:1239`](../ui/dr-ui/src/library_ui.rs#L1239), [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294), [`ui/dr-ui/src/library_ui.rs:1412`](../ui/dr-ui/src/library_ui.rs#L1412), [`ui/dr-ui/src/library_ui.rs:1531`](../ui/dr-ui/src/library_ui.rs#L1531), [`ui/dr-ui/src/library_ui.rs:2058`](../ui/dr-ui/src/library_ui.rs#L2058), [`ui/dr-ui/src/library_ui.rs:267`](../ui/dr-ui/src/library_ui.rs#L267), [`ui/dr-ui/src/library_ui.rs:278`](../ui/dr-ui/src/library_ui.rs#L278), [`ui/dr-ui/src/library_ui.rs:286`](../ui/dr-ui/src/library_ui.rs#L286), [`ui/dr-ui/src/library_ui.rs:298`](../ui/dr-ui/src/library_ui.rs#L298), [`ui/dr-ui/src/library_ui.rs:307`](../ui/dr-ui/src/library_ui.rs#L307), [`ui/dr-ui/src/library_ui.rs:399`](../ui/dr-ui/src/library_ui.rs#L399), [`ui/dr-ui/src/library_ui.rs:409`](../ui/dr-ui/src/library_ui.rs#L409), [`ui/dr-ui/src/library_ui.rs:451`](../ui/dr-ui/src/library_ui.rs#L451), [`ui/dr-ui/src/library_ui.rs:514`](../ui/dr-ui/src/library_ui.rs#L514), [`ui/dr-ui/src/library_ui.rs:5239`](../ui/dr-ui/src/library_ui.rs#L5239), [`ui/dr-ui/src/library_ui.rs:5257`](../ui/dr-ui/src/library_ui.rs#L5257), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/src/library_ui.rs:545`](../ui/dr-ui/src/library_ui.rs#L545), [`ui/dr-ui/src/library_ui.rs:557`](../ui/dr-ui/src/library_ui.rs#L557), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:2420`](../ui/dr-ui/ui/app.slint#L2420), [`ui/dr-ui/ui/app.slint:576`](../ui/dr-ui/ui/app.slint#L576), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260), [`ui/dr-ui/ui/library.slint:973`](../ui/dr-ui/ui/library.slint#L973) |
|
| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/schema.rs:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-catalog/src/schema.rs:795`](../core/dr-catalog/src/schema.rs#L795), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3195`](../ui/dr-ui/src/collections_ui.rs#L3195), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/lib.rs:1689`](../ui/dr-ui/src/lib.rs#L1689), [`ui/dr-ui/src/lib.rs:2526`](../ui/dr-ui/src/lib.rs#L2526), [`ui/dr-ui/src/library.rs:1324`](../ui/dr-ui/src/library.rs#L1324), [`ui/dr-ui/src/library.rs:1347`](../ui/dr-ui/src/library.rs#L1347), [`ui/dr-ui/src/library.rs:1505`](../ui/dr-ui/src/library.rs#L1505), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/src/library_ui.rs:1239`](../ui/dr-ui/src/library_ui.rs#L1239), [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294), [`ui/dr-ui/src/library_ui.rs:1412`](../ui/dr-ui/src/library_ui.rs#L1412), [`ui/dr-ui/src/library_ui.rs:1531`](../ui/dr-ui/src/library_ui.rs#L1531), [`ui/dr-ui/src/library_ui.rs:2058`](../ui/dr-ui/src/library_ui.rs#L2058), [`ui/dr-ui/src/library_ui.rs:267`](../ui/dr-ui/src/library_ui.rs#L267), [`ui/dr-ui/src/library_ui.rs:278`](../ui/dr-ui/src/library_ui.rs#L278), [`ui/dr-ui/src/library_ui.rs:286`](../ui/dr-ui/src/library_ui.rs#L286), [`ui/dr-ui/src/library_ui.rs:298`](../ui/dr-ui/src/library_ui.rs#L298), [`ui/dr-ui/src/library_ui.rs:307`](../ui/dr-ui/src/library_ui.rs#L307), [`ui/dr-ui/src/library_ui.rs:399`](../ui/dr-ui/src/library_ui.rs#L399), [`ui/dr-ui/src/library_ui.rs:409`](../ui/dr-ui/src/library_ui.rs#L409), [`ui/dr-ui/src/library_ui.rs:451`](../ui/dr-ui/src/library_ui.rs#L451), [`ui/dr-ui/src/library_ui.rs:514`](../ui/dr-ui/src/library_ui.rs#L514), [`ui/dr-ui/src/library_ui.rs:5239`](../ui/dr-ui/src/library_ui.rs#L5239), [`ui/dr-ui/src/library_ui.rs:5257`](../ui/dr-ui/src/library_ui.rs#L5257), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/src/library_ui.rs:545`](../ui/dr-ui/src/library_ui.rs#L545), [`ui/dr-ui/src/library_ui.rs:557`](../ui/dr-ui/src/library_ui.rs#L557), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:2688`](../ui/dr-ui/ui/app.slint#L2688), [`ui/dr-ui/ui/app.slint:589`](../ui/dr-ui/ui/app.slint#L589), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260), [`ui/dr-ui/ui/library.slint:973`](../ui/dr-ui/ui/library.slint#L973) |
|
||||||
| FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294) |
|
| FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1294`](../ui/dr-ui/src/library_ui.rs#L1294) |
|
||||||
| FR-NC-6c | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`core/dr-types/src/lib.rs:201`](../core/dr-types/src/lib.rs#L201), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3195`](../ui/dr-ui/src/collections_ui.rs#L3195), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260) |
|
| FR-NC-6c | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:119`](../core/dr-types/src/lib.rs#L119), [`core/dr-types/src/lib.rs:201`](../core/dr-types/src/lib.rs#L201), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3195`](../ui/dr-ui/src/collections_ui.rs#L3195), [`ui/dr-ui/src/collections_ui.rs:621`](../ui/dr-ui/src/collections_ui.rs#L621), [`ui/dr-ui/src/collections_ui.rs:683`](../ui/dr-ui/src/collections_ui.rs#L683), [`ui/dr-ui/src/library_ui.rs:1065`](../ui/dr-ui/src/library_ui.rs#L1065), [`ui/dr-ui/src/library_ui.rs:1138`](../ui/dr-ui/src/library_ui.rs#L1138), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/icons.slint:260`](../ui/dr-ui/ui/icons.slint#L260) |
|
||||||
| FR-NC-7 | [`core/dr-sync-nextcloud/src/lib.rs:95`](../core/dr-sync-nextcloud/src/lib.rs#L95), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:2624`](../ui/dr-ui/src/library.rs#L2624), [`ui/dr-ui/src/library_ui.rs:3608`](../ui/dr-ui/src/library_ui.rs#L3608), [`ui/dr-ui/ui/settings.slint:340`](../ui/dr-ui/ui/settings.slint#L340) |
|
| FR-NC-7 | [`core/dr-sync-nextcloud/src/lib.rs:95`](../core/dr-sync-nextcloud/src/lib.rs#L95), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:2624`](../ui/dr-ui/src/library.rs#L2624), [`ui/dr-ui/src/library_ui.rs:3608`](../ui/dr-ui/src/library_ui.rs#L3608), [`ui/dr-ui/ui/settings.slint:340`](../ui/dr-ui/ui/settings.slint#L340) |
|
||||||
| FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:116`](../core/dr-types/src/settings.rs#L116), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1002`](../ui/dr-ui/src/lib.rs#L1002), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) |
|
| FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:116`](../core/dr-types/src/settings.rs#L116), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1048`](../ui/dr-ui/src/lib.rs#L1048), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) |
|
||||||
| FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:123`](../ui/dr-ui/src/import.rs#L123), [`ui/dr-ui/src/import.rs:337`](../ui/dr-ui/src/import.rs#L337), [`ui/dr-ui/src/import.rs:585`](../ui/dr-ui/src/import.rs#L585), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1002`](../ui/dr-ui/src/lib.rs#L1002) |
|
| FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:123`](../ui/dr-ui/src/import.rs#L123), [`ui/dr-ui/src/import.rs:337`](../ui/dr-ui/src/import.rs#L337), [`ui/dr-ui/src/import.rs:585`](../ui/dr-ui/src/import.rs#L585), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1048`](../ui/dr-ui/src/lib.rs#L1048) |
|
||||||
| FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:117`](../core/dr-pipeline/src/sidecar.rs#L117), [`core/dr-pipeline/src/sidecar.rs:91`](../core/dr-pipeline/src/sidecar.rs#L91), [`ui/dr-ui/src/lib.rs:1579`](../ui/dr-ui/src/lib.rs#L1579), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library_ui.rs:466`](../ui/dr-ui/src/library_ui.rs#L466) |
|
| FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:118`](../core/dr-pipeline/src/sidecar.rs#L118), [`core/dr-pipeline/src/sidecar.rs:92`](../core/dr-pipeline/src/sidecar.rs#L92), [`ui/dr-ui/src/lib.rs:1658`](../ui/dr-ui/src/lib.rs#L1658), [`ui/dr-ui/src/library.rs:364`](../ui/dr-ui/src/library.rs#L364), [`ui/dr-ui/src/library_ui.rs:466`](../ui/dr-ui/src/library_ui.rs#L466) |
|
||||||
| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:155`](../core/dr-pipeline/src/sidecar.rs#L155), [`core/dr-pipeline/src/sidecar.rs:1828`](../core/dr-pipeline/src/sidecar.rs#L1828), [`core/dr-pipeline/src/sidecar.rs:351`](../core/dr-pipeline/src/sidecar.rs#L351), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library.rs:880`](../ui/dr-ui/src/library.rs#L880) |
|
| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:156`](../core/dr-pipeline/src/sidecar.rs#L156), [`core/dr-pipeline/src/sidecar.rs:183`](../core/dr-pipeline/src/sidecar.rs#L183), [`core/dr-pipeline/src/sidecar.rs:2024`](../core/dr-pipeline/src/sidecar.rs#L2024), [`core/dr-pipeline/src/sidecar.rs:352`](../core/dr-pipeline/src/sidecar.rs#L352), [`core/dr-pipeline/src/sidecar.rs:450`](../core/dr-pipeline/src/sidecar.rs#L450), [`core/dr-pipeline/src/spot.rs:245`](../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`ui/dr-ui/src/library.rs:750`](../ui/dr-ui/src/library.rs#L750), [`ui/dr-ui/src/library.rs:880`](../ui/dr-ui/src/library.rs#L880) |
|
||||||
| FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:53`](../core/dr-types/src/lib.rs#L53) |
|
| FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:53`](../core/dr-types/src/lib.rs#L53) |
|
||||||
| FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
| FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
||||||
| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/lib.rs:789`](../ui/dr-ui/src/lib.rs#L789), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) |
|
| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/lib.rs:835`](../ui/dr-ui/src/lib.rs#L835), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) |
|
||||||
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:243`](../core/dr-decode/src/lib.rs#L243), [`core/dr-types/src/lib.rs:129`](../core/dr-types/src/lib.rs#L129), [`core/dr-types/src/lib.rs:200`](../core/dr-types/src/lib.rs#L200) |
|
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:243`](../core/dr-decode/src/lib.rs#L243), [`core/dr-types/src/lib.rs:129`](../core/dr-types/src/lib.rs#L129), [`core/dr-types/src/lib.rs:200`](../core/dr-types/src/lib.rs#L200) |
|
||||||
| FR-RAW-3 | [`core/dr-decode/src/lib.rs:139`](../core/dr-decode/src/lib.rs#L139), [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-decode/src/locate.rs:1366`](../core/dr-decode/src/locate.rs#L1366) |
|
| FR-RAW-3 | [`core/dr-decode/src/lib.rs:139`](../core/dr-decode/src/lib.rs#L139), [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-decode/src/locate.rs:1366`](../core/dr-decode/src/locate.rs#L1366) |
|
||||||
| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1), [`ui/dr-ui/src/lib.rs:175`](../ui/dr-ui/src/lib.rs#L175) |
|
| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1), [`ui/dr-ui/src/lib.rs:176`](../ui/dr-ui/src/lib.rs#L176) |
|
||||||
| FR-RAW-5 | [`core/dr-decode/src/lib.rs:167`](../core/dr-decode/src/lib.rs#L167), [`core/dr-gpu/src/demosaic.rs:34`](../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:602`](../core/dr-gpu/src/demosaic.rs#L602), [`core/dr-gpu/src/demosaic.rs:681`](../core/dr-gpu/src/demosaic.rs#L681), [`core/dr-gpu/src/demosaic.rs:805`](../core/dr-gpu/src/demosaic.rs#L805) |
|
| FR-RAW-5 | [`core/dr-decode/src/lib.rs:167`](../core/dr-decode/src/lib.rs#L167), [`core/dr-gpu/src/demosaic.rs:34`](../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:602`](../core/dr-gpu/src/demosaic.rs#L602), [`core/dr-gpu/src/demosaic.rs:681`](../core/dr-gpu/src/demosaic.rs#L681), [`core/dr-gpu/src/demosaic.rs:805`](../core/dr-gpu/src/demosaic.rs#L805) |
|
||||||
| FR-UI-1 | [`ui/dr-ui/src/lib.rs:2489`](../ui/dr-ui/src/lib.rs#L2489), [`ui/dr-ui/src/lib.rs:67`](../ui/dr-ui/src/lib.rs#L67), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/library.slint:1031`](../ui/dr-ui/ui/library.slint#L1031) |
|
| FR-UI-1 | [`ui/dr-ui/src/lib.rs:2608`](../ui/dr-ui/src/lib.rs#L2608), [`ui/dr-ui/src/lib.rs:68`](../ui/dr-ui/src/lib.rs#L68), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/library.slint:1031`](../ui/dr-ui/ui/library.slint#L1031) |
|
||||||
| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:1508`](../ui/dr-ui/src/collections_ui.rs#L1508), [`ui/dr-ui/src/collections_ui.rs:1522`](../ui/dr-ui/src/collections_ui.rs#L1522), [`ui/dr-ui/src/collections_ui.rs:1568`](../ui/dr-ui/src/collections_ui.rs#L1568), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1666`](../ui/dr-ui/src/collections_ui.rs#L1666), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/lib.rs:67`](../ui/dr-ui/src/lib.rs#L67), [`ui/dr-ui/src/library_ui.rs:286`](../ui/dr-ui/src/library_ui.rs#L286), [`ui/dr-ui/src/library_ui.rs:5124`](../ui/dr-ui/src/library_ui.rs#L5124), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/src/library_ui.rs:6345`](../ui/dr-ui/src/library_ui.rs#L6345), [`ui/dr-ui/ui/app.slint:2142`](../ui/dr-ui/ui/app.slint#L2142), [`ui/dr-ui/ui/app.slint:262`](../ui/dr-ui/ui/app.slint#L262), [`ui/dr-ui/ui/app.slint:606`](../ui/dr-ui/ui/app.slint#L606), [`ui/dr-ui/ui/app.slint:613`](../ui/dr-ui/ui/app.slint#L613), [`ui/dr-ui/ui/library.slint:1186`](../ui/dr-ui/ui/library.slint#L1186), [`ui/dr-ui/ui/library.slint:1193`](../ui/dr-ui/ui/library.slint#L1193), [`ui/dr-ui/ui/library.slint:1199`](../ui/dr-ui/ui/library.slint#L1199), [`ui/dr-ui/ui/library.slint:2266`](../ui/dr-ui/ui/library.slint#L2266), [`ui/dr-ui/ui/library.slint:823`](../ui/dr-ui/ui/library.slint#L823), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/settings.slint:93`](../ui/dr-ui/ui/settings.slint#L93) |
|
| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:1508`](../ui/dr-ui/src/collections_ui.rs#L1508), [`ui/dr-ui/src/collections_ui.rs:1522`](../ui/dr-ui/src/collections_ui.rs#L1522), [`ui/dr-ui/src/collections_ui.rs:1568`](../ui/dr-ui/src/collections_ui.rs#L1568), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1666`](../ui/dr-ui/src/collections_ui.rs#L1666), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/lib.rs:68`](../ui/dr-ui/src/lib.rs#L68), [`ui/dr-ui/src/library_ui.rs:286`](../ui/dr-ui/src/library_ui.rs#L286), [`ui/dr-ui/src/library_ui.rs:5124`](../ui/dr-ui/src/library_ui.rs#L5124), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/src/library_ui.rs:6345`](../ui/dr-ui/src/library_ui.rs#L6345), [`ui/dr-ui/ui/app.slint:2355`](../ui/dr-ui/ui/app.slint#L2355), [`ui/dr-ui/ui/app.slint:264`](../ui/dr-ui/ui/app.slint#L264), [`ui/dr-ui/ui/app.slint:619`](../ui/dr-ui/ui/app.slint#L619), [`ui/dr-ui/ui/app.slint:626`](../ui/dr-ui/ui/app.slint#L626), [`ui/dr-ui/ui/library.slint:1186`](../ui/dr-ui/ui/library.slint#L1186), [`ui/dr-ui/ui/library.slint:1193`](../ui/dr-ui/ui/library.slint#L1193), [`ui/dr-ui/ui/library.slint:1199`](../ui/dr-ui/ui/library.slint#L1199), [`ui/dr-ui/ui/library.slint:2266`](../ui/dr-ui/ui/library.slint#L2266), [`ui/dr-ui/ui/library.slint:823`](../ui/dr-ui/ui/library.slint#L823), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/settings.slint:93`](../ui/dr-ui/ui/settings.slint#L93) |
|
||||||
| FR-UI-3 | [`ui/dr-ui/src/develop.rs:1831`](../ui/dr-ui/src/develop.rs#L1831), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/ui/app.slint:1915`](../ui/dr-ui/ui/app.slint#L1915), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682) |
|
| FR-UI-3 | [`ui/dr-ui/src/develop.rs:1923`](../ui/dr-ui/src/develop.rs#L1923), [`ui/dr-ui/src/develop.rs:2032`](../ui/dr-ui/src/develop.rs#L2032), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1997`](../ui/dr-ui/ui/app.slint#L1997), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682) |
|
||||||
| FR-UI-4 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:131`](../ui/dr-ui/src/collections_ui.rs#L131), [`ui/dr-ui/src/collections_ui.rs:1508`](../ui/dr-ui/src/collections_ui.rs#L1508), [`ui/dr-ui/src/collections_ui.rs:1522`](../ui/dr-ui/src/collections_ui.rs#L1522), [`ui/dr-ui/src/collections_ui.rs:1568`](../ui/dr-ui/src/collections_ui.rs#L1568), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1666`](../ui/dr-ui/src/collections_ui.rs#L1666), [`ui/dr-ui/src/collections_ui.rs:1693`](../ui/dr-ui/src/collections_ui.rs#L1693), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:582`](../ui/dr-ui/src/collections_ui.rs#L582), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/library_ui.rs:4414`](../ui/dr-ui/src/library_ui.rs#L4414), [`ui/dr-ui/src/library_ui.rs:4517`](../ui/dr-ui/src/library_ui.rs#L4517), [`ui/dr-ui/src/library_ui.rs:4545`](../ui/dr-ui/src/library_ui.rs#L4545), [`ui/dr-ui/src/library_ui.rs:5257`](../ui/dr-ui/src/library_ui.rs#L5257), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/ui/app.slint:1611`](../ui/dr-ui/ui/app.slint#L1611), [`ui/dr-ui/ui/app.slint:582`](../ui/dr-ui/ui/app.slint#L582), [`ui/dr-ui/ui/app.slint:613`](../ui/dr-ui/ui/app.slint#L613), [`ui/dr-ui/ui/library.slint:1186`](../ui/dr-ui/ui/library.slint#L1186), [`ui/dr-ui/ui/library.slint:1193`](../ui/dr-ui/ui/library.slint#L1193), [`ui/dr-ui/ui/library.slint:1199`](../ui/dr-ui/ui/library.slint#L1199), [`ui/dr-ui/ui/library.slint:2266`](../ui/dr-ui/ui/library.slint#L2266), [`ui/dr-ui/ui/library.slint:823`](../ui/dr-ui/ui/library.slint#L823), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:891`](../ui/dr-ui/ui/library.slint#L891) |
|
| FR-UI-4 | [`ui/dr-ui/src/collections_ui.rs:1005`](../ui/dr-ui/src/collections_ui.rs#L1005), [`ui/dr-ui/src/collections_ui.rs:118`](../ui/dr-ui/src/collections_ui.rs#L118), [`ui/dr-ui/src/collections_ui.rs:131`](../ui/dr-ui/src/collections_ui.rs#L131), [`ui/dr-ui/src/collections_ui.rs:1508`](../ui/dr-ui/src/collections_ui.rs#L1508), [`ui/dr-ui/src/collections_ui.rs:1522`](../ui/dr-ui/src/collections_ui.rs#L1522), [`ui/dr-ui/src/collections_ui.rs:1568`](../ui/dr-ui/src/collections_ui.rs#L1568), [`ui/dr-ui/src/collections_ui.rs:159`](../ui/dr-ui/src/collections_ui.rs#L159), [`ui/dr-ui/src/collections_ui.rs:1666`](../ui/dr-ui/src/collections_ui.rs#L1666), [`ui/dr-ui/src/collections_ui.rs:1693`](../ui/dr-ui/src/collections_ui.rs#L1693), [`ui/dr-ui/src/collections_ui.rs:485`](../ui/dr-ui/src/collections_ui.rs#L485), [`ui/dr-ui/src/collections_ui.rs:514`](../ui/dr-ui/src/collections_ui.rs#L514), [`ui/dr-ui/src/collections_ui.rs:582`](../ui/dr-ui/src/collections_ui.rs#L582), [`ui/dr-ui/src/collections_ui.rs:995`](../ui/dr-ui/src/collections_ui.rs#L995), [`ui/dr-ui/src/library_ui.rs:4369`](../ui/dr-ui/src/library_ui.rs#L4369), [`ui/dr-ui/src/library_ui.rs:4414`](../ui/dr-ui/src/library_ui.rs#L4414), [`ui/dr-ui/src/library_ui.rs:4517`](../ui/dr-ui/src/library_ui.rs#L4517), [`ui/dr-ui/src/library_ui.rs:4545`](../ui/dr-ui/src/library_ui.rs#L4545), [`ui/dr-ui/src/library_ui.rs:5257`](../ui/dr-ui/src/library_ui.rs#L5257), [`ui/dr-ui/src/library_ui.rs:5269`](../ui/dr-ui/src/library_ui.rs#L5269), [`ui/dr-ui/ui/app.slint:1669`](../ui/dr-ui/ui/app.slint#L1669), [`ui/dr-ui/ui/app.slint:595`](../ui/dr-ui/ui/app.slint#L595), [`ui/dr-ui/ui/app.slint:626`](../ui/dr-ui/ui/app.slint#L626), [`ui/dr-ui/ui/library.slint:1186`](../ui/dr-ui/ui/library.slint#L1186), [`ui/dr-ui/ui/library.slint:1193`](../ui/dr-ui/ui/library.slint#L1193), [`ui/dr-ui/ui/library.slint:1199`](../ui/dr-ui/ui/library.slint#L1199), [`ui/dr-ui/ui/library.slint:2266`](../ui/dr-ui/ui/library.slint#L2266), [`ui/dr-ui/ui/library.slint:823`](../ui/dr-ui/ui/library.slint#L823), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:891`](../ui/dr-ui/ui/library.slint#L891) |
|
||||||
| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2134`](../ui/dr-ui/src/lib.rs#L2134), [`ui/dr-ui/src/lib.rs:2523`](../ui/dr-ui/src/lib.rs#L2523), [`ui/dr-ui/src/lib.rs:2708`](../ui/dr-ui/src/lib.rs#L2708), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:297`](../ui/dr-ui/ui/app.slint#L297), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) |
|
| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2235`](../ui/dr-ui/src/lib.rs#L2235), [`ui/dr-ui/src/lib.rs:2642`](../ui/dr-ui/src/lib.rs#L2642), [`ui/dr-ui/src/lib.rs:2827`](../ui/dr-ui/src/lib.rs#L2827), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:299`](../ui/dr-ui/ui/app.slint#L299), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) |
|
||||||
| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:100`](../core/dr-pipeline/src/descriptor.rs#L100), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259) |
|
| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:100`](../core/dr-pipeline/src/descriptor.rs#L100), [`core/dr-pipeline/src/framing.rs:259`](../core/dr-pipeline/src/framing.rs#L259) |
|
||||||
| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
||||||
| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1555`](../ui/dr-ui/src/export.rs#L1555), [`ui/dr-ui/src/export.rs:1581`](../ui/dr-ui/src/export.rs#L1581), [`ui/dr-ui/src/export.rs:410`](../ui/dr-ui/src/export.rs#L410), [`ui/dr-ui/src/export.rs:436`](../ui/dr-ui/src/export.rs#L436), [`ui/dr-ui/src/lib.rs:1884`](../ui/dr-ui/src/lib.rs#L1884), [`ui/dr-ui/ui/app.slint:1015`](../ui/dr-ui/ui/app.slint#L1015), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) |
|
| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1555`](../ui/dr-ui/src/export.rs#L1555), [`ui/dr-ui/src/export.rs:1581`](../ui/dr-ui/src/export.rs#L1581), [`ui/dr-ui/src/export.rs:410`](../ui/dr-ui/src/export.rs#L410), [`ui/dr-ui/src/export.rs:436`](../ui/dr-ui/src/export.rs#L436), [`ui/dr-ui/src/lib.rs:1963`](../ui/dr-ui/src/lib.rs#L1963), [`ui/dr-ui/ui/app.slint:1073`](../ui/dr-ui/ui/app.slint#L1073), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) |
|
||||||
| NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-export/src/error.rs:1`](../core/dr-export/src/error.rs#L1), [`core/dr-thumbs/src/error.rs:1`](../core/dr-thumbs/src/error.rs#L1), [`ui/dr-ui/src/export.rs:500`](../ui/dr-ui/src/export.rs#L500) |
|
| NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-export/src/error.rs:1`](../core/dr-export/src/error.rs#L1), [`core/dr-thumbs/src/error.rs:1`](../core/dr-thumbs/src/error.rs#L1), [`ui/dr-ui/src/export.rs:500`](../ui/dr-ui/src/export.rs#L500) |
|
||||||
| NFR-OPS-1 | [`tools/traceability/src/lib.rs:266`](../tools/traceability/src/lib.rs#L266) |
|
| NFR-OPS-1 | [`tools/traceability/src/lib.rs:266`](../tools/traceability/src/lib.rs#L266) |
|
||||||
| NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479) |
|
| NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`tools/traceability/src/lib.rs:479`](../tools/traceability/src/lib.rs#L479) |
|
||||||
@@ -119,7 +120,7 @@ _None._
|
|||||||
| NFR-R5 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) |
|
| NFR-R5 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) |
|
||||||
| NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
|
| NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
|
||||||
| NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
|
| NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
|
||||||
| NFR-RES-1 | [`core/dr-pipeline/src/history.rs:72`](../core/dr-pipeline/src/history.rs#L72), [`ui/dr-ui/src/lib.rs:59`](../ui/dr-ui/src/lib.rs#L59) |
|
| NFR-RES-1 | [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/lib.rs:60`](../ui/dr-ui/src/lib.rs#L60) |
|
||||||
| NFR-RES-4 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/schema.rs:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/library.rs:2598`](../ui/dr-ui/src/library.rs#L2598) |
|
| NFR-RES-4 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/schema.rs:394`](../core/dr-catalog/src/schema.rs#L394), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/library.rs:2598`](../ui/dr-ui/src/library.rs#L2598) |
|
||||||
| NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) |
|
| NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) |
|
||||||
| R1 | [`tools/traceability/src/lib.rs:495`](../tools/traceability/src/lib.rs#L495), [`tools/traceability/src/lib.rs:499`](../tools/traceability/src/lib.rs#L499) |
|
| R1 | [`tools/traceability/src/lib.rs:495`](../tools/traceability/src/lib.rs#L495), [`tools/traceability/src/lib.rs:499`](../tools/traceability/src/lib.rs#L499) |
|
||||||
@@ -127,7 +128,7 @@ _None._
|
|||||||
|
|
||||||
## Not yet tagged
|
## Not yet tagged
|
||||||
|
|
||||||
86 of 177 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
|
85 of 177 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
|
||||||
|
|
||||||
<details><summary>Show untagged requirements</summary>
|
<details><summary>Show untagged requirements</summary>
|
||||||
|
|
||||||
@@ -143,7 +144,6 @@ _None._
|
|||||||
- FR-CULL-9
|
- FR-CULL-9
|
||||||
- FR-DEV-1
|
- FR-DEV-1
|
||||||
- FR-DEV-3g
|
- FR-DEV-3g
|
||||||
- FR-DEV-7
|
|
||||||
- FR-DSP-2
|
- FR-DSP-2
|
||||||
- FR-DSP-3
|
- FR-DSP-3
|
||||||
- FR-DSP-4
|
- FR-DSP-4
|
||||||
|
|||||||
+156
-28
@@ -1336,7 +1336,8 @@ impl DevelopSession {
|
|||||||
layer.set_param(id, param, default);
|
layer.set_param(id, param, default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::RESET_OP));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for p in &cap.params {
|
for p in &cap.params {
|
||||||
@@ -1344,7 +1345,8 @@ impl DevelopSession {
|
|||||||
}
|
}
|
||||||
// One step, though it moved every parameter the operation has: the
|
// One step, though it moved every parameter the operation has: the
|
||||||
// user pressed one button.
|
// user pressed one button.
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::RESET_OP));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset a curve, which is to reset its operation.
|
/// Reset a curve, which is to reset its operation.
|
||||||
@@ -1400,12 +1402,14 @@ impl DevelopSession {
|
|||||||
.map(|p| p.default)
|
.map(|p| p.default)
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
self.graph.set_param(op, param, default);
|
self.graph.set_param(op, param, default);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::RESET_PARAM));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_all(&mut self) {
|
pub fn reset_all(&mut self) {
|
||||||
self.graph.reset();
|
self.graph.reset();
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::RESET_ALL));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rasterise the current mask stack, if there is one.
|
/// Rasterise the current mask stack, if there is one.
|
||||||
@@ -1981,7 +1985,8 @@ impl DevelopSession {
|
|||||||
/// Called on the pointer's release rather than on each move, which is what
|
/// Called on the pointer's release rather than on each move, which is what
|
||||||
/// makes a drag one decision in the undo stack however many frames it took.
|
/// makes a drag one decision in the undo stack however many frames it took.
|
||||||
pub fn commit_gradient_drag(&mut self) {
|
pub fn commit_gradient_drag(&mut self) {
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_MOVED));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- repairs (FR-DEV-8) ------------------------------------------------
|
// --- repairs (FR-DEV-8) ------------------------------------------------
|
||||||
@@ -2019,7 +2024,8 @@ impl DevelopSession {
|
|||||||
.spots_mut()
|
.spots_mut()
|
||||||
.place(dr_pipeline::Spot::new(centre, offset, radius))?;
|
.place(dr_pipeline::Spot::new(centre, offset, radius))?;
|
||||||
self.selected_spot = Some(id.clone());
|
self.selected_spot = Some(id.clone());
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::SPOT_PLACED));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2145,7 +2151,8 @@ impl DevelopSession {
|
|||||||
|
|
||||||
/// A repair's drag finished: one history step for the whole gesture.
|
/// A repair's drag finished: one history step for the whole gesture.
|
||||||
pub fn commit_spot_drag(&mut self) {
|
pub fn commit_spot_drag(&mut self) {
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::SPOT_MOVED));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which repair the column is describing.
|
/// Which repair the column is describing.
|
||||||
@@ -2179,17 +2186,18 @@ impl DevelopSession {
|
|||||||
if self.selected_spot.as_deref() == Some(id) {
|
if self.selected_spot.as_deref() == Some(id) {
|
||||||
self.selected_spot = None;
|
self.selected_spot = None;
|
||||||
}
|
}
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::SPOT_REMOVED));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-DEV-8
|
/// TRACES: FR-DEV-8
|
||||||
/// Change one of the selected repair's settings.
|
/// Change one of the selected repair's settings.
|
||||||
///
|
///
|
||||||
/// Coalesced under [`Edit::Discrete`] like the drags are, rather than under
|
/// Recorded as a named [`Edit::Action`] rather than under a parameter key:
|
||||||
/// a parameter key: a repair is not an operation and has no `OpId` to
|
/// a repair is not an operation and has no `OpId` to name or coalesce by,
|
||||||
/// coalesce by, so a slider drag over it records a step per movement unless
|
/// so a slider drag over it records a step per movement unless the caller
|
||||||
/// the caller debounces. `SliderRow` fires once per completed gesture,
|
/// debounces. `SliderRow` fires once per completed gesture,
|
||||||
/// which is what makes that acceptable here and is why this is the one
|
/// which is what makes that acceptable here and is why this is the one
|
||||||
/// panel in the application built from that row rather than from a live
|
/// panel in the application built from that row rather than from a live
|
||||||
/// track.
|
/// track.
|
||||||
@@ -2204,7 +2212,8 @@ impl DevelopSession {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
change(spot);
|
change(spot);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::SPOT));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2290,7 +2299,8 @@ impl DevelopSession {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.active_masks = vec![id.clone()];
|
self.active_masks = vec![id.clone()];
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_ADDED));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2319,28 +2329,32 @@ impl DevelopSession {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.active_masks = vec![id.clone()];
|
self.active_masks = vec![id.clone()];
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_ADDED));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_mask(&mut self, id: &str) {
|
pub fn remove_mask(&mut self, id: &str) {
|
||||||
if self.graph.masks_mut().remove(id).is_some() {
|
if self.graph.masks_mut().remove(id).is_some() {
|
||||||
self.active_masks.retain(|a| a != id);
|
self.active_masks.retain(|a| a != id);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_REMOVED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mask_enabled(&mut self, id: &str, enabled: bool) {
|
pub fn set_mask_enabled(&mut self, id: &str, enabled: bool) {
|
||||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||||
layer.enabled = enabled;
|
layer.enabled = enabled;
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_TOGGLED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mask_invert(&mut self, id: &str, invert: bool) {
|
pub fn set_mask_invert(&mut self, id: &str, invert: bool) {
|
||||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||||
layer.invert = invert;
|
layer.invert = invert;
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_INVERTED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2349,7 +2363,7 @@ impl DevelopSession {
|
|||||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||||
layer.feather = feather.clamp(0.0, 1.0);
|
layer.feather = feather.clamp(0.0, 1.0);
|
||||||
self.history
|
self.history
|
||||||
.record(&self.graph, Edit::Op(OpId("mask-feather")));
|
.record(&self.graph, Edit::Control(labels::step::MASK_FEATHER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2360,7 +2374,8 @@ impl DevelopSession {
|
|||||||
};
|
};
|
||||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||||
layer.falloff = falloff;
|
layer.falloff = falloff;
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_FALLOFF));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2376,7 +2391,8 @@ impl DevelopSession {
|
|||||||
if morphology != Morphology::None && layer.morph_radius <= 0.0 {
|
if morphology != Morphology::None && layer.morph_radius <= 0.0 {
|
||||||
layer.morph_radius = 0.006;
|
layer.morph_radius = 0.006;
|
||||||
}
|
}
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::MASK_MORPHOLOGY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2384,7 +2400,7 @@ impl DevelopSession {
|
|||||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||||
layer.morph_radius = radius.clamp(0.0, 1.0);
|
layer.morph_radius = radius.clamp(0.0, 1.0);
|
||||||
self.history
|
self.history
|
||||||
.record(&self.graph, Edit::Op(OpId("mask-morph")));
|
.record(&self.graph, Edit::Control(labels::step::MASK_MORPH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2439,7 +2455,7 @@ impl DevelopSession {
|
|||||||
// one decision however many values it passes through. `Discrete`
|
// one decision however many values it passes through. `Discrete`
|
||||||
// would put every intermediate position on the undo stack.
|
// would put every intermediate position on the undo stack.
|
||||||
self.history
|
self.history
|
||||||
.record(&self.graph, Edit::Op(OpId("mask-opacity")));
|
.record(&self.graph, Edit::Control(labels::step::MASK_OPACITY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2704,6 +2720,25 @@ impl DevelopSession {
|
|||||||
/// is the sync case — a sidecar written on a device with a stock this one
|
/// is the sync case — a sidecar written on a device with a stock this one
|
||||||
/// lacks — and rendering it as *some other* film would be worse than
|
/// lacks — and rendering it as *some other* film would be worse than
|
||||||
/// rendering it plainly.
|
/// rendering it plainly.
|
||||||
|
/// TRACES: FR-DEV-3f | FR-DEV-5
|
||||||
|
/// Choose a stock **as the photographer just did**, and record the step.
|
||||||
|
///
|
||||||
|
/// Separate from [`Self::choose_film`] because that call has two very
|
||||||
|
/// different callers. Picking Portra from the list is an edit and belongs
|
||||||
|
/// in the history; the same call made while *restoring* an edit — opening
|
||||||
|
/// a photograph, or stepping to a history row that names a stock — is the
|
||||||
|
/// second half of putting a state back, and recording it would push a step
|
||||||
|
/// for the undo the photographer had just asked for.
|
||||||
|
///
|
||||||
|
/// Choosing a stock was not undoable at all before this existed: the pick
|
||||||
|
/// went straight to `choose_film`, which nothing on the history's path
|
||||||
|
/// ever sees.
|
||||||
|
pub fn pick_film(&mut self, stock: Option<&str>, print: bool) {
|
||||||
|
self.choose_film(stock, print);
|
||||||
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::FILM));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn choose_film(&mut self, stock: Option<&str>, print: bool) {
|
pub fn choose_film(&mut self, stock: Option<&str>, print: bool) {
|
||||||
let Some(stock) = stock else {
|
let Some(stock) = stock else {
|
||||||
self.set_film(None);
|
self.set_film(None);
|
||||||
@@ -2929,7 +2964,8 @@ impl DevelopSession {
|
|||||||
self.graph.set_crop(rotate_crop(crop, turns));
|
self.graph.set_crop(rotate_crop(crop, turns));
|
||||||
}
|
}
|
||||||
self.graph.rotate_quarters(turns);
|
self.graph.rotate_quarters(turns);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::ROTATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Straightening, in degrees. Positive turns the image clockwise.
|
/// Straightening, in degrees. Positive turns the image clockwise.
|
||||||
@@ -2954,7 +2990,8 @@ impl DevelopSession {
|
|||||||
dr_pipeline::framing::FLIP_H,
|
dr_pipeline::framing::FLIP_H,
|
||||||
f32::from(u8::from(!h)),
|
f32::from(u8::from(!h)),
|
||||||
);
|
);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::FLIP_H));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_flip_v(&mut self) {
|
pub fn toggle_flip_v(&mut self) {
|
||||||
@@ -2964,7 +3001,8 @@ impl DevelopSession {
|
|||||||
dr_pipeline::framing::FLIP_V,
|
dr_pipeline::framing::FLIP_V,
|
||||||
f32::from(u8::from(!v)),
|
f32::from(u8::from(!v)),
|
||||||
);
|
);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::FLIP_V));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the straightening angle, in degrees.
|
/// Set the straightening angle, in degrees.
|
||||||
@@ -3000,7 +3038,8 @@ impl DevelopSession {
|
|||||||
let view = self.graph.framing().view();
|
let view = self.graph.framing().view();
|
||||||
self.graph.framing_mut().reset();
|
self.graph.framing_mut().reset();
|
||||||
self.graph.framing_mut().set_view(view);
|
self.graph.framing_mut().set_view(view);
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::RESET_FRAMING));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How far the viewport is zoomed in: 1.0 fits the frame, 4.0 is 4×.
|
/// How far the viewport is zoomed in: 1.0 fits the frame, 4.0 is 4×.
|
||||||
@@ -3137,7 +3176,8 @@ impl DevelopSession {
|
|||||||
// A paste is undoable, and is the action most in need of it: it
|
// A paste is undoable, and is the action most in need of it: it
|
||||||
// replaces everything in scope at once, so getting it wrong costs more
|
// replaces everything in scope at once, so getting it wrong costs more
|
||||||
// than any single control can.
|
// than any single control can.
|
||||||
self.history.record(&self.graph, Edit::Discrete);
|
self.history
|
||||||
|
.record(&self.graph, Edit::Action(labels::step::PASTE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-CAT-8
|
/// TRACES: FR-CAT-8
|
||||||
@@ -3233,6 +3273,94 @@ impl DevelopSession {
|
|||||||
pub fn can_redo(&self) -> bool {
|
pub fn can_redo(&self) -> bool {
|
||||||
self.history.can_redo()
|
self.history.can_redo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
/// Every step this photograph has been through, newest first.
|
||||||
|
///
|
||||||
|
/// Newest first because the list is consulted to take back something just
|
||||||
|
/// done, not browsed chronologically — the order `dr_catalog::trash`
|
||||||
|
/// settled on for the same question. It also keeps the interesting end
|
||||||
|
/// against the heading, so a stack sixty-four deep does not put the step
|
||||||
|
/// the photographer is looking for at the bottom of a long scroll.
|
||||||
|
///
|
||||||
|
/// The reversal happens here rather than in the core, which returns the
|
||||||
|
/// stack in stack order and stamps each row with its own index — so
|
||||||
|
/// nothing on this side does arithmetic to turn a row back into a step.
|
||||||
|
pub fn history_rows(&self) -> Vec<crate::HistoryRow> {
|
||||||
|
let mut rows: Vec<_> = self
|
||||||
|
.history
|
||||||
|
.entries(&self.graph)
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| crate::HistoryRow {
|
||||||
|
index: entry.index as i32,
|
||||||
|
label: labels::resolve(entry.label.0).into(),
|
||||||
|
current: entry.current,
|
||||||
|
// Everything past the mark is a future the photographer
|
||||||
|
// stepped out of. Still listed, because it is still reachable
|
||||||
|
// by redo and hiding it would make redo arrive somewhere the
|
||||||
|
// panel never mentioned — but drawn as the branch it is.
|
||||||
|
undone: entry.index > self.history.cursor(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
rows.reverse();
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
/// Step straight to one row of [`Self::history_rows`].
|
||||||
|
///
|
||||||
|
/// Takes the row's own `index`, not its position in that list.
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// A number that changes exactly when [`Self::history_rows`] would.
|
||||||
|
///
|
||||||
|
/// The panel is rebuilt off this rather than every redraw: a drag ends in
|
||||||
|
/// a redraw per frame and changes no row, and pushing a fresh model makes
|
||||||
|
/// the toolkit tear down and recreate every one of them.
|
||||||
|
pub fn history_revision(&self) -> u64 {
|
||||||
|
self.history.revision()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_history(&mut self, index: i32) -> bool {
|
||||||
|
let Ok(index) = usize::try_from(index) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let step = self.history.go_to(&mut self.graph, index);
|
||||||
|
self.settle(&step);
|
||||||
|
step.moved()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// What undo would take back, and what redo would put back.
|
||||||
|
///
|
||||||
|
/// Named on the buttons rather than left to the bare verb. "Undo" asks the
|
||||||
|
/// photographer to remember what they last did, which after a run of small
|
||||||
|
/// adjustments is exactly what they have stopped tracking — and it is the
|
||||||
|
/// moment they are least willing to press a button and find out.
|
||||||
|
///
|
||||||
|
/// Empty when there is nowhere to go, so the caller falls back to the verb
|
||||||
|
/// alone rather than printing a label for a disabled control.
|
||||||
|
pub fn undo_label(&self) -> String {
|
||||||
|
// Undo takes back the step the graph is *standing on*, so the row to
|
||||||
|
// name is the current one — not the one it will land on.
|
||||||
|
self.step_name(self.history.cursor(), self.history.can_undo())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
pub fn redo_label(&self) -> String {
|
||||||
|
self.step_name(self.history.cursor() + 1, self.history.can_redo())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_name(&self, index: usize, offered: bool) -> String {
|
||||||
|
if !offered {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
self.history
|
||||||
|
.entries(&self.graph)
|
||||||
|
.into_iter()
|
||||||
|
.find(|e| e.index == index)
|
||||||
|
.map(|e| labels::resolve(e.label.0))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-express a crop rect after the frame it is measured against turns.
|
/// Re-express a crop rect after the frame it is measured against turns.
|
||||||
|
|||||||
+243
-56
@@ -9,47 +9,153 @@
|
|||||||
//! a derived label, before anyone writes a translation for it. That is the
|
//! a derived label, before anyone writes a translation for it. That is the
|
||||||
//! behaviour FR-DEV-3c promises: adding an operation needs no UI change.
|
//! behaviour FR-DEV-3c promises: adding an operation needs no UI change.
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-5
|
||||||
|
/// The history steps this interface records that no descriptor can name.
|
||||||
|
///
|
||||||
|
/// Constants rather than string literals at the call sites, and [`ALL`] rather
|
||||||
|
/// than a list written out again in a test. A key spelled one way where the
|
||||||
|
/// step is recorded and another way where it is catalogued resolves through
|
||||||
|
/// `derive` to something *plausible* — "Mask Toggled" — so the mistake does
|
||||||
|
/// not look like one. Naming each key once removes the opportunity.
|
||||||
|
///
|
||||||
|
/// A step that moved a parameter or an operation is deliberately not here: it
|
||||||
|
/// is named out of the descriptor, through the same `op.` and `param.` entries
|
||||||
|
/// the develop panel resolves. That is what lets an operation added as a YAML
|
||||||
|
/// declaration appear in the history correctly named with nothing written for
|
||||||
|
/// it here (FR-DEV-3c).
|
||||||
|
pub mod step {
|
||||||
|
use dr_pipeline::LocalizedKey;
|
||||||
|
|
||||||
|
pub const PASTE: LocalizedKey = LocalizedKey("history.paste");
|
||||||
|
pub const FILM: LocalizedKey = LocalizedKey("history.film");
|
||||||
|
|
||||||
|
pub const RESET_ALL: LocalizedKey = LocalizedKey("history.reset_all");
|
||||||
|
pub const RESET_OP: LocalizedKey = LocalizedKey("history.reset_op");
|
||||||
|
pub const RESET_PARAM: LocalizedKey = LocalizedKey("history.reset_param");
|
||||||
|
pub const RESET_FRAMING: LocalizedKey = LocalizedKey("history.reset_framing");
|
||||||
|
|
||||||
|
pub const ROTATE: LocalizedKey = LocalizedKey("history.rotate");
|
||||||
|
pub const FLIP_H: LocalizedKey = LocalizedKey("history.flip_h");
|
||||||
|
pub const FLIP_V: LocalizedKey = LocalizedKey("history.flip_v");
|
||||||
|
|
||||||
|
pub const MASK_ADDED: LocalizedKey = LocalizedKey("history.mask_added");
|
||||||
|
pub const MASK_REMOVED: LocalizedKey = LocalizedKey("history.mask_removed");
|
||||||
|
pub const MASK_TOGGLED: LocalizedKey = LocalizedKey("history.mask_toggled");
|
||||||
|
pub const MASK_INVERTED: LocalizedKey = LocalizedKey("history.mask_inverted");
|
||||||
|
pub const MASK_MOVED: LocalizedKey = LocalizedKey("history.mask_moved");
|
||||||
|
pub const MASK_FEATHER: LocalizedKey = LocalizedKey("history.mask_feather");
|
||||||
|
pub const MASK_MORPH: LocalizedKey = LocalizedKey("history.mask_morph");
|
||||||
|
pub const MASK_OPACITY: LocalizedKey = LocalizedKey("history.mask_opacity");
|
||||||
|
pub const MASK_FALLOFF: LocalizedKey = LocalizedKey("history.mask_falloff");
|
||||||
|
pub const MASK_MORPHOLOGY: LocalizedKey = LocalizedKey("history.mask_morphology");
|
||||||
|
|
||||||
|
/// TRACES: FR-DEV-8
|
||||||
|
/// A repair's own settings changed — its radius, its source, its opacity.
|
||||||
|
pub const SPOT: LocalizedKey = LocalizedKey("history.spot");
|
||||||
|
pub const SPOT_PLACED: LocalizedKey = LocalizedKey("history.spot_placed");
|
||||||
|
pub const SPOT_MOVED: LocalizedKey = LocalizedKey("history.spot_moved");
|
||||||
|
pub const SPOT_REMOVED: LocalizedKey = LocalizedKey("history.spot_removed");
|
||||||
|
|
||||||
|
/// Every step above, plus the two the core names for itself.
|
||||||
|
///
|
||||||
|
/// Exists so the catalogue can be checked against the keys that are
|
||||||
|
/// actually recorded rather than against a second copy of them. A new step
|
||||||
|
/// left out of this list is a step with no test — which is the failure
|
||||||
|
/// this whole arrangement is guarding, so it is worth saying plainly:
|
||||||
|
/// **add the constant to this slice.**
|
||||||
|
///
|
||||||
|
/// Test-only, because checking is the whole of what it is for. Resolving a
|
||||||
|
/// key at runtime goes through [`super::resolve`] one key at a time and
|
||||||
|
/// never needs the roll.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub const ALL: &[LocalizedKey] = &[
|
||||||
|
dr_pipeline::history::OPENED,
|
||||||
|
dr_pipeline::history::UNNAMED,
|
||||||
|
PASTE,
|
||||||
|
FILM,
|
||||||
|
RESET_ALL,
|
||||||
|
RESET_OP,
|
||||||
|
RESET_PARAM,
|
||||||
|
RESET_FRAMING,
|
||||||
|
ROTATE,
|
||||||
|
FLIP_H,
|
||||||
|
FLIP_V,
|
||||||
|
MASK_ADDED,
|
||||||
|
MASK_REMOVED,
|
||||||
|
MASK_TOGGLED,
|
||||||
|
MASK_INVERTED,
|
||||||
|
MASK_MOVED,
|
||||||
|
MASK_FEATHER,
|
||||||
|
MASK_MORPH,
|
||||||
|
MASK_OPACITY,
|
||||||
|
MASK_FALLOFF,
|
||||||
|
MASK_MORPHOLOGY,
|
||||||
|
SPOT,
|
||||||
|
SPOT_PLACED,
|
||||||
|
SPOT_MOVED,
|
||||||
|
SPOT_REMOVED,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve a key, deriving a fallback where none is catalogued.
|
/// Resolve a key, deriving a fallback where none is catalogued.
|
||||||
pub fn resolve(key: &str) -> String {
|
pub fn resolve(key: &str) -> String {
|
||||||
match key {
|
catalogued(key).map_or_else(|| derive(key), str::to_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The label this build has actually chosen for `key`, if it has chosen one.
|
||||||
|
///
|
||||||
|
/// Split out from [`resolve`] so that "is this catalogued?" can be asked, and
|
||||||
|
/// it is asked by the test that guards the history rows. Those are read as a
|
||||||
|
/// column of short phrases naming decisions, and `derive` is not up to that
|
||||||
|
/// job even when it produces real words: it turns `history.mask_toggled` into
|
||||||
|
/// "Mask Toggled", which is a description of a field rather than of something
|
||||||
|
/// the photographer did — and, being perfectly readable, is a mistake nobody
|
||||||
|
/// would look at twice.
|
||||||
|
///
|
||||||
|
/// Deriving stays right for `op.` and `param.`, where an operation added as a
|
||||||
|
/// YAML declaration must show up usable before anyone writes its translation
|
||||||
|
/// (FR-DEV-3c). The difference is that those keys name a thing and these name
|
||||||
|
/// an act.
|
||||||
|
fn catalogued(key: &str) -> Option<&'static str> {
|
||||||
|
Some(match key {
|
||||||
// Operations
|
// Operations
|
||||||
// The attribute names. Short on purpose: these are read as a strip
|
// The attribute names. Short on purpose: these are read as a strip
|
||||||
// of tabs, where a long word crowds out the next one.
|
// of tabs, where a long word crowds out the next one.
|
||||||
"attr.tone" => "Light".into(),
|
"attr.tone" => "Light",
|
||||||
"attr.colour" => "Colour".into(),
|
"attr.colour" => "Colour",
|
||||||
"attr.detail" => "Detail".into(),
|
"attr.detail" => "Detail",
|
||||||
"attr.optics" => "Optics".into(),
|
"attr.optics" => "Optics",
|
||||||
"attr.geometry" => "Geometry".into(),
|
"attr.geometry" => "Geometry",
|
||||||
"attr.effect" => "Effects".into(),
|
"attr.effect" => "Effects",
|
||||||
|
|
||||||
"op.white_balance" => "White Balance".into(),
|
"op.white_balance" => "White Balance",
|
||||||
"op.exposure" => "Exposure".into(),
|
"op.exposure" => "Exposure",
|
||||||
"op.highlights_shadows" => "Highlights & Shadows".into(),
|
"op.highlights_shadows" => "Highlights & Shadows",
|
||||||
"op.blacks_whites" => "Blacks & Whites".into(),
|
"op.blacks_whites" => "Blacks & Whites",
|
||||||
"op.brilliance" => "Brilliance".into(),
|
"op.brilliance" => "Brilliance",
|
||||||
"op.vibrance" => "Vibrance".into(),
|
"op.vibrance" => "Vibrance",
|
||||||
"op.saturation" => "Saturation".into(),
|
"op.saturation" => "Saturation",
|
||||||
"op.colour_mixer" => "Colour Mixer".into(),
|
"op.colour_mixer" => "Colour Mixer",
|
||||||
// "Sharpening" rather than what `derive` would make of the id. The id
|
// "Sharpening" rather than what `derive` would make of the id. The id
|
||||||
// says *capture* sharpening to separate it from the output sharpening
|
// says *capture* sharpening to separate it from the output sharpening
|
||||||
// an export applies (FR-EXP-4), which is a distinction about where in
|
// an export applies (FR-EXP-4), which is a distinction about where in
|
||||||
// the pipeline it sits; in the develop panel there is only one, and
|
// the pipeline it sits; in the develop panel there is only one, and
|
||||||
// "Capture Sharpen" would name a distinction the photographer cannot
|
// "Capture Sharpen" would name a distinction the photographer cannot
|
||||||
// see from there.
|
// see from there.
|
||||||
"op.capture_sharpen" => "Sharpening".into(),
|
"op.capture_sharpen" => "Sharpening",
|
||||||
"op.framing" => "Crop & Rotate".into(),
|
"op.framing" => "Crop & Rotate",
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
"param.temperature" => "Temperature".into(),
|
"param.temperature" => "Temperature",
|
||||||
"param.tint" => "Tint".into(),
|
"param.tint" => "Tint",
|
||||||
"param.exposure" => "Exposure".into(),
|
"param.exposure" => "Exposure",
|
||||||
"param.highlights" => "Highlights".into(),
|
"param.highlights" => "Highlights",
|
||||||
"param.shadows" => "Shadows".into(),
|
"param.shadows" => "Shadows",
|
||||||
"param.blacks" => "Blacks".into(),
|
"param.blacks" => "Blacks",
|
||||||
"param.whites" => "Whites".into(),
|
"param.whites" => "Whites",
|
||||||
"param.brilliance" => "Brilliance".into(),
|
"param.brilliance" => "Brilliance",
|
||||||
"param.vibrance" => "Vibrance".into(),
|
"param.vibrance" => "Vibrance",
|
||||||
"param.saturation" => "Saturation".into(),
|
"param.saturation" => "Saturation",
|
||||||
|
|
||||||
// What a faceted parameter adjusts — the mixer's three channels.
|
// What a faceted parameter adjusts — the mixer's three channels.
|
||||||
//
|
//
|
||||||
@@ -57,9 +163,9 @@ pub fn resolve(key: &str) -> String {
|
|||||||
// "Lum" from the keys. These name a run of twelve rows apiece, and an
|
// "Lum" from the keys. These name a run of twelve rows apiece, and an
|
||||||
// abbreviation at the head of a section is a word the reader has to
|
// abbreviation at the head of a section is a word the reader has to
|
||||||
// expand every time they scan past it.
|
// expand every time they scan past it.
|
||||||
"param.channel.hue" => "Hue".into(),
|
"param.channel.hue" => "Hue",
|
||||||
"param.channel.sat" => "Saturation".into(),
|
"param.channel.sat" => "Saturation",
|
||||||
"param.channel.lum" => "Luminance".into(),
|
"param.channel.lum" => "Luminance",
|
||||||
|
|
||||||
// The tone curve's four curves, which its points are *subject* to.
|
// The tone curve's four curves, which its points are *subject* to.
|
||||||
//
|
//
|
||||||
@@ -69,10 +175,10 @@ pub fn resolve(key: &str) -> String {
|
|||||||
// colours would derive correctly and are written out beside it anyway,
|
// colours would derive correctly and are written out beside it anyway,
|
||||||
// since a list where one entry is translated and three are guessed is
|
// since a list where one entry is translated and three are guessed is
|
||||||
// the shape a half-finished translation takes.
|
// the shape a half-finished translation takes.
|
||||||
"channel.rgb" => "RGB".into(),
|
"channel.rgb" => "RGB",
|
||||||
"channel.red" => "Red".into(),
|
"channel.red" => "Red",
|
||||||
"channel.green" => "Green".into(),
|
"channel.green" => "Green",
|
||||||
"channel.blue" => "Blue".into(),
|
"channel.blue" => "Blue",
|
||||||
|
|
||||||
// The hue bands, which a faceted row is *subject* to.
|
// The hue bands, which a faceted row is *subject* to.
|
||||||
//
|
//
|
||||||
@@ -82,32 +188,76 @@ pub fn resolve(key: &str) -> String {
|
|||||||
// nothing. These are also the only place a swatch's meaning is written
|
// nothing. These are also the only place a swatch's meaning is written
|
||||||
// down in words, which is what a photographer who cannot separate the
|
// down in words, which is what a photographer who cannot separate the
|
||||||
// squares by eye has to go on.
|
// squares by eye has to go on.
|
||||||
"band.red" => "Red".into(),
|
"band.red" => "Red",
|
||||||
"band.orange" => "Orange".into(),
|
"band.orange" => "Orange",
|
||||||
"band.yellow" => "Yellow".into(),
|
"band.yellow" => "Yellow",
|
||||||
"band.chartreuse" => "Yellow-Green".into(),
|
"band.chartreuse" => "Yellow-Green",
|
||||||
"band.green" => "Green".into(),
|
"band.green" => "Green",
|
||||||
"band.spring" => "Blue-Green".into(),
|
"band.spring" => "Blue-Green",
|
||||||
"band.cyan" => "Cyan".into(),
|
"band.cyan" => "Cyan",
|
||||||
"band.azure" => "Azure".into(),
|
"band.azure" => "Azure",
|
||||||
"band.blue" => "Blue".into(),
|
"band.blue" => "Blue",
|
||||||
"band.violet" => "Violet".into(),
|
"band.violet" => "Violet",
|
||||||
"band.magenta" => "Magenta".into(),
|
"band.magenta" => "Magenta",
|
||||||
"band.rose" => "Rose".into(),
|
"band.rose" => "Rose",
|
||||||
|
|
||||||
// Framing. "Straighten" rather than "Angle" because that is the task
|
// Framing. "Straighten" rather than "Angle" because that is the task
|
||||||
// the control performs; the number it reports is still degrees.
|
// the control performs; the number it reports is still degrees.
|
||||||
"param.angle" => "Straighten".into(),
|
"param.angle" => "Straighten",
|
||||||
"param.rotation" => "Rotate".into(),
|
"param.rotation" => "Rotate",
|
||||||
"param.flip_h" => "Flip Horizontal".into(),
|
"param.flip_h" => "Flip Horizontal",
|
||||||
"param.flip_v" => "Flip Vertical".into(),
|
"param.flip_v" => "Flip Vertical",
|
||||||
"param.crop_x" => "Crop Left".into(),
|
"param.crop_x" => "Crop Left",
|
||||||
"param.crop_y" => "Crop Top".into(),
|
"param.crop_y" => "Crop Top",
|
||||||
"param.crop_w" => "Crop Width".into(),
|
"param.crop_w" => "Crop Width",
|
||||||
"param.crop_h" => "Crop Height".into(),
|
"param.crop_h" => "Crop Height",
|
||||||
|
|
||||||
other => derive(other),
|
// TRACES: FR-DEV-5
|
||||||
}
|
// The steps that are not a parameter moving.
|
||||||
|
//
|
||||||
|
// Catalogued rather than derived because these are read as a *column*
|
||||||
|
// of short phrases, where `derive` would give "Mask Added" and "Reset
|
||||||
|
// Op" — the second of which names a function rather than an action,
|
||||||
|
// and the first of which reads as a label for a thing rather than for
|
||||||
|
// something the photographer did. A history is a list of decisions, so
|
||||||
|
// the rows are written as decisions.
|
||||||
|
//
|
||||||
|
// A step naming an operation or a parameter is *not* here: it resolves
|
||||||
|
// through the same `op.` and `param.` entries above that the panel
|
||||||
|
// uses, which is what lets an operation added as a YAML declaration
|
||||||
|
// appear in the history correctly named with no entry written for it
|
||||||
|
// (FR-DEV-3c).
|
||||||
|
"history.opened" => "Opened",
|
||||||
|
"history.edit" => "Edit",
|
||||||
|
"history.paste" => "Paste Settings",
|
||||||
|
"history.film" => "Film Stock",
|
||||||
|
|
||||||
|
"history.reset_all" => "Reset Everything",
|
||||||
|
"history.reset_op" => "Reset Group",
|
||||||
|
"history.reset_param" => "Reset Control",
|
||||||
|
"history.reset_framing" => "Reset Crop & Rotate",
|
||||||
|
|
||||||
|
"history.rotate" => "Rotate",
|
||||||
|
"history.flip_h" => "Flip Horizontal",
|
||||||
|
"history.flip_v" => "Flip Vertical",
|
||||||
|
|
||||||
|
"history.mask_added" => "Add Mask",
|
||||||
|
"history.mask_removed" => "Delete Mask",
|
||||||
|
"history.mask_toggled" => "Mask On/Off",
|
||||||
|
"history.mask_inverted" => "Invert Mask",
|
||||||
|
"history.mask_moved" => "Move Mask",
|
||||||
|
"history.mask_feather" => "Mask Feather",
|
||||||
|
"history.mask_morph" => "Mask Edge",
|
||||||
|
"history.mask_opacity" => "Mask Opacity",
|
||||||
|
"history.mask_falloff" => "Mask Falloff",
|
||||||
|
"history.mask_morphology" => "Mask Grow/Shrink",
|
||||||
|
"history.spot" => "Adjust Repair",
|
||||||
|
"history.spot_placed" => "Add Repair",
|
||||||
|
"history.spot_moved" => "Move Repair",
|
||||||
|
"history.spot_removed" => "Delete Repair",
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turn `op.some_new_thing` into `Some New Thing`.
|
/// Turn `op.some_new_thing` into `Some New Thing`.
|
||||||
@@ -137,6 +287,43 @@ fn derive(key: &str) -> String {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_step_the_interface_records_has_a_name() {
|
||||||
|
// A history is only as useful as its rows are distinguishable, and
|
||||||
|
// these are the ones no descriptor can name — the resets, the flips,
|
||||||
|
// the mask actions. A key missing from the catalogue derives something
|
||||||
|
// readable rather than nothing, so the failure this guards is subtler:
|
||||||
|
// two different steps deriving the *same* word, or a word that names
|
||||||
|
// the function that ran rather than the decision the user made.
|
||||||
|
let mut seen: Vec<String> = Vec::new();
|
||||||
|
for key in step::ALL.iter().map(|k| k.0) {
|
||||||
|
let label = catalogued(key).unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"{key} is not catalogued; it would fall through to `derive` \
|
||||||
|
and read as {:?}, which names a field rather than an act",
|
||||||
|
derive(key)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
assert!(!label.is_empty(), "{key} resolved to nothing");
|
||||||
|
assert!(
|
||||||
|
!seen.contains(&label.to_string()),
|
||||||
|
"{key} shares the label {label:?} with an earlier step"
|
||||||
|
);
|
||||||
|
seen.push(label.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_step_naming_an_operation_borrows_the_panels_label() {
|
||||||
|
// What keeps the history honest as the pipeline grows: a step that
|
||||||
|
// moved a parameter is named by the same entry the slider beside it
|
||||||
|
// is, so the two can never disagree, and an operation nobody has
|
||||||
|
// catalogued yet still reads correctly in both places.
|
||||||
|
assert_eq!(resolve("op.white_balance"), "White Balance");
|
||||||
|
assert_eq!(resolve("op.tone_curve"), "Tone Curve");
|
||||||
|
assert_eq!(resolve("param.exposure"), "Exposure");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn catalogued_keys_resolve_to_their_label() {
|
fn catalogued_keys_resolve_to_their_label() {
|
||||||
assert_eq!(resolve("op.white_balance"), "White Balance");
|
assert_eq!(resolve("op.white_balance"), "White Balance");
|
||||||
|
|||||||
+45
-3
@@ -43,7 +43,7 @@ mod sidecar_cache;
|
|||||||
mod spots_ui;
|
mod spots_ui;
|
||||||
mod trash;
|
mod trash;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -1303,9 +1303,16 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
|||||||
//
|
//
|
||||||
// Called on every slider change, so it must do no more than run the
|
// Called on every slider change, so it must do no more than run the
|
||||||
// adjust pass — the demosaic is not repeated.
|
// adjust pass — the demosaic is not repeated.
|
||||||
|
// TRACES: FR-DEV-5
|
||||||
|
// The history revision the panel was last built from. See the use below:
|
||||||
|
// the list is rebuilt when it would read differently, not when the picture
|
||||||
|
// is redrawn, and those are very different rates.
|
||||||
|
let drawn_history: Rc<Cell<Option<u64>>> = Rc::new(Cell::new(None));
|
||||||
|
|
||||||
let render_now: Render = {
|
let render_now: Render = {
|
||||||
let session = session.clone();
|
let session = session.clone();
|
||||||
let viewport = viewport.clone();
|
let viewport = viewport.clone();
|
||||||
|
let drawn_history = drawn_history.clone();
|
||||||
Rc::new(move |window: &AppWindow, draft: bool| {
|
Rc::new(move |window: &AppWindow, draft: bool| {
|
||||||
let mut slot = session.borrow_mut();
|
let mut slot = session.borrow_mut();
|
||||||
let Some(s) = slot.as_mut() else { return };
|
let Some(s) = slot.as_mut() else { return };
|
||||||
@@ -1318,6 +1325,20 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
|||||||
window.set_can_undo(s.can_undo());
|
window.set_can_undo(s.can_undo());
|
||||||
window.set_can_redo(s.can_redo());
|
window.set_can_redo(s.can_redo());
|
||||||
|
|
||||||
|
// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
// The list, on the same path and for the same reason — but only
|
||||||
|
// when it would read differently. A drag ends in a redraw per
|
||||||
|
// frame while folding into one step, so an unconditional rebuild
|
||||||
|
// here would tear down and recreate every row of the panel sixty
|
||||||
|
// times a second to arrive back at the list already on screen.
|
||||||
|
let revision = s.history_revision();
|
||||||
|
if drawn_history.get() != Some(revision) {
|
||||||
|
drawn_history.set(Some(revision));
|
||||||
|
window
|
||||||
|
.set_history_rows(slint::ModelRc::new(slint::VecModel::from(s.history_rows())));
|
||||||
|
window.set_undo_label(s.undo_label().into());
|
||||||
|
}
|
||||||
|
|
||||||
// TRACES: FR-DEV-3
|
// TRACES: FR-DEV-3
|
||||||
// Which part of the region overlay the view is showing. Here
|
// Which part of the region overlay the view is showing. Here
|
||||||
// rather than in the panel's own sync because a pan or a zoom
|
// rather than in the panel's own sync because a pan or a zoom
|
||||||
@@ -2089,7 +2110,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
|||||||
.and_then(dr_film::find)
|
.and_then(dr_film::find)
|
||||||
.and_then(dr_film::default_print)
|
.and_then(dr_film::default_print)
|
||||||
.is_some();
|
.is_some();
|
||||||
s.choose_film(*stock, print);
|
s.pick_film(*stock, print);
|
||||||
}
|
}
|
||||||
sync_rows(&w, &rows, &session);
|
sync_rows(&w, &rows, &session);
|
||||||
redraw(&w);
|
redraw(&w);
|
||||||
@@ -2103,7 +2124,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
|||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
if let Some(s) = session.borrow_mut().as_mut() {
|
if let Some(s) = session.borrow_mut().as_mut() {
|
||||||
if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) {
|
if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) {
|
||||||
s.choose_film(Some(&stock), print);
|
s.pick_film(Some(&stock), print);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sync_film(&w, &session);
|
sync_film(&w, &session);
|
||||||
@@ -2149,6 +2170,27 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
// Clicking a row. Arriving six steps away costs what arriving from one
|
||||||
|
// does, because a step is a whole state — see `History::go_to`.
|
||||||
|
{
|
||||||
|
let weak = window.as_weak();
|
||||||
|
let session = session.clone();
|
||||||
|
let redraw = redraw.clone();
|
||||||
|
let rows = rows.clone();
|
||||||
|
window.on_history_picked(move |index| {
|
||||||
|
let Some(w) = weak.upgrade() else { return };
|
||||||
|
let stepped = session
|
||||||
|
.borrow_mut()
|
||||||
|
.as_mut()
|
||||||
|
.is_some_and(|s| s.go_to_history(index));
|
||||||
|
if stepped {
|
||||||
|
sync_rows(&w, &rows, &session);
|
||||||
|
redraw(&w);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---- zoom, pan and crop ---------------------------------------------
|
// ---- zoom, pan and crop ---------------------------------------------
|
||||||
//
|
//
|
||||||
// Zoom and pan are viewing state and touch no parameter, so unlike the
|
// Zoom and pan are viewing state and touch no parameter, so unlike the
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Theme } from "theme.slint";
|
|||||||
import { AdjustPanel, GeometryPanel, ModeStrip, ParamRow, TransferPanel, ViewMode } from "adjust.slint";
|
import { AdjustPanel, GeometryPanel, ModeStrip, ParamRow, TransferPanel, ViewMode } from "adjust.slint";
|
||||||
import { GradientHandle, HandleRole, MaskPanel, MaskRow, SubjectRow } from "masks.slint";
|
import { GradientHandle, HandleRole, MaskPanel, MaskRow, SubjectRow } from "masks.slint";
|
||||||
import { SpotHandle, SpotPanel, SpotRole } from "spots.slint";
|
import { SpotHandle, SpotPanel, SpotRole } from "spots.slint";
|
||||||
|
import { HistoryPanel, HistoryRow } from "history.slint";
|
||||||
import { LaunchScreen } from "launch.slint";
|
import { LaunchScreen } from "launch.slint";
|
||||||
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow } from "library.slint";
|
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow } from "library.slint";
|
||||||
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
||||||
@@ -361,6 +362,15 @@ export component AppWindow inherits Window {
|
|||||||
in property <bool> can-redo: false;
|
in property <bool> can-redo: false;
|
||||||
callback undo();
|
callback undo();
|
||||||
callback redo();
|
callback redo();
|
||||||
|
|
||||||
|
/// Every step, newest first. Rust owns the order and stamps each row with
|
||||||
|
/// its own position in the stack, so nothing here does arithmetic to turn
|
||||||
|
/// a row back into a step.
|
||||||
|
in property <[HistoryRow]> history-rows;
|
||||||
|
/// What undo would take back, resolved. Empty when there is nowhere to go.
|
||||||
|
in property <string> undo-label: "";
|
||||||
|
/// A row's own `index`.
|
||||||
|
callback history-picked(int);
|
||||||
/// Scroll-to-zoom: factor, and the anchor in fractions of the visible area.
|
/// Scroll-to-zoom: factor, and the anchor in fractions of the visible area.
|
||||||
callback zoom-at(float, float, float);
|
callback zoom-at(float, float, float);
|
||||||
callback pan-by(float, float);
|
callback pan-by(float, float);
|
||||||
@@ -2604,6 +2614,35 @@ in property <bool> panel-visible: true;
|
|||||||
film-picked(i) => { root.film-picked(i); }
|
film-picked(i) => { root.film-picked(i); }
|
||||||
film-print-toggled(on) => { root.film-print-toggled(on); }
|
film-print-toggled(on) => { root.film-print-toggled(on); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 1px;
|
||||||
|
background: Theme.rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last in the column, and the length is why. The
|
||||||
|
// stack runs sixty-four deep, so anywhere else it
|
||||||
|
// would push the controls it is a record of below
|
||||||
|
// the fold. Its own rows run newest-first, which
|
||||||
|
// puts the steps worth reaching immediately under
|
||||||
|
// the heading rather than at the far end of them.
|
||||||
|
//
|
||||||
|
// Present in local mode as well. The mask work
|
||||||
|
// *is* history — adding a layer, moving a
|
||||||
|
// gradient and feathering an edge are all steps —
|
||||||
|
// and a list that emptied when the photographer
|
||||||
|
// entered the mode that produces the most steps
|
||||||
|
// would be a list they stopped trusting.
|
||||||
|
HistoryPanel {
|
||||||
|
rows: root.history-rows;
|
||||||
|
enabled: root.adjust-enabled;
|
||||||
|
can-undo: root.can-undo;
|
||||||
|
can-redo: root.can-redo;
|
||||||
|
undo-label: root.undo-label;
|
||||||
|
undo => { root.undo(); }
|
||||||
|
redo => { root.redo(); }
|
||||||
|
picked(i) => { root.history-picked(i); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// TRACES: FR-DEV-5 | FR-DEV-7
|
||||||
|
// The steps this photograph has been through, and the way back to any of them.
|
||||||
|
//
|
||||||
|
// **Nothing here names an operation, and nothing here names a step.** The rows
|
||||||
|
// arrive already resolved — Rust turns each step's localisation key into a
|
||||||
|
// display string against the UI's catalogue — so an operation added to the
|
||||||
|
// pipeline as a YAML declaration appears in this list under a sensible name
|
||||||
|
// with no change to this file, on the same terms it appears in the panel
|
||||||
|
// (FR-DEV-3c).
|
||||||
|
//
|
||||||
|
// **Why a list and not just the two buttons.** Undo answers "take back the
|
||||||
|
// last thing", which is the question a photographer asks about the mistake
|
||||||
|
// they have just noticed. It is the wrong instrument for the one they notice
|
||||||
|
// six adjustments later: eight presses, each changing the picture, with no way
|
||||||
|
// to see how far back the mistake was without passing through it. A step is a
|
||||||
|
// whole state in `dr-pipeline`, so arriving at one from six away costs what
|
||||||
|
// arriving from one does — which is what makes a row worth making clickable
|
||||||
|
// rather than decorative.
|
||||||
|
|
||||||
|
import { Theme } from "theme.slint";
|
||||||
|
import { PanelHeading, Caption, Label, Value, Button } from "widgets.slint";
|
||||||
|
|
||||||
|
// One step, flattened for Slint's model system.
|
||||||
|
export struct HistoryRow {
|
||||||
|
// Routing back to the core: this step's position in the stack. **Not** its
|
||||||
|
// position in this list, which runs the other way — see `history-rows` in
|
||||||
|
// `develop.rs` for why the two are deliberately different numbers.
|
||||||
|
index: int,
|
||||||
|
|
||||||
|
// Resolved in Rust against the UI's catalogue; the core deals in keys.
|
||||||
|
label: string,
|
||||||
|
|
||||||
|
// The state the photograph is in right now. Exactly one row carries it.
|
||||||
|
current: bool,
|
||||||
|
|
||||||
|
// A step the photographer has stepped back *out* of, still reachable by
|
||||||
|
// redo. Listed rather than hidden — redo would otherwise arrive somewhere
|
||||||
|
// this panel never mentioned — but drawn as the branch it is.
|
||||||
|
undone: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
component StepRow inherits Rectangle {
|
||||||
|
in property <HistoryRow> data;
|
||||||
|
in property <bool> enabled: true;
|
||||||
|
callback picked();
|
||||||
|
|
||||||
|
height: Theme.row-height;
|
||||||
|
background: root.data.current
|
||||||
|
? Theme.selected
|
||||||
|
: (touch.has-hover ? Theme.hover : transparent);
|
||||||
|
|
||||||
|
touch := TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: max(parent.height, Theme.touch-target);
|
||||||
|
y: (parent.height - self.height) / 2;
|
||||||
|
enabled: root.enabled;
|
||||||
|
mouse-cursor: root.enabled ? MouseCursor.pointer : MouseCursor.default;
|
||||||
|
clicked => { root.picked(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
// Small, because the panel around this already pads. The row's
|
||||||
|
// background is the highlight for the current step, so it wants to be
|
||||||
|
// a band the width of the column rather than a chip inset from it.
|
||||||
|
padding-left: Theme.gap-sm;
|
||||||
|
padding-right: Theme.gap-sm;
|
||||||
|
spacing: Theme.gap-sm;
|
||||||
|
|
||||||
|
// The mark for where the photograph stands. A filled bar against the
|
||||||
|
// leading edge rather than a tick beside the name: the eye finds one
|
||||||
|
// edge down a column of forty rows, and a glyph in the text column
|
||||||
|
// would have to be read.
|
||||||
|
Rectangle {
|
||||||
|
width: 2px;
|
||||||
|
height: parent.height;
|
||||||
|
background: root.data.current ? Theme.active : transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.data.label;
|
||||||
|
emphasised: root.data.current || touch.has-hover;
|
||||||
|
horizontal-stretch: 1;
|
||||||
|
overflow: elide;
|
||||||
|
// Dimmed rather than removed: this step is a future the
|
||||||
|
// photographer stepped out of, and it is still where redo goes.
|
||||||
|
opacity: root.data.undone ? 0.45 : 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export component HistoryPanel inherits Rectangle {
|
||||||
|
in property <[HistoryRow]> rows;
|
||||||
|
in property <bool> enabled: true;
|
||||||
|
in property <bool> can-undo: false;
|
||||||
|
in property <bool> can-redo: false;
|
||||||
|
/// What undo would take back, already resolved. Empty when there is
|
||||||
|
/// nowhere to go.
|
||||||
|
in property <string> undo-label: "";
|
||||||
|
|
||||||
|
callback undo();
|
||||||
|
callback redo();
|
||||||
|
/// A row's own `index`, not its position in `rows`.
|
||||||
|
callback picked(int);
|
||||||
|
|
||||||
|
background: Theme.surface;
|
||||||
|
|
||||||
|
// **Flat, deliberately** — `MaskPanel` carries the long version of this
|
||||||
|
// and it applies here unchanged: a nested layout under-reports its height,
|
||||||
|
// so what follows it gets drawn on top of what came before, which is
|
||||||
|
// invisible in the source and obvious the moment anyone opens the panel.
|
||||||
|
// Every element is a direct child of the one layout that measures them,
|
||||||
|
// and the ones that come and go carry their own condition rather than
|
||||||
|
// being grouped inside a wrapper.
|
||||||
|
//
|
||||||
|
// No explicit height either, for the same reason `AdjustPanel` and
|
||||||
|
// `MaskPanel` declare none: the row count changes as the photographer
|
||||||
|
// works, and a height pinned to a layout's preferred size is one more
|
||||||
|
// thing that has to keep up with a repeater.
|
||||||
|
VerticalLayout {
|
||||||
|
padding: Theme.gap;
|
||||||
|
spacing: Theme.gap-sm;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
// A heading, not a collapsible. `GeometryPanel` carries the argument
|
||||||
|
// and it holds here: the list is last in the column, so what a lid
|
||||||
|
// would save is scrolling past nothing.
|
||||||
|
HorizontalLayout {
|
||||||
|
PanelHeading { text: "HISTORY"; }
|
||||||
|
Rectangle { horizontal-stretch: 1; }
|
||||||
|
if root.enabled && root.rows.length > 1: Value {
|
||||||
|
text: root.rows.length - 1 + (root.rows.length == 2 ? " step" : " steps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !root.enabled: Caption { text: "No image"; }
|
||||||
|
|
||||||
|
// The pair, here as well as in the status strip. The strip's copy is
|
||||||
|
// the one that survives the column being put away; this one sits
|
||||||
|
// against the list that says what it will do, which is the pairing
|
||||||
|
// that makes either of them legible.
|
||||||
|
if root.enabled: HorizontalLayout {
|
||||||
|
spacing: Theme.gap-sm;
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Undo";
|
||||||
|
enabled: root.can-undo;
|
||||||
|
horizontal-stretch: 1;
|
||||||
|
clicked => { root.undo(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Redo";
|
||||||
|
enabled: root.can-redo;
|
||||||
|
horizontal-stretch: 1;
|
||||||
|
clicked => { root.redo(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// What the left-hand button would take back, spelled out.
|
||||||
|
//
|
||||||
|
// On a caption rather than on the button, because `Button` sizes to
|
||||||
|
// its label and does not elide: "Undo Highlights & Shadows" is most of
|
||||||
|
// a 280px column on its own, and two of those would lever the column
|
||||||
|
// open — `MaskPanel` has the same note about an unwrapped sentence.
|
||||||
|
//
|
||||||
|
// Undo only. Redo's destination is the row directly above the mark in
|
||||||
|
// the list below, where it can be seen; the step undo takes back is
|
||||||
|
// the one the photographer is standing on and stopped tracking after a
|
||||||
|
// run of small adjustments, which is the moment they are least willing
|
||||||
|
// to press a button to find out.
|
||||||
|
if root.enabled && root.can-undo: Caption {
|
||||||
|
text: "Undo: " + root.undo-label;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.enabled && root.rows.length > 0: Rectangle {
|
||||||
|
height: 1px;
|
||||||
|
background: Theme.rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newest first. The list is consulted to take back something just
|
||||||
|
// done rather than browsed from the beginning — the order
|
||||||
|
// `dr_catalog::trash` settled on for the same question — and it keeps
|
||||||
|
// the end being worked at against the heading rather than sixty-four
|
||||||
|
// rows below it.
|
||||||
|
for row in root.rows: StepRow {
|
||||||
|
data: row;
|
||||||
|
enabled: root.enabled;
|
||||||
|
picked => { root.picked(row.index); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user