Editor guide
What refuses, and why
The Editor declines edits it cannot make correctly rather than guessing. Every refusal you are likely to meet, what causes it, and what to do instead.
Most of the Editor's edits are made by rewriting the exact spot in your source file that produced the thing you clicked (no model involved). That is what makes them fast and trustworthy. It also means there is a hard boundary around what can be done that way, and when an edit falls outside it, the Editor stops and tells you.
A refusal here is not a missing feature report. It is the tool declining to guess about your code. The alternative to a refusal is not a working edit. It is a change that compiles, looks plausible, and quietly does the wrong thing. That failure mode is much more expensive to find later, especially in a repo other people read.
Refusals appear as a toast in the bottom-left corner. Ones worded as a failure also open a Save failed dialog with the full reason.
Structural edits#
Moving an element into another file#
What you see. Dragging in the Layers panel or in the prototype, the drop is rejected with "Can't drag-move across files (the drop landed in another component's source)", or a save fails with "Cross-file moves are not supported in V1".
Why. Moving an element between two components is not a relocation of text: the element usually depends on data and handlers that exist in the source component and do not exist in the destination. Doing it properly means creating new props, wiring them through, and possibly adding imports. A text move would produce a component referencing variables it does not have.
This is easy to hit without realising it: dropping into what looks like the same page often lands inside a slot rendered by a different component's source file.
Instead. Ask chat: it can read both files, add the wiring and move the markup. Or move it by hand.
Dragging an element that repeats#
What you see. "Drag-move involving a repeated (v-for) element isn't supported yet. Use the Layers panel to move it with a scope choice."
Why. A repeated element (a row rendered by a Vue v-for or a React .map) is one template rendered N times. There is only one place in the source to edit, so a plain move would relocate every row at once, which is almost never what dragging one row means. What you probably want is to reorder that one item in the underlying data array, which is a different edit to a different file position.
Instead. Use the Layers panel to move it. That path asks you which you mean before doing anything.
Dropping into a container that repeats#
What you see. "Can't insert into a repeated (v-for) element: pick a non-repeated container or use chat."
Why. Same reason in the other direction: inserting into the loop template adds your new element to every row.
Instead. Drop into a container outside the loop, or ask chat to add it to the loop's data instead of its markup.
Dropping onto something with no source mapping#
What you see. One of:
- "Can't drag this element: it has no source mapping (not authored in this prototype's repo, or the framework adapter didn't tag it)."
- "Can't drop here: the destination's parent has no source mapping."
- "Can't drop here: no valid parent container."
Why. The Editor knows where an element came from because your build stamps a file, line and column onto it. Markup produced entirely inside a design-system package, or by a framework the tagging plugin does not cover, has no stamp: there is no known place in your repo to write to.
Instead. Select the nearest ancestor that does belong to your repo, or describe the change in chat.
Dropping an element into itself#
What you see. "Can't drop an element into itself or one of its descendants."
Why. It would produce a cycle, which is not a tree.
Moving out of an invisible conditional or loop wrapper#
What you see. A message naming the wrapper, for example: "Cannot move <KCard> out of its enclosing <template v-if="isOpen">: it would silently stop being conditional and render always."
Why. This is the most valuable refusal in the tool, because the thing it prevents is invisible. <template v-if> and <template v-for> render no element of their own, so nothing on screen shows you that the card you are dragging is inside a condition. The card's own source range does not include the wrapper's directive, so moving the card out of it would drop the condition, and the card would start rendering unconditionally, or stop repeating, with no error anywhere.
Moving out of a visible conditional container such as <div v-if> is still allowed. You can see that container, so reparenting out of it is a normal edit you meant to make.
Instead. Move the whole <template v-if> block, or ask chat to restructure it.
Deleting the last thing on the page#
What you see. "Refusing to delete the template's only rendered content: would leave an empty <template>."
Why. An empty component template is not a valid component.
Unwrapping the wrong thing#
Unwrap removes a wrapper element and hoists its children into its place. It refuses in four cases:
| Case | Reason |
|---|---|
| Self-closing element | Nothing to hoist. Use Delete. |
| No rendered children | Nothing to hoist. Use Delete. |
The wrapper carries a v-if / v-for directive |
Removing the wrapper would silently drop the condition or the loop. Use Flatten conditional… from the element's right-click menu in the Layers panel, or Delete. |
| It is the only root and has multiple children | Vue requires a component to have a single root. |
Inserting something that is not one element#
Insert refuses an empty payload, an insert into a self-closing element such as <img />, a snippet with no root element, and a snippet with several sibling roots ("Insert snippet must be a SINGLE root element. Wrap multiple siblings in one parent element first."). It also refuses plain text containing {{ or }}: Vue interpolation is an expression, not text, and quietly escaping it would be wrong in both directions.
Text and prop edits#
"This item" on repeated text#
What you see. Editing text on one item of a repeated list opens a dialog that asks "Change the text on this item or all items?" Its description says "This is item 3 of 8, rendered by a loop, and they all come from the same code." Both tiles work: This item patches just that item's data entry. All items rewrites the shared template, the same as it always has.
Why it can still refuse. Patching one item's text means finding which field on that item's data the text came from. Editor can only work that out for one shape: a direct interpolation like {{ step.label }}, sitting right inside the element.
Text inside a wrapper element, a plain literal, a computed expression like {{ step.label.toUpperCase() }}, and chained access like {{ step.title.text }} all refuse this way.
When it refuses, you see "Iteration edit refused:" followed by the reason, and nothing is queued for you automatically. Unlike a bound-prop refusal, this one does not open the AI lane on its own.
Instead. Choose All items if the change belongs in the shared template, or ask chat to change the one data entry.
Loops the deterministic lane cannot follow#
What you see. "Iteration edit refused:" followed by a reason mentioning the loop expression.
Why. To edit one row's data, the Editor has to find the array literal behind the loop. It refuses when it cannot do that unambiguously:
- The loop iterates a property path such as
v-for="item in group.items"rather than a bare name. Walking intogroupand picking the first array it finds would pick the wrong one when the object holds several. - The array is not declared in the same component's script: it comes in as a prop or from a store.
Instead. Ask chat, which can trace where the data actually lives.
Bound values, v-model, and dynamic v-bind#
This one is not really a refusal: it is a handoff, and it is worth knowing so you do not read it as failure.
When a prop is written :label="title" rather than label="Save", or the element carries v-model or a v-bind spread, the deterministic applicator refuses with a typed hint, and that hint is what opens the AI lane. The edit gets queued, the top bar shows Apply N with AI, and a short focused agent turn traces the binding to where the value is really defined. See deterministic editing.
The React equivalent of a composed className={cn(...)} or style={base} does not get that lane. It refuses outright with "The class/style is dynamically composed, so adjust it via chat", because there is no safe splice and the binding-tracing pass does not fit a class composition.
Ambiguity#
An ambiguous selector#
What you see. "The clicked element's selector matches multiple elements within <KCard>'s mount root, so this hint may be stale; refusing rather than risk editing the wrong instance."
Why. To edit inside a design-system component, the Editor uses a rendering hint, a recorded mapping from "this prop" to "this element in the rendered output" (see glossary). If the element you clicked answers to a selector that matches more than one element inside that component, the hint cannot say which one you meant. Usually this means the hint was recorded against a version of the component whose markup has since changed.
Instead. Select a more specific element, or ask chat. If it happens repeatedly on a component you own, the fix is a more specific rendering hint. See design-system grounding.
No hint matched at all#
What you see. "No rendering hint matched the clicked element inside <KCard>. The element may be rendered by library-internal markup."
Why. You clicked something the design-system package draws internally, which no prop of yours controls. There is nothing in your repo to edit.
Explicitly marked uneditable#
Some manifests mark a surface uneditable on purpose (an icon a component owns, a structural wrapper). The refusal carries the reason the manifest author wrote.
Several edits landing on the same loop position#
What you see. Cannot save: 2 edits still need a scope choice. Resolve the "Resolve ambiguous edit" dialog, or dismiss it to discard, before saving.
Why. Two edits pointing at the same template position but different rendered items cannot both be applied without knowing which item each meant.
Files the Editor will not write#
Design tokens inside node_modules#
What you see. "Can't edit this token: it's defined in <package>. Set your own value in one of your project's stylesheets instead."
Why. node_modules is not your code. It is regenerated by the next npm install, so any edit there is silently lost, and it is not in git, so nobody reviewing your branch would ever see the change. The Editor checks the resolved path after following symlinks, so a symlink pointing into a package is caught too.
Instead. Redefine the token in your own stylesheet. A design system's tokens are CSS custom properties, and yours will win if it is more specific or loaded later.
Paths outside the prototype#
Every edit target is resolved against the prototype root (before and again after following symlinks), and the file extension is checked at both points. A target outside the root, or of a kind that lane does not handle, is refused.
JSX inside a Vue component#
Vue lets you write JSX inside <script setup lang="tsx"> instead of a <template> block. The Editor supports that, with one exception, and the exception is worth understanding because it is not arbitrary.
These work on JSX inside a Vue script block: editing a prop, deleting an element, unwrapping an element, and inserting an element.
Move does not. You see: "Moving elements into or out of a <script setup lang="tsx"> block isn't supported: the template and the script block are different languages. Move it by editing the source directly."
Why. Move is the only edit with two coordinates (where the element is, and where it should go), and in a file like this those two coordinates can land in different blocks. A JSX element cannot become a child of a Vue template node, and vice versa: they are different languages compiled by different pipelines. Doing "something reasonable" (moving within one block and ignoring the other coordinate) would put your element somewhere you never pointed at. The refusal fires when either end of the move is in a JSX block; a move entirely within the template is unaffected.
Instead. Move it in your code editor.
A related one: group moves (relocating a whole v-if / v-else-if / v-else chain as a unit) are Vue-template only. On a React target you get "Group moves are not supported for React targets yet".
The safety checks around every write#
These are not about your code's shape; they are the Editor refusing to write when it is no longer sure of its ground.
Stale target. "The prototype re-rendered from newer source than these edits were captured against. Re-apply the edit against the current view." Your click carried both a source position and a fingerprint of the file at that moment. If the file changed since (you saved in your code editor, a chat turn wrote it, hot reload rebuilt it), the position may no longer point where you think. Re-select the element and redo it.
External-edit conflict. The file changed under the Editor between when it read it and when it went to write. The save fails with recovery options rather than overwriting.
Backup could not be written. "Edit aborted; no source files modified." Every write copies the file's previous contents into .desde/backups/ first. If that copy fails, the edit does not happen: the Editor will not make a change it cannot take back.
The result would not parse. After splicing, the patched source is re-parsed with the same compiler your build uses. If the result is not valid, it is thrown away and nothing is written.
The edit would change nothing. An edit whose output matches the original byte for byte is refused rather than reported as saved. This exists so the Editor can never tell you it saved something over a file it did not touch.
Substrate limits#
Distinct from the refusals above: these are things the Editor does not support at all today.
- Vue 3 and React only: those are the two languages the Editor can stamp and edit. The dev server underneath them may be plain Vite, Nuxt, React Router or Next.js, each of which the Editor starts for you. Astro works too but is off by default and stamps islands only; see attach mode. There is no support for SvelteKit, Svelte, Angular, webpack or Create React App.
- One person, one machine. The Editor is a local CLI with no multi-user mode. Sharing work with other people goes through git, or through the Viewer for comments.
- The canvas and screenshot-plan surface is off by default.
- Component detach and component swap are off by default, and so is the icon picker, which rides the same lane. Both applicators are Vue-only with no React equivalent, and were unused on Vue, so the controls were withdrawn rather than maintained.
"lanes": { "detach": true, "swap": true }indesde.config.jsonrestores them, and nothing was deleted. See Swap, Detach and the icon picker. - Vue 2 is refused outright, and is not on the roadmap.
- There is no install from npm. Every package in the repository is marked private, so there is no
npm install -g. See the Editor quickstart for the paths that do exist. - Editor and Viewer cannot run against one prototype at the same time. Editor supervises a live dev server and writes to your working tree. Viewer serves a frozen build. See what is Desde.
How to read a refusal#
Three questions, in order:
- Is it telling me the edit is ambiguous? ("matches multiple elements", "needs a scope choice"): make the target more specific and try again.
- Is it telling me the edit would be silently wrong? ("would stop being conditional", "affects every row"): you are being shown something you could not see. Read it before working around it.
- Is it telling me this lives somewhere else? (
node_modules, another file, library-internal markup): the change has a correct home; the Editor is just declining to write to the wrong one.
In all three cases chat is the escape hatch, and it is not a lesser option: it is the lane built for edits that need reading and reasoning rather than a splice.
Next#
- Deterministic editing: why the boundary exists
- Chat: the lane refusals hand off to
- Glossary