Editor guide
Attach mode
Run the Editor against a dev server it does not own. When you need it, what you add to your own config, and why the stamper must never reach a production build.
By default the Editor starts your dev server for you. As of 2026-08-11 that is true for Vue 3 or React on plain Vite, and for Nuxt, React Router and Next.js: you run desde . and it boots the right dev server itself, in its own process, with no configuration from you at all.
Attach mode is the other arrangement. You start your dev server exactly as you always do, and the Editor attaches to the running one. It never loads your config, never owns the process, and never decides how your app is built.
That used to be the only way to use a meta-framework with the Editor. It is not any more, so the reason to reach for it has narrowed to three:
- Astro, which does not boot in-process by default. See which framework takes which path.
- You want your own dev server. Any host can be turned off with
{"hosts": {"next": false}}indesde.config.json, which sends that framework back to this page. - Something about your setup the Editor cannot own: a dev server behind a custom script, a container, or a remote machine.
The cost of not owning the dev server is that the Editor cannot inject the one thing it genuinely needs. In attach mode you add that to your own config, once. In-process boots need none of it: the same stamper is injected in memory, and nothing is written to your repo or your config.
Which framework takes which path#
| Your project | How it runs by default | What you add to your config |
|---|---|---|
| Vue 3 or React on plain Vite | The Editor boots Vite for you | Nothing |
| Nuxt | The Editor boots Nuxt for you, in-process | Nothing |
| React Router v7 / v8 | The Editor boots it for you, in-process | Nothing |
| Next.js (App Router, Turbopack) | The Editor boots it for you, in-process behind a proxy | Nothing |
| Astro | Attach mode, or in-process with {"hosts": {"astro": true}} |
The stamper in vite.plugins, for attach (islands only) |
| Svelte, SvelteKit, Angular | Not supported | N/A |
Svelte and Angular are not a matter of configuration: there is no source stamper for either, so there is nothing to add. They are out of scope, not merely unwired.
Astro is held back deliberately, not left un-done. Its boot passes every check the other three pass; what is missing is downstream, and it is a split no user could form a mental model of: markup written directly in a .astro page is inspect-only, while components in the .jsx, .tsx or .vue islands on that same page edit normally. Turning it on is one line, and the gap is exactly the one described below.
What you add for attach mode#
Everything from here down applies when you are attaching to a dev server you started. If the Editor is booting your dev server for you, you can stop reading. None of it is needed.
Why you have to edit your own config#
Everything the Editor does with your source starts from one attribute. Your build stamps data-desde-src="app/routes/home.tsx:24:12" onto each element it renders, and that is how a click in the running app becomes a known file, line and column. Without it the prototype is inspect-only: you can select and read elements, and every edit is refused, because there is no place in your repo the Editor is willing to guess at.
In the default arrangement the Editor adds that stamper itself, at boot, in memory. In attach mode your dev server is the only thing that reads your config, so the plugin has to be in the config you wrote.
That makes stamping a real onboarding step, and a skipped onboarding step is the worst failure this tool has: the app boots, everything looks right, and you only find out when the first edit is refused. So the CLI checks before it starts anything:
- if your config already references the stamper, it says so and continues;
- if it does not, it prints the exact block to paste, the file to paste it into, and stops.
It also tells you when a config is wired incorrectly in one of the two ways that are otherwise invisible: a Next loader registered for *.tsx but not *.jsx, and a loader with no production gate.
The steps#
-
Start your own dev server, however you normally do it:
next dev,nuxt dev,react-router dev,astro dev. -
Attach the Editor to it, passing the URL it is serving on:
node editor-cli/bin/desde.mjs ~/code/my-prototype --attach http://localhost:3000 -
Read the preflight. If it printed a block, paste it into the named file.
-
Restart your dev server. Config files are not hot-reloaded: this is the step people skip, and it looks exactly like the block not working.
-
Run the attach command again. It should now report the config as already wired.
What the block actually does#
The Vite frameworks: Nuxt, Astro, React Router#
One import and one array entry:
import desdeSourceTag from './.desde/stamp/vue-source-tag.mjs'
export default defineNuxtConfig({
vite: {
plugins: [desdeSourceTag()],
},
})React Router and plain Vite take the same plugin in the root vite.config's own plugins array; Astro takes it in vite.plugins inside astro.config. Position in the array does not matter: the plugin declares enforce: 'pre', so it runs before your framework's plugins whatever order you list it in.
The plugin also declares apply: 'serve', which is what keeps it out of your production build. See below.
Next.js#
Next needs two things, and both are in the printed block.
A Turbopack loader, registered for *.tsx and *.jsx separately. A rule for *.tsx alone leaves every .jsx file unstamped, and nothing announces it: the page renders, the elements are selectable, and only the edit is refused. Measured on a real Next 16 app: with both rules, a plain .jsx page is stamped; with the *.jsx rule removed, the same page has zero stamps while the .tsx pages keep all 630 of theirs.
An allowedDevOrigins entry. Next 16's dev server answers 403 to any /_next/* request that carries an Origin header from a host it does not recognise, and attach mode serves your app through a proxy so the browser is on a different origin. The entry has to be a bare hostname: Next parses the incoming Origin and compares only its hostname, so an entry with a port in it matches nothing. Measured: with allowedDevOrigins: ['proto.local'] the request returns 200; change that one entry to 'proto.local:7436' and the identical request returns 403.
Never ship the stamper to production#
This is the part to read even if you skim everything else.
Your config file is committed. That means the stamper you add for the Editor is also read by next build, nuxt build and react-router build, and an unguarded one writes data-desde-src="src/app/(main)/dashboard/page.tsx:41:8" into the HTML and JavaScript you serve to real users. Internal file paths, directory structure and line numbers, on your public site.
That is not hypothetical. Measured on a real Next 16 app: with the gate removed, one next build put 14,075 stamps into the server output and 4,179 into the client chunks.
The generated blocks close this for you, and the two lanes close it differently.
- Next gates on the phase argument Next passes to a function-form config, not on
NODE_ENV.NODE_ENVis ambient: a CI runner that exportsNODE_ENV=developmentwould re-enable stamping in a production build, and nothing in the output would look wrong. The phase comes from Next itself and cannot be set from the environment. Verified: the same config yields 630 stamps on one page undernext devand 0 in.next/serverand.next/staticafternext build. - The Vite frameworks rely on the plugin's own
apply: 'serve', which Vite honours in both dev and build. Verified:react-router buildandnuxt buildboth complete with 0 stamps anywhere in their output, while the same config stamps normally in dev.
If you edit the generated block, do not "simplify" either gate away. If you wire a stamper by hand instead, the preflight will warn you when it cannot find a gate, but it can only see what is in the file: it cannot check your build for you.
What to commit#
The stamper itself is a bundle the Editor writes into your repo at .desde/stamp/. That directory is ignored locally only, through .git/info/exclude, which is not shared with anyone.
So there are two coherent choices, and one that breaks for your teammates:
- Commit both the config block and
.desde/stamp/. Everyone's dev server boots; nobody who is not using the Editor notices anything. - Commit neither. Keep the block as a local change.
- Commit the config block alone: this is the broken one. A teammate pulls a config that imports a file they do not have, and their dev server fails to start.
The preflight prints which files the block depends on, for exactly this reason.
What attach mode does not change#
- Edits still write your source files, through the same deterministic pipeline as always.
- Hot reload is your dev server's, not ours. The Editor writes a file; your dev server notices and reloads the page as it always did, and the stamps survive the hot update.
- Refusals are the same refusals. Everything in what refuses, and why applies unchanged.
Known gaps#
- Astro stamps islands only. Markup written directly in a
.astrofile has no stamper, so most of a typical Astro page stays inspect-only. Components in.jsx,.tsxor.vueislands are stamped normally. This is the reason Astro is the one framework the Editor does not boot for you by default: the split is inside a single project, and there is no way to see which half of a page you are on until an edit is refused. - One framework per project. Detection picks a single framework, so an Astro project mixing Vue and React islands stamps only one of them.
- Next.js Pages Router is untested. The App Router is what has been measured.
- Svelte and Angular have no stamper at all.
Next#
- What refuses, and why: including what an unstamped element does
- Deterministic editing: what the stamp is used for
- Chat: the lane for edits a splice cannot make