Guide

A code generator app for X, where X is whatever you type, and you watch it build.

Almost every tool in this category shows you a spinner while it assembles the thing you asked for. mk0r ships a 28-line bridge file inside every sandbox that forwards Vite's HMR events to the parent window, so the preview is a live readout of what the agent just wrote, which files rebuilt, how many modules changed, and whether any of them errored. Here is the bridge, in full, with the five events it posts and the context around each one.

M
Matthew Diakonov
9 min
4.8from 10K+ creators
28-line bridge in every sandbox
Five HMR event types forwarded
Vite dev server on port 5173

What every guide on this topic misses

Open the other pages that currently rank for this topic. They all do the same thing. They list the Xs the tool can produce. Forms. Tables. Landing pages. Signature blocks. Pricing grids. The list is almost identical across every tool because the underlying models overlap in training data. What none of those pages explain is the mechanism between prompt and pixel. They show a before and an after, never the middle.

The middle is where differentiation lives. If your generator ships the finished zip, the middle is a black box. If your generator shows the preview as a static iframe refresh, the middle is a stall. If your generator is wired into a real Vite dev server and forwards the HMR lifecycle to your browser, the middle is a live signal you can read. mk0r picks the third option, and the piece that makes it work is a 28-line file you can grep for in the public repo.

The bridge, in full

Below is the entire contents of docker/e2b/files/app/src/_mk0rBridge.ts. It is copied into every sandbox at /app/src/_mk0rBridge.ts, and the entry file src/main.tsx imports it before anything else mounts.

docker/e2b/files/app/src/_mk0rBridge.ts

Three things to notice. First, every message carries a source: 'mk0r-preview' tag. The parent window uses that tag to filter out unrelated cross-origin noise, because a real browser iframe receives postMessages from every script that runs inside it. Second, each payload includes ts, the millisecond timestamp, which lets the parent measure time between events and surface it. Third, the HMR events carry an updatescount. That count is the number of modules in the current hot update, which is how the UI shows "3 modules updated" instead of "something changed."

0Lines in the bridge file
0Event types posted to the parent
0Port the Vite dev server binds
0Accounts required to run X

How the event flow actually moves

The agent writes a file. Vite's file watcher fires. Vite emits vite:beforeUpdate. The bridge catches it and posts hmr:before to the parent. Vite compiles. Vite emits vite:afterUpdate. The bridge posts hmr:after. If anything broke along the way, Vite emits vite:error and the bridge posts hmr:error with the message text.

Agent writes a file, the preview sees it rebuild

Agent turn
npm install
Syntax error
Vite dev server
hmr:before
hmr:after
hmr:error
hmr:full-reload

The bridge is intentionally thin. It does not transform payloads, rewrite messages, or batch events. Vite already emits the right shape. The bridge just forwards. That thinness is what makes the preview feel fast. There is no intermediate layer deciding when to refresh a screenshot.

Where the bridge sits on boot

The sandbox startup script boots Vite on port 5173 before anything else that listens on an external port. Once Vite is up, the bridge loads on the first page hit because src/main.tsx imports it at the top.

docker/e2b/files/opt/startup.sh

The "wait until port 5173 accepts a TCP connection" loop is what lets the rest of the pipeline trust that the preview iframe will have something to render. Without it, the first prompt could race the dev server and the user would see a 502 from the proxy on port 3000.

The rule that protects the bridge

The agent runs inside the sandbox and has write access to the whole filesystem. Without a clear rule, it would occasionally decide to "clean up" the bridge file because it does not look like application code. The project's CLAUDE.md pins the bridge as untouchable.

docker/e2b/files/app/CLAUDE.md

The wording matters. Saying "do not modify" without saying why will sometimes lose to an agent that decides the rule is obsolete. Anchoring the rule to a concrete import in src/main.tsxgives the agent a reason that survives "but I refactored main.tsx" reasoning. The bridge import must stay because the parent window needs the messages, and the agent cannot see the parent window from inside the sandbox.

What the console shows when the bridge is doing its job

Here is an abbreviated trace from a real sandbox session where the agent built a habit tracker. The command output and bridge console logs interleave because both stream to the preview window.

/app — agent building a habit tracker

One generator, many Xs

Because the scaffold is identical every time, X can be anything the agent can express as React components that fit inside a Vite project. The bridge is agnostic to what the components do. It forwards the same five events whether X is a signature pad or an internal dashboard.

X as a habit tracker

One prompt turns into /app/src/App.tsx with localStorage, streak logic, and a weekly grid. The bridge posts hmr:after every time the agent saves a file.

X as a signature builder

A canvas element, pen state, export to PNG. The rebuild after the agent wires the download handler fires 'hmr:before' then 'hmr:after' in the parent window.

X as a pricing calculator

Inputs, a result, currency formatting, plus a print stylesheet the agent adds on turn two. You watch turn two land without touching the preview.

X as a budget planner

Category inputs, a total, a pie chart from a chart lib the agent installs on demand. The install turns into a full reload; the bridge posts 'hmr:full-reload'.

X as a QR code tool

A npm install of qrcode.react, a text field, a downloadable SVG. Every keystroke on the prompt becomes a concrete file in /app/src.

X as an internal dashboard

Neon database wired from /app/.env, a few SQL queries, a table with filters. Multiple components land in one turn; each lands as its own HMR update.

The sequence from prompt to rebuild, end to end

If you read the bridge without the surrounding flow, it looks like a postMessage utility. It only earns its place when you trace the chain from prompt submit to the preview redrawing.

1

Prompt submitted

The user types a description of X on mk0r.com. No account, no modal.

2

Sandbox ready

A pre-baked E2B sandbox warms up, startup.sh finishes, Vite is listening on 5173.

3

Agent plans a first edit

