Keep an edit under a name, not just on the clipboard

FR-DEV-6 asks for three things — named presets, copy/paste between
images, and batch-apply to a selection. The last two have been here for
a while; this is the first.

The format is the sidecar's, deliberately. A preset *is* the non-default
half of a version, so the lines are the same lines keyed the same way,
which makes the two files diffable against each other and lets someone
debugging an edit paste a block from one into the other. One file rather
than one per preset: a preset per file makes the name a path, and every
name then has to survive a filesystem — a `/` becomes a directory, a
name differing only in case collides on one platform and not another,
and renaming becomes two operations that can half-fail. As a key in a
document it is none of those.

Unknown *parameters* needed no machinery. `Preset` already holds
whatever keys it is given and resolves them against the descriptors only
at apply time, so one written by a newer build survives by being stored.
Only lines that are not `op.param = float` at all are preserved
verbatim, which is the sidecar's version-skew promise made here too.

Applying is the paste path with a different source, so a preset reaches
a selection through the sidecar read-modify-write that was already
there: no graph, no decode, no GPU, forty files or one.

Two smaller decisions worth the record. A library that fails to parse is
held empty in memory and *not* written back over — settings regenerate
themselves and this is work, so a parse failure must not be the moment
it is destroyed. And every save persists immediately and rolls the
in-memory copy back if the write fails, so the sheet never lists a
preset the file does not have.

The grid's "Presets" button is gated on the selection alone, unlike the
"Paste to 40" beside it. That button needs a clipboard armed this
session; the preset list is whatever was saved last month, and hiding it
behind an unrelated action is what makes a feature only its author knows
about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 20:07:14 +02:00
co-authored by Claude Opus 5
parent 5133e53bc8
commit 5a8327824f
10 changed files with 1502 additions and 47 deletions
+21
View File
@@ -850,6 +850,9 @@ component HeaderActions inherits HorizontalLayout {
callback export-selection();
callback cancel-export();
callback paste-settings-to-selection();
/// TRACES: FR-DEV-6
/// Open the named-preset sheet over the selection.
callback open-presets();
callback remove-from-collection();
/// Open the sheet that files the selection in a collection.
callback add-to-collection();
@@ -931,6 +934,20 @@ component HeaderActions inherits HorizontalLayout {
clicked => { root.paste-settings-to-selection(); }
}
// TRACES: FR-DEV-6
// The saved settings, beside the copied ones.
//
// Gated on the selection alone, unlike the paste beside it: that button
// needs a clipboard *this session*, where the preset list is whatever the
// photographer saved last month. Requiring an armed clipboard here would
// hide the saved presets behind an unrelated action — which is the shape
// of bug that makes a feature only its author knows about (FR-UI-4).
if root.selected-count > 0: Button {
text: "Presets";
y: root.centred ? (root.row-height - self.height) / 2 : 0;
clicked => { root.open-presets(); }
}
// TRACES: FR-EXP-7 | NFR-ARCH-3
// Export the selection, and stop the batch that is running.
//
@@ -1332,6 +1349,8 @@ export component LibraryGrid inherits Rectangle {
in property <bool> settings-armed: false;
in property <string> settings-summary;
callback paste-settings-to-selection();
/// TRACES: FR-DEV-6
callback open-presets();
// TRACES: FR-EXP-7
// Exporting the selection. The grid owns neither the settings that decide
@@ -1698,6 +1717,7 @@ export component LibraryGrid inherits Rectangle {
export-selection => { root.export-selection(); }
cancel-export => { root.cancel-export(); }
paste-settings-to-selection => { root.paste-settings-to-selection(); }
open-presets => { root.open-presets(); }
remove-from-collection => { root.remove-from-collection(); }
select-mode: root.select-mode;
toggle-select-mode => { root.toggle-select-mode(); }
@@ -1783,6 +1803,7 @@ export component LibraryGrid inherits Rectangle {
export-selection => { root.export-selection(); }
cancel-export => { root.cancel-export(); }
paste-settings-to-selection => { root.paste-settings-to-selection(); }
open-presets => { root.open-presets(); }
remove-from-collection => { root.remove-from-collection(); }
select-mode: root.select-mode;
toggle-select-mode => { root.toggle-select-mode(); }