DEV Community

Cover image for Visual editing for Astro, with no second source of truth
Maninderpreet Singh
Maninderpreet Singh

Posted on

Visual editing for Astro, with no second source of truth

Most visual editors for static sites ask for the same trade. Content moves into a CMS, layout moves into an editor-specific format, and your .astro files become a rendering target instead of the thing you edit.

I built @sudodevstudio/astro-ai to avoid that trade. It is a development-only Astro integration. You select an element on the rendered page, and the edit is written to the source file that produced it.

Why source ownership was the point

A separate authoring system is a reasonable choice for some projects. Editorial workflows and non-technical authors need an architecture built for them.

For a site already maintained through code, a second representation adds a standing question: does a manual edit survive the next visual one, and which representation wins when they disagree? I wanted visual edits and manual edits to operate on the same source files, with no separate representation to reconcile. A visual edit is an ordinary source change. It shows up in the same diff and the same pull request as an edit you typed.

That constraint works in both directions. Before the editor offers an operation on the rendered page, it has to establish that the source can express it. A good part of this post is about the operations it declines.

The 30-second setup

npm install --save-dev @sudodevstudio/astro-ai
Enter fullscreen mode Exit fullscreen mode
// astro.config.mjs
import { defineConfig } from 'astro/config';
import buildWithAI from '@sudodevstudio/astro-ai';

export default defineConfig({
  integrations: [buildWithAI({ agent: 'codex' })], // or 'claude'
});
Enter fullscreen mode Exit fullscreen mode

Start the dev server, open Build with AI in Astro's dev toolbar, and select an element. The AI provider is optional. Call buildWithAI() with no agent to get deterministic editing alone. For AI requests, authenticate the CLI first with codex login, or launch claude and complete its login flow.

Editing a heading produces this:

--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1 +1 @@
-<h1 class="hero-title">Build something useful</h1>
+<h1 class="hero-title">Build something people use</h1>
Enter fullscreen mode Exit fullscreen mode

The text changes. The class and the surrounding code stay where they were. Vite HMR renders the updated source.

How the page maps back to source

During development, the integration parses .astro files with @astrojs/compiler-rs and .jsx and .tsx files with Babel. Supported elements are instrumented with data-astro-ai-* attributes that link a rendered DOM node to a file path and known source ranges.

Deterministic edits replace specific source ranges without regenerating the file from an AST. Formatting and comments outside those ranges remain untouched.

Before an edit is committed, the command engine checks that the file still matches the source it inspected, then parses the proposed result. Those two checks reject stale selections and transformations that would introduce a syntax error.

Deterministic edits and the role of AI

Separating mechanical edits from open-ended ones turned out to matter more than anything else in the design.

Many visual edits have one correct outcome. Changing literal text, setting an existing literal prop, reordering compatible siblings, removing a node, or inserting an element requires no interpretation. The editor knows the source transformation and applies it locally. There is no model request and no token cost on that path.

AI is useful for requests that need reasoning across the implementation. "Make this section responsive" can touch structure, styling, and component boundaries together. "Explain why this renders twice" needs context the DOM does not carry. Those requests go to the Codex or Claude CLI you are already authenticated with. The agent works in a temporary project copy filtered by .gitignore and any additional excludeDirectories, and you can include project convention files:

buildWithAI({
  agent: { provider: 'claude', model: 'your-model' },
  excludeDirectories: ['vendor', 'src/generated'],
  skills: ['AGENTS.md'],
});
Enter fullscreen mode Exit fullscreen mode

Credentials stay in the CLI's credential store and are never sent to browser code.

The limits are part of the design

A rendered element is not necessarily an independent unit of source:

{items.map((item) => <a href={item.url}>Read more</a>)}
Enter fullscreen mode Exit fullscreen mode

Every link comes from one template. Editing the literal Read more text changes the template, so every link generated from it changes. The URL comes from item.url, which is a different source relationship. Selecting one rendered link does not establish whether you meant that data item, the shared template, or the component containing it. Content supplied by an expression or external data has to be edited at its actual source.

Structural edits have comparable boundaries:

  • Sibling reordering refuses to cross expressions or comments.
  • Moving into a slot requires a compatible slot declared through visualComponents, with the source and target in the same file.
  • Deterministic insertion currently accepts lowercase element names.

These boundaries reduce the range of operations available. That is the trade I chose over guessing how a visual action should change the underlying program.

One history, and nothing in production

Deterministic edits and agent-authored changes pass through the same patch-transaction store. It records file contents before and after a change and exposes readable diffs on a shared undo/redo stack, persisted in .astro/astro-ai/transactions.json. That file is local tool state for undo and recovery. The project source still defines the site.

Conflicting changes are reported with a recovery diff rather than overwriting newer source. That path matters because source can change in your code editor while the toolbar is open.

The toolbar, instrumentation, and agent bridge are enabled only in the dev server. The repository includes a production audit that checks build output for known editor markers. The credentialed agent bridge is also disabled when Astro listens beyond loopback, unless you opt in with allowNetworkAgent: true.

Where this fits

This suits Astro projects where developers own the source and want to work visually inside the same workflow. It is not a CMS. If you need editorial roles, approvals, and content operations independent of the dev server, those requirements need a different tool. The supported transformations also limit which structures can be edited deterministically today.

I would choose this approach when developers want visual editing while continuing to maintain the site through source files and Git, and when a change made on the page should be reviewable the same way as one made in the editor.

The package targets Astro 7 and Node 22.12+ and is MIT licensed. The repository has the installation guide, configuration options, and a demo video.

Top comments (0)