The agent reads /app/CLAUDE.md, decides which component to write, chooses a file path under src/.

4

Agent writes the file

A real write lands on the sandbox filesystem. Vite's watcher picks it up immediately.

5

Bridge forwards hmr:before

The parent window receives { source: 'mk0r-preview', type: 'hmr:before', updates: n, ts }.

6

Bridge forwards hmr:after

Vite compiles, swaps the module in place, posts vite:afterUpdate, the bridge forwards it.

7

Preview iframe repaints

The component mounts or re-renders in the iframe. You see X change without a full reload.

8

Turn loops

The agent decides the next edit. The same five events gate the next rebuild. X keeps taking shape.

Versus the generator pattern most tools still use

A lot of code-generator-for-X tools still treat generation as a one-shot: prompt goes in, a finished artifact comes out, the user decides whether to keep or regenerate. mk0r swaps that for a continuous loop that reports itself.

FeatureTypical code-for-X toolsmk0r
Preview rebuild signalSpinner while building, no event streamFive distinct postMessage events from Vite HMR with timestamps
Build failure feedbackBlank preview or stale iframe until the next successhmr:error event with the exact error message, forwarded inline
Code output formatCopy-paste snippet or downloadable zipReal TypeScript files in /app/src, served by live Vite on port 5173
Iteration latencyFull page refresh or full rebuild each timeHMR per file change, preview updated in place, no reload
Installable dependenciesFixed palette of components, no npmnpm install anything; full reload reported as hmr:full-reload
Account requirementEmail and password before first outputNo signup, sandbox boots on prompt submit

The part worth copying

If you are building a code generator app and you want the preview to feel alive, the lesson is not the 28 lines. The lesson is that the dev server already emits the events you need, and the only novel work is forwarding them over a channel the host frame can read. Vite gives you vite:beforeUpdate, vite:afterUpdate, vite:error, and vite:beforeFullReload for free. The bridge just adds a 0 -line translation to window.postMessage. Steal it.

Want to see the bridge forwarding events in real time?

Book a 20-minute walkthrough. I will share a sandbox and you can watch the hmr:after events stream as the agent writes your X.

Book a call

Frequently asked questions

What does X actually mean in 'code generator app for X'?

X is a placeholder. It can be a habit tracker, a budget planner, a signature page, a QR tool, a pricing calculator, a quiz, an internal dashboard, anything. mk0r does not specialize in one X. It treats X as a variable you fill in with a sentence, then writes React plus TypeScript into a live Vite project to produce that X. The same generator handles the same palette of Xs because the underlying project scaffold is identical every time.

What is the mk0r bridge file, and where does it live?

It is a 28-line TypeScript file at docker/e2b/files/app/src/_mk0rBridge.ts in the public repo. It gets baked into every sandbox at /app/src/_mk0rBridge.ts. Its job is to forward Vite's HMR lifecycle events to window.parent using window.postMessage. The parent window is the preview iframe shown on mk0r.com. That is how the UI knows when your X rebuilt, how many modules changed, and whether the rebuild errored.

Which specific events does the bridge fire?

Five. On boot it posts type: 'ready' with the current URL. On each HMR cycle it posts 'hmr:before' and 'hmr:after' with the number of updates. On build errors it posts 'hmr:error' with the error message. On full reloads it posts 'hmr:full-reload'. Every message includes a timestamp and a source tag of 'mk0r-preview' so the parent can filter cross-origin noise.

Why build a bridge at all instead of polling?

Polling misses events and wastes energy. A postMessage bridge is zero cost when nothing changes, and it carries the exact payload Vite already knows about: the count of modules in the update, the error object on failure, the full URL on ready. You cannot derive that from a screenshot or a refresh. The bridge turns the preview iframe into a live readout instead of a stale thumbnail.

Does the bridge work in both generation modes?

The bridge is specific to VM mode, where a real Vite server is running inside an E2B sandbox on port 5173. Quick mode produces a standalone HTML document streamed directly, so there is no dev server and no HMR to bridge. If you want to see the bridge in action, ask for anything that needs a React component tree: a dashboard, a form flow, a multi-step tool.

Can I read the bridge source without running a sandbox?

Yes. The repo is public. The 28 lines live at docker/e2b/files/app/src/_mk0rBridge.ts. The startup script that boots Vite on port 5173 lives at docker/e2b/files/opt/startup.sh, line 60. The project CLAUDE.md that tells the agent never to modify the bridge lives at docker/e2b/files/app/CLAUDE.md, lines 8 and 9. None of it is hidden.

Does this mean the agent writes real files, not strings?

Yes. The sandbox has a real filesystem at /app. The agent edits /app/src/App.tsx and /app/src/components/*.tsx. Vite's file watcher notices the write. HMR fires. The bridge reports 'hmr:before', then 'hmr:after'. The preview shows the change. There is no 'render this string' layer. What you see is the same Vite dev server pipeline you would run on your laptop.

How long does a rebuild take after the agent writes a file?

HMR typically completes in well under a second for a component edit, depending on how many modules are in the update. That is Vite's default, not something mk0r tuned. What mk0r adds is the signal that the update happened so the preview can react. Without the bridge, you would have to infer it from the iframe content.

What happens if the generated code breaks?

Vite emits vite:error. The bridge forwards it as 'hmr:error' with the message string. The preview can show the error inline instead of a blank page. The agent reads the same error from the Vite runtime and patches the broken file. The rebuild loop is tight enough that most syntax errors self-resolve inside a single turn.

Do I need an account to try this?

No. Open mk0r.com and type a description of X. A fresh sandbox boots, the bridge is already at /app/src/_mk0rBridge.ts, Vite is already listening on 5173, and the agent starts writing against your prompt. No email, no card, no install.