Undo and Redo
Undo journal must identify saved position by state identity
req~logic.undo.saved-position-identity~1
A library counts as unmodified exactly when its history stands at the position it was saved at, and not when it has merely travelled the same distance along a different history. This decides whether the modified marker is shown and whether closing the library offers to save it, so a wrong answer loses the user’s work silently.
Needs: impl, utest
Library must maintain isolated undo history
req~logic.undo.journal-per-library~1
Each open library keeps its own undo history, and undo, redo and the saved position act on that library alone. A change belongs to the library it was made in, whichever library is in front when it is recorded, and closing a library discards its history.
Needs: impl, utest
Undo journal must apply and record undoable changes atomically
req~logic.undo.apply-and-record-atomically~1
When the undo journal performs a change, the change becomes visible in the library and present on the undo stack as a single operation. No other thread can observe the library holding a change the journal does not yet know about, so an undo arriving from one reverses the change it was aimed at rather than the one before it.
Needs: impl, utest
Field editors must route undo shortcuts to library undo history
req~logic.undo.text-field-shortcut~1
Pressing the undo or redo shortcut while a field editor’s text control has focus performs the library’s undo or redo, the same as the toolbar buttons. JavaFX’s built-in per-control text undo is bypassed: it would only revert the keystrokes of that one control, and it throws when its history is empty.
Needs: impl, utest
Undo journal must decline undo while command holds pending writes
req~logic.undo.writes-reserved-against-undo~1
While a command is applying changes it has not yet handed to the journal, undo and redo decline for that library and say which command holds it. Taking a change back over writes that are not yet recorded would leave the library in a state no step on the stack describes, and the push that follows would discard the undone change.
Needs: impl, utest
Undo journal must refuse stale change when library state has advanced
req~logic.undo.stale-change-refused~1
A change describing one value applies only while the library still holds the value it recorded, and reports the mismatch instead of writing. A command writing on a background thread can have moved that value on since, and overwriting it would replace a newer value with an older one behind the user’s back.
Needs: impl, utest
Group operations must record on undo stack as single step
req~logic.undo.group-operations-recorded~1
Adding, removing, moving, sorting and editing groups each go on the undo stack as a single step, together with the entry assignments the operation changed. Undoing one restores the tree that was there before it, and the assignments with it.
Needs: impl, utest
Library modified marker must derive state from undo journal position
req~logic.undo.modified-marker-derived~1
A library counts as modified exactly when its journal stands away from the saved position, or when it was changed by something the journal could not record. The marker is derived from that rather than set by each command, so no undo path has to remember to correct it afterwards.
Needs: impl, utest
Library settings updates must record on undo stack as single step
req~logic.undo.library-settings-recorded~1
Accepting the Library properties dialog goes on the undo stack as a single step covering every tab, and undoing it restores the settings the library had before the dialog was opened. The settings are written straight to the library’s metadata by seven tabs at once, so recording them as one snapshot pair is what makes the dialog undoable at all — and what keeps the modified marker honest for a change no command would otherwise report.
Needs: impl, utest
Text typing must record on undo stack word by word
req~logic.undo.typing-is-one-step~2
A run of keystrokes in one field of one entry goes on the undo stack as a single step, so undoing takes back the word that was typed rather than the last character. The run ends at the end of a word, when the editor moves to something else, when the library is saved at that point, or when a command records a step of its own; a run that ends where it started leaves no step behind.
Rationale: Breaking at a word matches conventional editor expectations and bounds undo granularity so that a single undo command does not erase an entire multiline field.
Needs: impl, utest
Entry insertion must record on undo stack as single step
req~logic.undo.entry-insert-recorded~1
An entry that reaches a library goes on the undo stack as a single step, whichever way it was added — typed identifier, URL, dropped file, paste or citation relation — together with the citation key and group assignments the insert produces. The library counts as modified from that moment, so it is not closed without being offered a save, and undoing removes the entry again.
Needs: impl, utest