# 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.

- Page: https://midcode.app/docs/start/how-it-works
- From the midcode docs. Every page as Markdown: https://midcode.app/llms.txt

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](https://midcode.app/docs/start/open-a-project.md), 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:

```bash
node --require <midcode>/next-hook.cjs node_modules/next/dist/bin/next dev --port 4310 --hostname localhost
```

The 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](https://midcode.app/docs/frameworks/react-vite.md), [Svelte and SvelteKit](https://midcode.app/docs/frameworks/svelte.md) 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.

```tsx title="app/page.tsx, as you wrote it"
<section className="px-6 py-16">
  <h1 className="text-5xl">Built to outlast</h1>
  <Card title="Oak" />
</section>
```

```tsx title="What the dev server compiles"
<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_modules` are 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](https://midcode.app/docs/frameworks/server-templates.md). 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](https://midcode.app/docs/frameworks/static-generators.md).

## 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](https://midcode.app/docs/agents/overview.md).

## 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:

```diff title="app/page.tsx"
 <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](https://midcode.app/docs/editor/styles.md) works out which classes to take out and which to add, and the new one takes the old one's place:

```diff title="app/page.tsx"
-<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:

```tsx title="app/page.tsx"
{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](https://midcode.app/docs/editor/text-and-media.md) has the full rules, and [Code that midcode can edit](https://midcode.app/docs/agents/editable-code.md) 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](https://midcode.app/docs/publish/publish.md): 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](https://midcode.app/docs/start/project-files.md) 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 `.tsx` and `.jsx` files. JSX written in a plain `.js` file 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](https://midcode.app/docs/start/supported-stacks.md).
