DEV Community

Cover image for I built an AI studio that hands the unfinished parts to a human developer
Александр Пак
Александр Пак

Posted on AI-assisted

I built an AI studio that hands the unfinished parts to a human developer

Most AI app builders end at the same place: you get a demo that looks great, then you open the code and realize someone still has to wire up payments, a database, email, auth. The tool pretends that part doesn't exist.

I wanted the opposite. A studio that builds as much as a browser can run, checks that it actually works, and then writes down honestly what is left for a human. This post is about how that works inside, the bugs that bit me, and what it costs per project.

Live: aiuniversenetwork.com (there's a no-signup demo on the landing page).

What it is
You describe an idea in plain words ("a landing page for a coffee shop with a menu and a booking button", "a tower defense game", "a dashboard with charts"). The studio generates a working web project, shows it in a preview, and lets you keep changing it in chat. You can download a ZIP or publish a link.

Stack, kept boring on purpose:

FastAPI + SQLite, one server, one systemd unit
Vanilla JS single-page frontend, no framework, no build step
Gemini for generation, headless Chromium (Playwright) for checking
An optional second model on paid tiers for fixes and a final polish
The part that matters: it tests its own output
A generated page that throws on the first click is worse than no page. So every generation goes through a loop:

Generate the code.
Open it in headless Chromium in a separate, unprivileged subprocess with a hard timeout.
Collect console errors and uncaught exceptions, detect a blank screen, and click the buttons it finds.
If something is wrong, send the exact errors back to the model and ask for a fix.
Repeat for a small, fixed number of rounds (two by default) and keep the best version.
text
generate ─▶ run in Chromium ─▶ errors? ─yes─▶ ask model to fix ─┐
▲ │no │
└────────────────┴───── save best version ◀───┘
The user sees this as a progress strip: Understanding → Designing → Writing code → Checking → Polishing → Saving, with a timer. Not decoration: during checking and fixing it can go back to "Writing code", and watching it do that builds more trust than a spinner.

A detail I like: on single-file projects, a polish pass from the second model is only accepted if it passes the same check again. If the "improved" version has new problems (or not fewer than before), it's thrown away. Models love to "improve" things into worse things.

Two changes made this much cheaper. The second model no longer rewrites the whole page: it returns only SEARCH/REPLACE patches, which are applied to the original, so the output is a fraction of the size. And the polish pass runs only when the check found problems; a page that already passes is left alone.

Multi-file projects
Single-file output hits a ceiling fast. For bigger projects the studio does this instead:

Ask the model for a file plan as JSON (which files, what each is for; index.html always first).
Write the files one by one, each call seeing the files written so far.
Bundle them into one self-contained page for the preview: stylesheets and scripts are inlined, and for multi-page sites the pages are embedded as JSON with a tiny router that swaps documents.
Run the same Chromium check on the bundle and let the model fix only the files that are broken.
Limits are deliberately small (up to 8 files, 200 KB per file, 600 KB in total), because a model that writes 40 files writes 40 places for bugs.

The ZIP you download contains the real file tree, a README on how to run it, and a standalone.html that opens with a double click. (I added the last one after opening index.html straight from the archive and getting an unstyled page. The unzip-and-double-click path is the first thing a human tries.)

The handoff package
This is the feature I'm most interested in feedback on.

Every project ZIP includes HANDOFF.md. The model is told to leave working stubs for anything a browser can't do, marked like this:

js
// TODO(human): send the order to a payment gateway (Stripe, etc.) and
// create the order on the backend
async function submitOrder(order) {
return { ok: true, demo: true };
}
A small scanner collects those comments from every file (//, /* */ and <!-- --> styles, with file name and line number), merges them with the open steps of the project's plan, and writes a document a developer can start from: what's open, where exactly, how to run it, and notes for whoever continues.

One lesson from the scanner: my first regex also matched #todo-list in CSS. Matching # comments was a mistake in a project that is full of CSS selectors, so only comment syntaxes that can't be selectors are matched now.

The point: the human developer doesn't need to read foreign code to find out what's missing. The studio did the 80% that is mechanical and wrote down the 20% that needs a person.

What broke (and what I learned)

  1. The auto-check silently didn't run for days. In production the log said autocheck failed to run: unexpected kwargs: user. I launch Chromium as nobody so untrusted generated code never runs as root, via asyncio.create_subprocess_exec(..., user="nobody"). But uvicorn[standard] installs uvloop, and uvloop's subprocess implementation doesn't accept user=. The exception was caught and logged as a warning, and generation carried on without any check. Nothing looked broken to users. The fix was to run subprocess.run in a thread. The lesson is the boring one: a fallback that degrades silently is a bug that hides itself. Now the admin panel shows whether the checker is on and counts how many fixes and polish passes ran and how many were rejected.

  2. Cyrillic project titles returned HTTP 500 on download. Content-Disposition: attachment; filename="…" must be Latin-1 in the header value. The fix is an ASCII fallback plus filename*=UTF-8''… (RFC 5987).

  3. A false "failed to create project" error. Generation can take longer than nginx's default 60-second proxy timeout. The browser got a 504 while the server was happily still working, then the project appeared. Now a 5xx without a JSON detail is treated as "still running" and the client waits for the WebSocket event instead. Also set proxy_read_timeout 600s.

  4. My own QR code was undecodable. I wrote a tiny QR encoder (no dependency) for the payment address and the first version produced valid-looking codes that no phone could read. The cause: format-information bits written with swapped row and column. I verified the fix by decoding 16 different payload lengths with OpenCV rather than by eyeballing it.

What it costs
On Gemini Flash, a single-page project is roughly 300 KRW (about $0.20) to generate. A three-page coffee-shop site plus one follow-up edit came to about 336 KRW. The checking loop is cheap because Chromium runs on the same small VPS (2 vCPU / 2 GB RAM) and only the fix rounds cost tokens.

I haven't published numbers for the second model yet because I'm still measuring: the admin panel logs input and output tokens per call and how often a polish is accepted vs. discarded. The first full-page version of that pass cost far more than I expected, which is why it now works with patches and only when needed. I'd rather report real data than guess.

Honest limits
Everything generated runs in the browser. There is no generated backend. Payments, databases, email, auth are stubs with TODO(human) notes. That is the whole idea of the handoff, but it's also a hard limit.
Quality depends on the model and the prompt. The checker catches crashes and dead buttons; it can't tell you the design is ugly.
Large, stateful projects (games, simulators) usually need several rounds of edits. The multi-file mode and the project plan help, but expect to iterate.
Try it, break it
The landing page has a demo that doesn't require an account (one free run). If you try it, I'd love to know:

Does HANDOFF.md actually save a developer time, or is it noise?
What kind of project made it fail?
Would you want the TODO convention to be configurable, e.g. issues instead of comments?
There's a small Discord community for feedback: discord.gg/5WXvF62uh5.

Thanks for reading.

Top comments (0)