feat: add session undo and redo
Continuous Integration / lint-and-test (push) Successful in 14s
Continuous Integration / docker-build-and-push (push) Successful in 34s

This commit is contained in:
Lucas Winther committed 2026-09-30 01:33:29 +02:00
1 parent d38584396b
commit 804f32c5fb
9 files changed
+411 -14

No files matched your search

+15
View File
@@ -101,6 +101,21 @@ uses on a first run, so "reset" and "a brand-new browser" cannot drift apart.
Both are preceded in the UI by a JSON backup download that must succeed before
the destructive write is attempted.
## Session undo and redo
The snapshot store keeps up to 50 prior saved datasets in memory. A changed
`mutate` adds one step after its write succeeds; an unchanged mutation adds
none. Undo and redo run through the same queue and persist their target dataset
before changing the visible snapshot or either history stack. A failed write
therefore leaves both the data and the available undo/redo actions intact.
Import and reset clear the stacks only after their write succeeds. They are
whole-dataset replacement flows with backup-first protection, not ordinary
undoable edits. History is not in `Dataset` or IndexedDB and vanishes on reload.
The header reads a stable history capability snapshot from the store to enable
its controls; keyboard shortcuts skip editable fields so browser text undo
still works.
## Money
A float, deliberately, with two rules that make it safe:
@@ -0,0 +1,12 @@
# Session undo and redo implementation
1. Extend snapshot-store tests with changed and unchanged edits, undo/redo
ordering, a new edit after undo, the 50-step bound, queued writes, failed
writes, import/reset boundaries, and session reload behavior. Run them red.
2. Add history state and queued undo/redo operations in `snapshot.ts`. Ensure
history changes only after persistence commits. Run the store tests green.
3. Add header controls and keyboard handling with an interaction test for
disabled states, error display, and native editable shortcuts. Run it red,
implement, then run it green.
4. Update architecture and user-facing documentation. Run `bun test`,
`bunx tsc --noEmit`, build, and inspect desktop/mobile browser captures.
@@ -0,0 +1,31 @@
# Session undo and redo
The owner needs to reverse an accidental saved edit without reconstructing a
budget from an export. History lasts for the current page session only; a reload
starts with the saved dataset and empty history.
## Behavior
- Every successful, changed `mutate` produces one undo step. Unchanged edits
produce none. The last 50 prior datasets are retained.
- Undo and redo restore complete datasets, including IDs and dated rules, by
writing through the store's existing serialization queue. A failed write
changes neither the displayed dataset nor either history stack.
- A new successful edit after undo clears redo. A failed edit leaves redo
available.
- Import and reset are destructive whole-dataset operations. Their existing
backup-first flow remains mandatory. On success they clear both history
stacks; on failure they leave history intact. Neither can be undone with the
session controls.
- The header offers Undo and Redo buttons. Ctrl/Cmd+Z undoes and
Ctrl/Cmd+Shift+Z or Ctrl/Cmd+Y redoes when focus is outside an editable
control. Native text editing shortcuts remain native.
- A failed undo or redo shows its persistence error near the controls.
## Implementation boundary
History belongs to `src/store/snapshot.ts`, where edits already serialize and
only become visible after persistence succeeds. The UI reads stable capability
state from the same store and invokes its methods; no domain or dataset schema
change is needed. History is deliberately memory-only, so exports contain only
the current dataset and IndexedDB needs no upgrade.