Over the past three weeks, we developed and launched one of our most technically challenging and exciting features to date: Visual Edits. At Lovable, our AI-powered full-stack app-building platform already enables users to generate fully functional applications through simple prompts. However, we recognized that developers and designers often require more precise control over the final details of their applications.
To address this need, we created Visual Edits: an intuitive, Figma-like visual editor that empowers all users—regardless of their frontend expertise—to visually select, edit, and save instant frontend changes directly in their applications.
This post explores the engineering behind Visual Edits, the technical challenges we overcame, and why solving complex frontend UX problems is crucial to our mission of transforming how applications are built.
Problem 1: Tightening the Loop from Idea to Implementation
Traditional UI development cycles follow a cumbersome process: tweaking CSS, saving changes, waiting for rebuilds, refreshing browsers, noticing mistakes, and adjusting again. This iteration cycle is slow, frustrating, and error-prone. While AI generation excels at rapid initial development, polishing frontend elements requires precise iteration with:
- Instant visual feedback
- Reliable application of consistent styles
- Clean, maintainable code with minimal AI hallucination during final refinements
Problem 2: AI is still (relatively) expensive
Despite decreasing AI costs, they remain a significant factor—especially when providing context on an entire application for small changes.
Each regeneration costs both time and computational resources, affecting both our infrastructure expenses and users' waiting time. Visual Edits significantly reduces these costs by enabling precise, targeted changes without requiring AI intervention.
This approach makes creating high-quality products more cost-effective and precise.
Our Approach: Bringing the Editor into the Browser
We decided to move part of the code layer closer to the visualization layer within the browser, creating a WYSIWYG editor. Unlike traditional WYSIWYG tools that abstract away code entirely, our approach maintains a direct, bi-directional connection between visual edits and the underlying source code. Here's how it works:

1. Stable JSX Tagging & Instant Cloud Dev Servers
When starting a Lovable project, an ephemeral development server spins up instantly in the cloud. This infrastructure deserves its own detailed explanation, but essentially, we continuously host over 4,000 instances on fly.io to serve Lovable projects. At compile-time, each JSX component generated by our AI receives a unique, stable ID through our custom Vite plugin. These stable IDs persist across visual changes.
Our infrastructure orchestrates these containers in clusters across multiple regions, with each container running an isolated Node.js environment containing a complete copy of the application code. This approach enables horizontal scaling based on demand while maintaining consistent performance regardless of project complexity or user location.
When users visually select any DOM element in their application, we instantly trace it back to the exact JSX responsible for rendering it. This ensures precise bi-directional mapping:
- Visual to code: Clicking an element → Matched to the reliable JSX location
- Code to visual: JSX changes → Immediately reflected changes in the UI
2. Client-Side AST and Tailwind Generation
We synchronize the project's code entirely into the browser, representing it as an Abstract Syntax Tree (AST)—an interactive, live data structure reflecting the entire application structure. Babel and SWC provide excellent libraries for this purpose. Having the AST client-side unlocks numerous capabilities:
- Safely making declarative changes to the source code
- Propagating changes to the DOM "optimistically" in real-time
- Eliminating network roundtrips during iteration
While theoretically possible to rely on regular expressions for this functionality (which somewhat resembles how AST parsers work internally), having the actual AST available allows for far more robust and maintainable code.
For example, consider updating a component's styling. Without AST parsing, we might resort to potentially dangerous regex replacements:
// Dangerous approach without AST
function updateComponentStyles(fileContent, componentName, newStyle) {
const regex = new RegExp(
`<${componentName}[^>]*className="([^"]*)"[^>]*>`,
"g",
);
return fileContent.replace(regex, (match, className) => {
return match.replace(className, newStyle);
});
}
This approach fails with nested components, complex JSX, or when components use dynamic expressions. With AST parsing, we can safely traverse the component tree and make precise modifications:
export function updateComponentStyles(ast, componentName, newStyle) {
traverse(ast, {
JSXElement(path) {
const element = path.node.openingElement;
if (element.name.name === componentName) {
const classAttr = element.attributes.find(
(attr) =>
attr.type === "JSXAttribute" && attr.name.name === "className",
);
if (classAttr && classAttr.value.type === "StringLiteral") {
classAttr.value.value = newStyle;
}
}
},
});
return generate(ast).code;
}
AST parsing also simplifies otherwise complex tasks, like extracting a JSX tree of all components:
export function toJSXTree(ast: t.File) {
const tree: TreeNode[] = [];
const elems = new Map<t.Node, TreeNode>();
// Traverse the AST and build a tree of JSX elements
traverse(ast, {
JSXElement: (path) => {
const node: TreeNode = {
type: path.node.openingElement.name.name,
children: [],
};
elems.set(path.node as t.Node, node);
let parent: typeof path.parentPath | null = path.parentPath;
while (parent) {
if (parent.node && elems.has(parent.node as t.Node)) {
elems.get(parent.node as t.Node)?.children.push(node);
break;
}
parent = parent.parentPath;
}
if (!parent) tree.push(node);
},
});
return tree;
}
When users visually adjust a component—for example, by modifying Tailwind classes—these changes are optimistically applied by a client-side Tailwind generator that intelligently reads custom configurations. Even before saving, visual edits are previewed exactly as they'll appear post-save, thanks to client-side AST mutations and instant Tailwind parsing.

3. Hot Module Replacement
Thanks to Vite and our persistent Dev Servers, we can immediately trigger a Hot Module Replacement (HMR), refreshing the preview without requiring an explicit page reload. Upon saving changes, a chain of events executes instantly:
- Clean, standard-compliant JSX/TSX is generated from the modified AST
- Diffs are computed to update only precisely modified lines
- Changes are securely pushed to the cloud-hosted environment
- An HMR event is immediately triggered back to the user's session
This round-trip, from intuitive visual interaction to production-grade code, happens seamlessly in seconds—enabling developers and designers to iterate at unprecedented speeds without sacrificing code quality.

Why This Matters
Accelerating the iteration cycle isn't merely about convenience. We've experienced firsthand how it transforms product development:
- Higher quality: When small edits become effortless and instant, users are more likely to experiment, refine, and polish their UI until it achieves perfection.
- Reduced cognitive load: Eliminating the need to mentally simulate how CSS or component states might behave frees up cognitive resources for more creative focus and reduces debugging time.
- Enhanced collaboration: With visual edits becoming integral to the development workflow, designers and engineers naturally collaborate more closely, enabling all team members to participate intuitively in shaping the final product.
From a technical perspective, developing Visual Edits required deep integration of complex technologies including client-side AST handling, dynamic in-browser compilation, custom code generation, rapid cloud deployments, and fine-grained React component controls. This feature represents the culmination of cutting-edge web development practices.
Future Directions
The launch of Visual Edits has opened exciting possibilities for future development:
- Real-time, persistent code editing experiences combining AI and contextual user decisions
- Instant, application-wide theming capabilities
- Integrated essential tools that eliminate the need for external services
We're enthusiastic about the potential impact these innovations will have on the overall application development experience.
Join Our Team
At Lovable, we're at the forefront of full-stack AI-assisted application development. Our team regularly tackles rewarding challenges at the intersection of AI/ML, frontend tooling, cloud scalability, and developer productivity.
If building products that fundamentally improve software creation resonates with you, we're seeking ambitious, passionate engineers to join our team.
Join us at lovable.dev/careers
Learn more about Lovable at Lovable.dev or connect with our team directly if you'd like additional information.