Undo and Redo

The saved state of a library is identified, not counted

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

Every library has its own 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

An undoable change is applied and recorded as one operation

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

Undo shortcuts in text fields drive the library’s 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

A command’s writes are reserved against undo

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

A change that the library has moved on from is refused, not applied

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

Every group operation is one undo 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

The modified marker follows the journal

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 are one undo 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

Typing a word is one undo step

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. Breaking at a word is what every other editor does, and it bounds what a single Ctrl+Z can take back: without it, one keystroke of undo takes back a whole abstract.

Needs: impl, utest

An entry added to a library is one undo 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