How midcode works
The road of one click in midcode, from the canvas to the line of code it changes, through the dev server, the marks on each element, the edit and undo.
midcode keeps no copy of your site and has no format of its own. The canvas is your dev server, running. An edit is a change to one of your files, a few characters wide. This page follows one click from the canvas to the line it changes, so you know what midcode does to your project and what it never does.
The example is a Next.js project. Other stacks take the same road with a different first step, linked along the way.
1. Your dev server, with one thing added
When you open a project, midcode starts its dev server on a free port. For Next.js that is your own next, from your node_modules, with one file preloaded:
node --require <midcode>/next-hook.cjs node_modules/next/dist/bin/next dev --port 4310 --hostname localhostThe hook wraps the function Next.js loads its config with. On the config it gets back, in memory, it adds one rule: files ending in .tsx and .jsx go through midcode’s loader first, under Turbopack or webpack, whichever your project uses. It also turns off the Next.js badge that would sit over the canvas.
Nothing is written to disk for this. Your next.config isn’t edited, and nothing is installed in the project. If a future Next.js changes shape and the hook doesn’t fit, it steps aside: the server runs as it always would, without selection.
Projects that run on Vite (Vite + React, React Router, Remix, SvelteKit, Astro) get the same thing through a config file kept in midcode’s own folder: it loads your config and adds midcode’s plugin. See React with Vite, Svelte and SvelteKit and the other pages under Frameworks.
2. Every element learns where it’s written
The loader runs on each of your JSX files as the dev server compiles them. It adds one attribute to every element: the file, line and column where that element is written.
<section className="px-6 py-16">
<h1 className="text-5xl">Built to outlast</h1>
<Card title="Oak" />
</section><section data-mc="app/page.tsx:12:5" className="px-6 py-16">
<h1 data-mc="app/page.tsx:13:7" className="text-5xl">Built to outlast</h1>
<Card data-mci="Card|app/page.tsx:14:7" title="Oak" />
</section>There are three marks:
| Mark | On | Says |
|---|---|---|
data-mc="file:line:col" | Every HTML element in your JSX | Where that element is written. |
data-mci="Name|file:line:col" | Every component instance | Which component it is, and where the instance is written. |
data-mcu | The element a component returns | Where that component is used, outermost first. |
The last one is why selecting a <section> drawn by <Hero /> can lead to the <Hero /> written in the page, and why Layers can name components.
What matters about the marks:
They exist only in what the dev server compiles. The file on disk is untouched, and the rest of its text stays exactly where it was, so line numbers stay true.
They exist only while midcode runs the server. Your own
next dev, your production build and your deployed site never have them.Files in
node_modulesare skipped. An element drawn by a library has no mark, and the panel says so: “A library draws this element: it isn’t in your project’s files, so there’s nothing to edit here.”Wrappers that render nothing of their own are skipped too: fragments,
Suspense,StrictMode, context providers.
A site a server renders from templates (Django, Rails, Hugo, plain PHP) has no build step to hook into. From the next release, midcode finds each element in the project’s templates after the page loads: see Sites a server renders. Where pages are built from Markdown (Hugo, Jekyll, Eleventy), it finds headings, paragraphs and list items in the .md files by their words: see Hugo, Jekyll and Eleventy.
3. The bridge inside each frame
Each breakpoint on the canvas is a frame that loads your dev server at that width. When a frame finishes loading, midcode runs a script inside it: the bridge. The bridge draws the hover and selection outlines, in a layer of its own that your CSS can’t reach, and it listens for clicks, double-clicks and drags.
A site may refuse to be shown inside a frame (X-Frame-Options, or frame-ancestors in its Content Security Policy). midcode lifts that for your dev server’s address only.
When you click, the bridge finds the nearest element with a mark and tells the editor what it is: its marks, which copy it is when one line renders many (a .map()), and a CSS selector as a last resort. The “Code” section of the right panel then shows where it lives:
“Element”: the file and line of the element itself.
“Instance”: the component instance that drew it, if any.
“Used at”: where its component is used.
“Inside”: the nearest ancestors written in other files.
Click any of them to copy file:line:col. That reference is what you hand to an agent: see Your agent in midcode.
4. The edit goes to the file
Say you double-click the heading and type. The window sends the element’s reference, the old text and the new one to midcode’s main process, the only part of the app that reads and writes your files. It opens app/page.tsx, parses it, finds the element that starts at line 13, column 7, and checks that its text is what the canvas was showing. Then it replaces only the characters that change:
<section className="px-6 py-16">- <h1 className="text-5xl">Built to outlast</h1>+ <h1 className="text-5xl">Built to endure</h1> <Card title="Oak" />A style works the same way. The style panel works out which classes to take out and which to add, and the new one takes the old one’s place:
-<section className="px-6 py-16">+<section className="px-6 py-24">midcode never reprints a file. It doesn’t format, reorder or touch a line it wasn’t asked about, so git diff shows exactly the edit. Edits to one project run one at a time, in order.
If the file moved on while you were looking (you or your agent saved it and the page hasn’t reloaded), the element isn’t where the mark says, and the edit is refused rather than guessed: “Couldn’t find the element at app/page.tsx:13 (did the file change?)” or “The text in the code doesn’t match the preview. Wait for it to reload and try again.”
When the text isn’t where it shows
Sites often keep their words in data:
{features.map((f) => (
<h3>{f.title}</h3>
))}The <h3> knows where it’s written, but there’s no text there to change. So midcode looks for the exact text as a string in the project’s files: modules, JSON, Markdown, CSS. One match is the answer. With several, it keeps the ones in files the element’s file imports. If it still can’t tell, it names the places instead of picking one: “That text appears in 3 places (…). Open the right one in your editor.”
Text a program computes has nothing to rewrite: “This text isn’t written there: it comes from a variable or prop.” Text, images and video has the full rules, and Code that midcode can edit says how to write code that stays editable.
5. The page updates itself
midcode doesn’t redraw anything. The file changed on disk, your dev server noticed, and its hot reload brought the change to every frame. What you see on the canvas is always your real site, built by your real toolchain.
6. Undo, and the list of changes
Every write is kept as a before and an after.
⌘Z puts the file back as it was. ⇧⌘Z does the edit again.
One edit is one step, whatever it took: a class changed on five selected elements, or a component inserted along with the file midcode created for it and its import. Undoing an edit that created a file deletes that file.
An undo only happens if the file is exactly as the edit left it. If you, your editor or your agent changed it since, midcode leaves it alone: “The file changed outside midcode, so that edit can’t be undone”.
A file midcode created that something else now imports stays too.
What your agent changes from the chat in the Agent tab goes into the same history, one step per change it makes.
The history is kept in memory: up to 200 steps per project, until you quit midcode. After that, your undo is git.
Each edit also adds a line, in words, to the list in Publish: Text “Built to outlast” → “Built to endure”. Undo takes the line out again. Publishing turns the files you choose into one commit and pushes it.
Some things midcode saves aren’t code edits and have no undo: comments, layer names, breakpoints and page order (files in .midcode/), a page made or removed from the page menu (a removed page’s file is in the Trash), rows of a database, and a Shopify store. Each page says so where it applies.
What this means for your project
Your project never depends on midcode. There’s no runtime, no wrapper component and no build plugin left behind.
What midcode adds is plain files you can read: What midcode adds to your project lists every one.
You can edit the same files in your editor or with an agent while the project is open. midcode shows whatever is there.
Limits
Only elements written in your project’s files can be selected by their code. What a library draws can’t.
In Next.js and Vite projects the marks go on
.tsxand.jsxfiles. JSX written in a plain.jsfile isn’t marked.A mark is a line and a column. Between a save and the reload that follows, marks can point at the old position; edits made in that moment are refused, not misplaced.
Undo history doesn’t survive quitting midcode.
How much can be edited depends on the stack: see What works with what.