Desde

Concepts

Branch mode

How the Editor saves. Every edit writes your real source files immediately. There is no Save button, and undo is not git.

The Editor has no Save button. When you change a prop in the inspector, drag an element, or accept a change from chat, the Editor writes your actual source file on disk right then. Your dev server picks the change up and the preview updates.

This is the part of the product most likely to surprise you, so it is worth reading before you make your first edit. The short version: the Editor treats your checked-out git branch as the workspace, your uncommitted working tree as the scratchpad, and your own git as the boundary between "trying something" and "keeping it."

Every edit writes immediately#

There is no staging area, no draft copy, and no separate Editor save file. An edit goes straight to the file it came from:

  1. You change something in the UI.
  2. The Editor works out which file, line and column produced the thing you touched.
  3. It rewrites that spot in the file and writes the file to disk.
  4. Vite reloads the affected module and the preview updates.

Afterwards, git status in that repo shows an ordinary modified file. If you had your code editor open on the same file, it will prompt you to reload it.

There is one exception. Some edits cannot be made by rewriting source directly, for example changing text that comes from a computed expression rather than a literal. Those are parked in a queue and handed to the AI lane instead, and they are only written when you click Apply N with AI in the top bar. While that queue has anything in it, the top bar shows that button in place of Commit, so you cannot commit a half-applied set of changes by accident. See deterministic editing for why some edits take that route.

Warning Point the Editor at a repo whose work you are willing to have modified in place. It is editing your files, not a copy of them. Start on a branch you do not mind rewriting.

Branch mode is the only substrate#

"Branch mode" is the name for the model above: the Editor edits the working tree of whatever branch is currently checked out.

There is no other mode. An earlier design gave each editing session its own git worktree with automatic commits; that was removed. If you have EDITOR_WORKTREE_MODE or EDITOR_BRANCH_MODE set in a shell or a script, they no longer do anything: the CLI prints a warning at boot and uses branch mode anyway.

At boot the Editor adds .desde/ to .git/info/exclude in your clone, so its own scratch directory does not show up in git status. That is a local ignore. It is never committed and other people working on the repo never see it.

Three ways to go back, and they are not the same#

This is the part that catches people. The Editor gives you three different recovery mechanisms with three different scopes. They do not stack, and one cannot undo another.

Mechanism Where What it restores Scope
Undo / Redo The floating toolbar, the arrow buttons The previous state of the files one edit touched Edits made in this running Editor
Undo on an Activity row Activity panel, per entry The bytes that one recorded edit replaced One edit, written as a new change
Backup journal .desde/backups/ on disk The bytes any single write replaced Every write, recoverable by hand

Undo and Redo#

The Undo / Redo arrows sit in the floating toolbar under the header, beside the tool picker. They walk a stack of recorded edits: each entry holds the before and after bytes of every file that edit touched, and undoing rewrites those bytes back. It is not a git operation and it leaves no trace in git history.

Things worth knowing:

  • The stack lives in the running Editor process. Quit the CLI and it is gone.
  • It holds at most 50 steps, and at most 20 MB across the undo and redo stacks combined. Older steps fall off.
  • It is cleared when you switch branches, create a branch, publish, or merge and push. Those operations rewrite the working tree, so replaying an old step onto it would be wrong. Renaming a branch does not clear it. It does not touch the working tree.
  • Before restoring anything, the Editor checks that the files still look the way it expects. If you edited one of them in your own code editor in the meantime, the undo refuses rather than clobbering your work. When a step can never be applied again, the Editor offers to discard just that step so it stops blocking the ones behind it.

The Activity panel's own Undo#

The Activity panel in the right rail is one list, newest first, of every recorded edit. Each row has a menu with two items: Undo and Copy path.

This Undo is backed by the edit ledger rather than the in-process stack, so it survives quitting the CLI. It restores the bytes that edit replaced by writing them as a new change. It never rewrites history, and it never produces a revert commit.

It is offered only when the Editor can still prove the file has not moved underneath it. If the content hash has drifted, because you edited the file yourself or a later edit touched it, the row says so and Undo is not offered, rather than clobbering work it cannot account for.

Note There used to be a per-file Discard changes action here that reset a whole file to your last commit. It was removed when the panel was rebuilt around the edit ledger on 2026-08-19. To throw away a file's uncommitted changes now, use git directly: git checkout HEAD -- <path>.

Warning Discarding a file with uncommitted changes is not recoverable from the toolbar. If the change matters, commit first. Committing is cheap and you can always squash later.

The backup journal#

