Skip to content

fix: designspace saves preserve project structure with atomic writes (re-target)#124

Merged
kostyafarber merged 4 commits into
mainfrom
fix/designspace-save
Jul 3, 2026
Merged

fix: designspace saves preserve project structure with atomic writes (re-target)#124
kostyafarber merged 4 commits into
mainfrom
fix/designspace-save

Conversation

@kostyafarber

Copy link
Copy Markdown
Collaborator

Re-targeted copy of #123, which was accidentally merged into its stacked base branch (fix/ufo-preservation) after that base had already merged to main via #119 — so its content never reached main. Same four commits, now against main directly (they apply cleanly: the branch is based on #119's commits, which are in main verbatim).

See #123 for the full description: multi-UFO write-back using stored source filenames, atomic .designspace XML via the shared write_file_atomic helper, data-before-pointer ordering with documented mid-save failure state, SourceRole master/layer distinction persisted through SQLite and .shift, and the multi-UFO round-trip / atomicity / no-phantom-master test suite.

cargo test --workspace: 311 passed, 0 failed at branch tip; clippy zero warnings.

Known remaining reader-side gap tracked in #122.

🤖 Generated with Claude Code

Source now carries a role (Master vs Layer) and an optional UFO layer
binding. The UFO reader marks synthesized non-default-layer sources as
layer-only, the designspace reader records each <source layer="...">
binding, and the UFO writer returns layer-bound data to the layer it
came from instead of the source name.
…writes

The designspace writer previously collapsed every project into a single
{stem}.ufo with all non-default sources as layers, restructuring
multi-UFO projects and leaving the original master UFOs stale on disk.
Sources are now grouped by their stored filename and written back to
the UFO files they came from, mirroring the declared layer shape;
masters created inside Shift get deterministic collision-safe
filenames. Layer-only sources are written as UFO layers without
gaining <source> entries.

The designspace XML itself now goes through a shared crash-safe helper
(temp file + fsync + rename + platform-gated parent fsync, extracted
from the UFO writer) instead of a direct overwrite, and is always
written after the UFO data so the pointer file never references
half-written masters.
…otion

Covers the multi-UFO round trip (same UFO filenames, source-to-file-
to-layer assignment, locations, and geometry after reopen), that a
failed save leaves the existing designspace XML intact, and that plain
UFO extra layers stay UFO layers instead of becoming designspace
masters, with and without axes.
The master-vs-layer distinction and designspace layer bindings now
survive both persistence layers: the SQLite store gains a layer_name
column and writes the real source kind instead of hardcoding 'master',
and .shift's sources.json carries role and layerName. Both default to
Master/absent so existing data reads unchanged.
@kostyafarber kostyafarber merged commit 8e28257 into main Jul 3, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant