Store undo history as serialized patches - #359
Conversation
Replace the snapshot undo history (raw copies of the whole cloud plus a Go-side header list) with a patch-based history: each edit pushes a patch reverting it, serialized and stored uniformly on the JS heap. For now every edit type uses replacePatch, a whole-cloud snapshot, so behavior and memory characteristics are unchanged while the pipeline (push, serialized storage, undo by revert) is in place. Follow-ups replace the snapshot fallback with cheap per-operation patches and compress what remains. The non-js history stub becomes a real implementation (historyMem), making undo behavior testable with plain go test; randomized round-trip tests assert byte-exact restoration. Undo depth semantics of max_history are unchanged (N entries = N undos). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #359 +/- ##
==========================================
+ Coverage 39.34% 45.93% +6.58%
==========================================
Files 8 9 +1
Lines 1426 1585 +159
==========================================
+ Hits 561 728 +167
+ Misses 829 808 -21
- Partials 36 49 +13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR refactors the editor undo history mechanism to store serialized “inverse edit” patches (currently implemented as a whole-cloud replacePatch) instead of storing full point cloud copies, laying groundwork for future per-operation patches and improved memory behavior (especially in WASM/JS).
Changes:
- Introduces a patch serialization format (
patch.go) and uses it to store undo history as packed patch bytes. - Replaces the previous undo history API with
push(patch),undo(current), andsquashLatest()and updates call sites accordingly. - Adds Go (non-js) in-memory history implementation plus new tests validating undo round-trips and history constraints.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| undo.go | Replaces dummy history with historyMem storing packed patch chunks for non-js builds. |
| undo_js.go | Refactors JS history to store packed patch bytes in JS Uint8Array chunks and adds squashLatest(). |
| patch.go | Adds patch interface plus replacePatch encoding/decoding and chunk reversion logic. |
| patch_test.go | Adds unit tests for replacePatch revert and encode/decode round-trip. |
| history_test.go | Adds randomized editor edit/undo round-trip tests, max history depth tests, and squash tests. |
| editor.go | Updates history interface and editor operations to record replacePatch snapshots on edits. |
| command.go | Replaces the previous pop() usage with squashLatest() for voxel-filter undo grouping. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A failed revert used to discard the entry; a later undo would then apply an older patch to a state it was not recorded against. Keep the history intact and block undo at the broken entry instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
buf.Bytes() retains the grown capacity of the buffer, which can be nearly twice the content size and is held long-term by historyMem. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
historyMem exists only in the non-js build; go vet for GOOS=js compiles test files too and failed on the reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bound nFields by the minimal encoded field size so corrupted counts fail before allocating, and rewrite the viewpoint bound in the same multiplication-free form as the other guards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pushing a replacePatch serialized the whole cloud into a Go buffer before copying it to the JS heap, transiently holding extra full-size copies in the WASM linear memory, which never shrinks. Split the patch wire form into a head and a raw payload (encodeHead/payload) and copy both straight into one Uint8Array, restoring the memory behavior of the previous direct-copy implementation for snapshots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
Every edit clones the whole point cloud in the WASM heap and pushes another full copy to the JS heap as undo history. For large PCDs the history alone keeps several times the file size resident, and since the WASM linear memory never shrinks, the per-edit clone turns the peak into permanent residency.
Changes
Groundwork for patch-based undo history; only the history mechanism is replaced.