Underneath both of the above, every write the Editor performs copies the file's previous contents into a fresh directory under .desde/backups/ before touching the original. The layout is .desde/backups/<timestamp>-<uuid>/<path-inside-your-repo>. If that backup cannot be written, the edit is refused and nothing is modified.

This is a manual safety net, not a feature with a button. If something has gone badly wrong, you can find the previous version of a file there and copy it back yourself.

It is swept at CLI boot and again after every Commit, so it does not grow forever. A backup directory is kept only if it is among the 200 most recent and less than 14 days old; anything less than 10 minutes old is never deleted regardless. Both limits are configurable, as retention.backups.keepNewest and retention.backups.maxAgeDays in your repo's .desde/config.json.

Committing#

The top-bar Commit button is the boundary between "uncommitted mess" and "kept." It shows the number of changed files, and it is disabled when the working tree is clean. Clicking it opens a dialog for an optional message and then runs the equivalent of:

git add -A && git commit

on the currently checked-out branch. If you leave the message blank it uses a default one.

Two details that matter:

  • git add -A stages the entire working tree, not only what the Editor touched. Anything you changed by hand in the same repo goes into the same commit.
  • The commit runs with --no-verify, so your repo's pre-commit hooks do not run. A failing hook would otherwise leave your edits stranded with an error you cannot act on from inside the Editor. If you rely on hooks for formatting or linting, run them yourself before you push.

You can also commit from your own terminal at any time. The Editor is not tracking commits; it just reads git status.

Branches#

The Branch menu in the top bar is a plain git branch switcher. It shows the current branch and lets you:

  • switch to another local branch
  • create a new branch from the default branch
  • duplicate the current branch
  • rename the current branch
  • publish the current branch into the default branch

Switching and creating both refuse if you have uncommitted changes. This is deliberate. git checkout carries non-conflicting uncommitted changes across to the target branch, so a dirty switch would quietly smear one branch's work onto another. Branch mode never commits on your behalf, so instead it stops and tells you to commit or discard first.

Renaming does not touch the working tree, so it works with a dirty tree.

Publishing#

Publishing is the "make this the real version" step: it folds your branch into the default branch (usually main) as a single commit.

There are two places to do it. Publish to main in the Branch menu merges locally. That item only appears when you are on some branch other than the default one, since there is nothing to publish from the default branch into itself. The Merge / Push dropdown in the top bar offers the same thing plus the variants that involve the remote:

  • Push branch: commit anything pending, then git push origin <branch> using your own git credentials.
  • Merge & push to main: merge into the default branch locally, then push the default branch.
  • Merge to local main: merge locally only; the remote is untouched.

The dropdown's fourth item, Open pull request, opens a dialog with a title and body, and creates the PR through your own gh CLI. It is disabled in two cases, each naming its reason in the subtitle: you are on the default branch (switch to another branch first), or the repository has no origin remote.

The merge itself works like this:

  1. If the branch being published is the one you have checked out and it has uncommitted changes, those are committed onto it first. Publishing is a save boundary too.
  2. A temporary git worktree is created under .desde/, checked out on the default branch.
  3. The branch is squash-merged into the default branch inside that temporary worktree, so your own checkout and your current branch are never touched.
  4. The temporary worktree is removed, whether the merge succeeded or not.
  5. Your branch is then reset to match the freshly-published default branch. The content is identical (you just published it), and this keeps the next publish from conflicting against your own earlier squash.

You stay on your branch throughout. The default branch simply moves forward.

When a publish conflicts#

If the squash-merge hits a conflict, the publish fails. The temporary worktree is torn down and the default branch stays exactly where it was. Your branch keeps its content, but note that step 1 above already ran, so if your tree was dirty those changes are now committed onto your branch. Nothing is lost; there is simply a commit where there was not one before.

The Editor names the specific files that conflicted rather than reporting a bare failure, and the Merge / Push dropdown offers Pull remote changes and Update from main to bring your branch up to date first.

What it does not do is resolve the conflict for you. Nothing auto-merges, and there is no editor for conflict markers. You fix it in your own branch:

git merge main

Fix the conflicts, commit, then publish again.

A publish can also stop with "nothing to publish": that means the squash produced no changes, so your branch's content is already in the default branch.

Habits that make this comfortable#

  • Work on a branch, not on main. Create one from the Branch menu before you start.
  • Commit early and often. It costs nothing, it is what Discard restores to, and it is the only thing that survives a CLI restart.
  • Commit before you switch branches (you will be told to anyway).
  • If you keep a code editor open on the same repo, expect files to change under you. Reload rather than saving over the Editor's write.
  • Treat .desde/backups/ as a last resort, not as your undo.

Related: the workspace header, where these controls live · how editing works · glossary