6 min read
A design file you can diff is harder than it sounds
Why readable FNX was not enough: Fanta's 25,311-line diff, overwritten source edits, and the move to project files as the authority.
On this page
Fanta build log · Part 2 of 4 · 9 and 23 September milestones
Our pitch for Fanta is simple: a canvas edit should be a reviewable source change. In a 9 September release-build rehearsal, we created one rectangle after importing a 9.6 MB .fig file and got a Git diff with 25,311 insertions and 25,310 deletions. The rectangle was there. Almost every other line had moved too.
That was the moment “the format is text” stopped sounding like a solution. A text file is useful only if an ordinary edit stays local, a hand edit survives the next save, and two writers cannot quietly destroy each other's work. We had to fix the diff, then revisit which representation owned the project.
Run / Deploy / Read
Pick the path that matches your hardware and patience
Part 1 explains why we put a native design canvas inside a code-editor fork. This part is what happened when we held the source half of that idea to the standard of a real developer workflow.
One rectangle, fifty thousand changed lines
Fanta stores a project as a directory. A manifest identifies the project, each page has a .fnx source file, components have their own source, and binary assets live outside the text. FNX is a deliberately restricted, static JSX-like language. A scene node is an element; its properties are literal attributes. We did not want arbitrary JavaScript execution to decide what a canvas means.
The first edit after importing that .fig in our 9 September rehearsal exposed a subtle numeric bug. The import path and the save path printed floating-point numbers that were visually equivalent but differed by one bit after parsing. A design page with thousands of numeric properties came back with thousands of textual differences. The fix was to enable serde_json's float_roundtrip behavior in both the FNX and project-format crates. We also stopped putting a fresh timestamp in fanta.json on every save.
After those changes, the same kind of MCP-driven rectangle creation produced a much better result: three changed files, nine insertions and one deletion. The page source gained one line like this:
+ <Rect name="smoke-mcp rectangle" x={0.0} y={0.0} width={120.0} height={80.0} />That is a shortened illustration of the actual measured diff in the release notes; the saved line includes more attributes. The other changes were page metadata and the identity/order sidecar. A fast reader of the Git diff could now understand the edit. The project writer also fingerprints pages and components so it can reuse the bytes of untouched designs instead of reprinting the whole project.
We had solved diff noise. We had not solved ownership.
The bug underneath the good diff
On 23 September, we audited the file path with a more adversarial question: what happens if somebody edits the source while Fanta is open? The audit found several ways the promise could fail:
- A stale in-memory document could replace a hand-edited FNX page on save.
- The file watcher dropped events during a save and its cooldown period; a later canvas save could then overwrite an external edit that was never reconciled.
- Reprinting a page discarded source comments, whitespace and wrapper text.
- A hand-written file next to generated page files could be pruned because the writer treated everything in that directory as its own output.
- Replacing an asset with different bytes of the same length could escape the old length-only check.
One of these would be enough to make a code-backed editor untrustworthy. Together they showed the architectural problem. We had been treating the live Doc as the authority and generating files from it. But the advertised workflow gave an external editor, a Git branch switch and an agent permission to change those same files. Two authorities will disagree eventually. The file should win, and the canvas should be an editing and rendering projection of it.
Moving the authority to disk
The 23 September project-file pivot made the rule explicit in the current project schema: the project files are the persisted design. Canvas operations patch source; external changes enter through a watcher and reconciliation path. That decision reached beyond the printer.
Identity became explicit. Each FNX element has a stable id. A user can rename a page folder without changing the page's identity, and a hand-added node should not get a different identity merely because the same project was checked out elsewhere. Older projects still carry .ids.json sidecars for compatibility and sibling order.
A canvas edit patches a source span. Fanta keeps the parsed source around so a property change can leave unrelated comments and formatting alone. If the source contains unsupported syntax or a parse error, the editor keeps the last valid canvas visible and reports the problem instead of regenerating over the file.
Writes check what they replace. Before committing a canvas patch, the session compares the on-disk hash with the version it last observed. A clean external edit can be loaded. If both sides changed, non-overlapping edits can be merged; an overlap is shown as a conflict. A one-second autosave timer still exists, but a timer is a scheduling device, not a conflict policy.
Assets get real identity checks. New asset IDs derive from a SHA-256 digest of the bytes, while assets/index.json holds the full digest and size. Identical bytes deduplicate. A same-length substitution no longer passes as the same asset.
Multi-file changes have a recovery path. Individual changed files use temporary files and atomic renames. A transaction journal records a multi-file save so reopening after an interruption can replay it when the expected hashes still match. If the hashes do not match, the app reports a recovery problem instead of guessing which version to keep. Derived previews do not become the source of truth.
The result is still a folder you can inspect:
fanta.json
pages/home/page.fnx
pages/home/page.ids.json
components/button/master.fnx
doc/variables.json
assets/index.json
assets/images/...Those names show where a change belongs. A reviewer can inspect the page, the variable file or an asset addition independently. Git supplies history when the project is in a repository. In the non-store workflow Fanta can initialize Git for a new project; the Mac App Store sandbox has different process restrictions, so we do not promise that exact auto-init behavior there.
The small remaining print
“Files are authoritative” is a contract, not magic. The code pane can now edit FNX and validate it before updating the canvas, but it is still a restricted language, not a React runtime. Project-contained font registration, ordinary SVG/video paste and some editable SVG features remain format work. Saves still scan and hash the project inventory for integrity, which costs time on very large projects. There is also a narrow file-system race between a hash check and rename because portable file APIs do not provide one atomic compare-and-rename operation. The journal and the next watcher event are designed to recover or report that divergence, not pretend it cannot occur.
I would rather publish those limits than make a prettier claim about “design as code.” The useful milestone is concrete: a small canvas edit can be a small diff, a source edit has a route back into the canvas, and conflicts have an explicit path. The next build log will follow the same idea into a 40,000-node file, where correctness is only half of the problem. The final entry will follow it into a Mac App Store sandbox and RevenueCat purchase flow.
If you have merged visual changes through Git, tell us where the review breaks down: giant diffs, unstable layer IDs, assets, or the moment a tool rewrites your careful formatting. We want those failure cases while the project is still being built in public. #Shipaton #BuildInPublic
Comments
Tags in this post
Keep reading
Why we built Fanta on top of a code editor
How our 2024 browser-canvas experiments and 2026 native editor fork shaped Fanta's readable design files, canvas, and agent workflow.
7 min · Sep 29, 2026
Carnegie Hall, rebuilt from a seating chart
How I turned a flat ticketing map into a complete Stern Auditorium: 2,758 seats, four tiers of gilded parapets, vaulted ceilings and hundreds of real light fixtures. I did it by building tools instead of sculpting.
33 min · Sep 16, 2026
Parsing documents without uploading them
A procurement pipeline that never leaves the tab: PDF, DOCX and XLSX to editable Markdown, PP-OCRv6 on ONNX Runtime Web inside a module worker, and the canvas shims nobody warns you about.
12 min · Jul 7, 2026
All tags
- #ai
- #blender
- #build-in-public
- #cubemap
- #design-tools
- #diamond
- #environment-art
- #expo
- #fanta
- #file-formats
- #git
- #graphics
- #houdini
- #huggingface
- #image-generation
- #licensing
- #mdx
- #meta
- #next.js
- #ocr
- #onnx
- #open-source
- #procedural
- #python
- #r3f
- #ray-tracing
- #react-native
- #rendering
- #replicate
- #rust
- #sam2
- #segmentation
- #shaders
- #shipaton
- #technical-art
- #three.js
- #vercel
- #wasm
- #web-worker
- #webgl
- #webgpu