Keep the mask when the photograph is closed
A local adjustment survived until the session ended and then did not exist. Nothing reported it, because nothing had failed. The sidecar format has carried masks since they were added, `Version::apply` restores them into the graph, and `Version::update` captures them — that work landed complete and was never called. The autosave writes `copy_settings()`, which is a `Preset`: a map from (operation, parameter) to a number. A mask is not a parameter. It is a rule about *where*, with a chain of its own, so it fell outside the only thing being written, and the read path had nothing to read. The save now carries the stack beside the preset, on both the local and the remote path. Deliberately not merged but replaced wholesale: this is the stack as it stands, so a layer the user deleted has to leave the file too. Merging two devices' stacks is `Sidecar::merge`'s job and belongs to sync (FR-NC-9). A paste still carries no masks, and the `Option` is how that is said. Settings travel between photographs; a mask does not, because it is drawn against one frame and describes nothing on another — and `Scope` cannot express that, since it filters parameters and a mask is not one. The test fails against the old save path with "the mask must come back with the photograph", which is the whole of the defect: not a crash, not an error, just an edit that was not there in the morning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+30
-1
@@ -375,6 +375,23 @@ pub enum Amendment {
|
||||
Settings {
|
||||
preset: dr_pipeline::Preset,
|
||||
scope: dr_pipeline::Scope,
|
||||
/// TRACES: FR-DEV-3 | FR-CAT-8
|
||||
/// The local adjustments, when this is an image's own edit being
|
||||
/// written back rather than a paste onto someone else's.
|
||||
///
|
||||
/// A `Preset` is a parameter map, and a mask is not a parameter — it
|
||||
/// is a rule about *where*, with a chain of its own. So a save that
|
||||
/// carried only the preset wrote the sliders and silently dropped
|
||||
/// every local adjustment: the sidecar format has stored masks since
|
||||
/// they were added and `Version::apply` restores them, but nothing
|
||||
/// ever put any there. The mask survived until the session ended and
|
||||
/// then did not exist.
|
||||
///
|
||||
/// `None` for a paste, which must not carry the source image's masks
|
||||
/// onto the target: a mask is drawn against one photograph and means
|
||||
/// nothing on another, and `Scope` cannot express that because it
|
||||
/// filters parameters.
|
||||
masks: Option<dr_pipeline::mask::MaskStack>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -591,8 +608,20 @@ fn amend(base: dr_pipeline::Sidecar, w: &SidecarWrite) -> dr_pipeline::Sidecar {
|
||||
version.rating = *rating;
|
||||
version.flag = *flag;
|
||||
}
|
||||
Amendment::Settings { preset, scope } => {
|
||||
Amendment::Settings {
|
||||
preset,
|
||||
scope,
|
||||
masks,
|
||||
} => {
|
||||
preset.amend(&mut version.params, *scope);
|
||||
// Replaced wholesale rather than merged: this is the whole of the
|
||||
// image's local adjustment stack as it stands, so a layer the user
|
||||
// deleted has to leave the sidecar too. Cross-device merging of
|
||||
// two stacks is `Sidecar::merge`'s job and happens on sync, not
|
||||
// here (FR-NC-9).
|
||||
if let Some(masks) = masks {
|
||||
version.masks = masks.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user