Fix undo ledger holes at the store seam and refuse outdated draft stores#127
Open
kostyafarber wants to merge 3 commits into
Open
Fix undo ledger holes at the store seam and refuse outdated draft stores#127kostyafarber wants to merge 3 commits into
kostyafarber wants to merge 3 commits into
Conversation
The sources/source_lib/fontinfo_remainder/font_binaries changes from the UFO preservation and designspace work reshaped the baseline schema while SCHEMA_VERSION stayed 1, so pre-existing draft stores failed opaquely with "no such table" mid-session. Bump the version and refuse outdated stores up front with an actionable error (delete the draft store and re-import) — pre-release policy is baseline-in-place, no migrations.
Undo/redo replays persist through the same change-set seam as apply, but the seam dropped state the in-memory replay kept, so the draft store silently diverged from the live font: - SourceCreated carried only name + location; replaying a source delete undo (or an axis-delete undo, which re-emits located sources) wiped filename, color, role, layer_name, and source lib in the store. The change now snapshots the full source. - A batch like [DeleteAxis, DeleteSource] wedged undo forever: the reversed replay re-inserts the source before its axis row exists, hitting a foreign-key violation on the location upsert, and the hand-back retry loop can never get past it. Location entries on axes without rows are now skipped (mirroring replace_font_state); the axis step that follows re-emits the source and fills them in. - order_index was hardcoded 0 for created axes/sources/glyphs and a glyph rename reset it. Creates now append at COUNT(*), deletes compact the sequence (mirroring Vec::remove), renames leave order untouched. - Restoring a deleted glyph layer re-created a bare row; contours, anchors, components, guidelines, and layer lib never reached the store. GlyphLayerValue now carries the layer's full content and replays emit it alongside the created row.
Every undo/redo in the workspace intent tests now reloads the font from the draft store and asserts it equals the in-memory font, so any field a future replay drops or misorders fails structurally instead of surfacing at crash-recovery. Adds regression tests for the previously wedged [DeleteAxis, DeleteSource] batch (both orders, including redo/undo cycles) and for source extras surviving delete undo, driven by the kitchen-sink sample font.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adversarial re-review of #119 and #121 (rebased on #124) found the undo ledger and the SQLite draft store disagreeing about what a replay restores. This PR fixes the seam and adds a structural test invariant so the class stays fixed.
Bugs fixed
Wedged batch undo (FK violation on reverse replay). Undoing
[DeleteAxis, DeleteSource]replays steps reversed, so the source row is re-inserted before its axis row exists; the location upsert hits a foreign-key violation, the hand-back mechanism restores the entry, and every retry fails identically — undo is permanently stuck. The store now skips location entries on axes that have no row (mirroringreplace_font_state); the axis step that follows in the same replay re-emits the located sources and fills the values in. Both batch orders plus redo/undo cycles are covered by regression tests.SourceCreatedreplay wiped source fields. The change record carried only name + location, so undoing a source delete (or an axis delete, which re-emits located sources) blankedfilename,color,role,layer_name, and the source lib in the draft store while memory kept them — divergence that surfaced on crash-recovery.SourceCreatednow snapshots the full source and the store handler writes all of it, includingsource_lib.SCHEMA_VERSIONnever bumped. feat: UFO round-trip preservation contract #119/fix: designspace saves preserve project structure with atomic writes (re-target) #124 added tables and columns (fontinfo_remainder,source_lib,font_binaries,sources.color,sources.layer_name) while the version stayed 1, so older draft stores failed opaquely with "no such table". Version is now 2 and an outdated store is refused with an actionable error ("delete the draft store and re-import the font") — per the pre-release policy: baseline edited in place, no migration machinery.order_indexnever properly persisted. Created axes/sources/glyphs were hardcoded to order 0 and a glyph rename reset the glyph's order. Creates now append atCOUNT(*)(mirroring the in-memoryVec::push), deletes compact the sequence (mirroringVec::remove), and identity changes no longer touch order.Bonus hole caught by the new invariant: restoring a deleted glyph layer (source-delete undo, glyph replay) re-created a bare row — contours, anchors, components, guidelines, and layer lib never reached the store.
GlyphLayerValuenow carries the layer's full content and replays emit it alongside the created row.Structural enforcement
workspace_test.rsnow routes every undo/redo throughundo_and_verify/redo_and_verify, which reload the font from the draft store andassert_eq!it against the in-memory font (Fontequality covers all persisted data, index caches excluded). This is the invariant that catches bugs 1, 2, 4, and 5 structurally, and new intent tests inherit it.Tests
cargo test --workspace: all green (122 shift-font, 39 shift-store, 48 shift-workspace, plus backends/bridge/source suites)SourceCreatedround-trip, missing-axis location skip, order append/compaction,[DeleteAxis, DeleteSource]batch undo/redo in both orders, source extras surviving delete undocargo fmtandcargo clippy --all-targetsclean; pre-commit hooks (check/fmt/clippy/test) passed per commit🤖 Generated with Claude Code