Why we rejected the export-and-fork model for visual UI editing - CrossUI Studio — Symmetric Visual IDE
Author
User<br>linb<br>Posts by this author<br>Posts by this author
~10 min read · Engineering
There are roughly three honest ways a visual editing tool can relate to your codebase.
The first is export and fork : you design in the tool, it generates code, you paste it into your project, and from that moment forward the tool and your repo are strangers. This is how most AI code generators (v0, Bolt, Lovable) work — and for greenfield components, it's fine. You generate, you customize, you ship.
The second is SDK ownership : the tool becomes a runtime dependency. Your components are stored in a proprietary model, rendered via the tool's SDK, and deployed through the tool's infrastructure. Leave the tool, and you're migrating a schema, not just moving files. This is roughly how Plasmic and Builder.io work — and again, for the right use case (marketing pages, designer-editable CMS content), it makes sense.
The third — the one we bet CrossUI Studio on — doesn't have a clean industry name yet. We call it Symmetric Collaborative Development (SCD) : your React source code is the single source of truth, and the visual canvas is a peer editor of that source. Every visual change is written back to your file as a precise AST patch. No export step. No SDK. No lock-in. Stop using the tool tomorrow and nothing breaks.
This post explains why we think the third model is the right one for senior engineers maintaining real codebases — and what it actually takes to build it.
TL;DR. If your existing repo is the source of truth and you want a visual layer over it, the editor has to write back through the AST — atomically, one property at a time — or it will lose against your code reviewer. Everything else is a different product for a different team.
The git diff test
Here's the simplest way to evaluate any visual editor. Three steps:
Open the tool with a real MUI component already in your codebase.
Change variant="text" to variant="contained" on a button.
Run git diff.
Every "design-to-code" tool I evaluated over the last five years failed this test, in one of two ways. Either the file was re-emitted entirely — comments stripped, formatting normalized, imports reshuffled, hand-tuned useMemo blocks subtly rearranged — or the tool refused to touch existing files at all and emitted a parallel "design document" we then had to glue back into the project.
Neither was acceptable for a team that takes diff hygiene seriously. We rejected three tools in a row on this basis.
The third time, I decided the category itself was wrong.
Why "re-emit the file" is such a hard habit to break
If you read how most visual editors are architected, the root assumption is: the tool owns the rendering model . The tool maintains its own representation of the component tree — either in a proprietary schema or in an in-memory tree — and "saving" means serializing that representation back to JSX.
Serialization is lossy by nature. The tool's model doesn't know about your comment. It doesn't know why you named a variable isSaving. It doesn't know that your /* Main content area */ block comment is something a human will read in code review. So when it writes back, it writes what it knows — the component structure, the props, the styles — and silently discards everything it doesn't.
The result is a visually identical file that is a conceptually different file. Same output, different authorship. If you're a senior engineer who cares about your codebase, that's enough reason to never use the tool again.
The only way to avoid this is to not use serialization at all. Which means working at the AST level from the start — and the AST is hard.
What AST-level sync actually requires
Abstract Syntax Trees are how JavaScript parsers represent code internally. Every identifier, every JSX attribute, every function call has a position in the tree. If you want to change variant="outlined" to variant="contained", you don't re-serialize the whole file — you find the JSXAttribute node whose name is variant, update its StringLiteral value, and write back only the characters that changed.
This is what CrossUI Studio's engine does. The practical consequences:
Formatting is preserved. We don't pass the file through Prettier on every visual edit (though we do run Prettier on deliberate code saves). The unchanged bytes don't move.
Comments survive. AST nodes have attached comment ranges. We track them and leave them where they are.
Business logic is untouched. The engine identifies which AST node corresponds to the visual change and patches only that node. Your custom hooks, your useCallback wrappers, your conditional rendering logic above and below the component — we don't touch any of it.
The undo stack is unified. Whether you typed in the code editor, dragged a component on the canvas, or changed a value in the inspector, every operation is a reversible AST...