SMERESKI
  1. PROJECTS
  2. SKILLS
  3. RESUME
  4. BLOG
  5. GAMES
  6. CONTACT

POST · 2026-08-22

2026-08-22 · 5 MIN READ

The loop is the product

I built the v0 loop in a day — live preview, forked versions, design tokens, export into real repos — then swapped three different engines into it. The loop barely noticed.

ownershipaiprocess
vzero rendering a terminal-themed server monitoring dashboard generated from a single prompt
AI narration — synthetic voice (Kokoro), not a human recordingAI-assisted text — drafted with Claude, edited and approved by a humanAI-built software — vzero itself was pair-built with Claude Code, tests included
AI narration — synthetic voice (Kokoro), not a human recording · listen, or click any word to play from there

The first page my homemade v0 ever generated was a beautiful, empty void. I had asked a small local model for a pricing page three tiers, dark background and it streamed back 1,620 characters of careful CSS: a navy palette, card borders, hover states. Then it stopped, apparently satisfied, without ever writing a body tag. The preview faithfully rendered what it was given, which was nothing, styled impeccably. I sat there admiring the most thoroughly decorated blank page I have ever shipped.

The itch had been building for a while. v0 is Vercel's describe-a-UI-and-watch-it-appear tool, and the loop it sells is genuinely good describe, preview, refine, keep what works. What it will never do is point at my stuff: my design tokens, my repos, my pick of model, including the ones running on my own GPUs. I kept bumping into that wall, so one morning I asked my coding agent to build me the loop itself. By that evening it had a name, vzero, and a growing test suite; the engines and the public release ate a second day.

The build ran the way my agent projects run now: checkpoints, each one a runnable slice with its acceptance tests written before the feature. The trick that kept the pace safe is that the whole suite runs against a fake model a deterministic mock that answers instantly and needs no credentials. The mock plants little markers in whatever it generates, things like how many earlier versions it saw and which theme it was handed, so a browser test can click through the real interface and prove the right context flowed through, instead of me squinting at output.

Unit tests green at each checkpoint ship
CP0 core loop29
CP1 refinement30
CP2 forks + diff43
CP3 themes52
CP4 react target59
CP5 flutter + export72
public release79

Ten Playwright browser tests accumulated alongside; every checkpoint shipped with the full suite green before the next began.

Nothing advanced while the suite was red. Most of the code is plumbing you would expect, but two pieces surprised me by how small they stayed. Versions came out almost free: every generation stores the prompt that made it, and the context for the next request is rebuilt by walking the current version's parent chain. Which means clicking an old version and typing is a fork branch-accurate context, no extra machinery and a diff view shows what each refinement actually touched.

The other piece was the live React preview, and it taught me the week's best browser lesson. Generated components compile right in a sandboxed iframe, with Babel served from the repo so nothing calls out to a CDN, and they render inside an error boundary. I learned why the boundary matters the hard way. A sandboxed iframe has an opaque origin, so when a generated component crashes, the window's error event reports only the useless string 'Script error.' The boundary sees the real exception, so a broken generation shows a readable overlay instead of a silently blank pane.

Themes were the selfish feature, the one no commercial tool could give me. Pick a theme when a chat starts and its design tokens ride along in the system prompt, with instructions to define them as CSS custom properties and use nothing else for color. At home that means my whole theme library; the public repo ships four samples in the same shape. Pages come out matching everything else I run because they are built from the same tokens.

Eventually the fake model had to give way to real ones, which is where the void came from. The first engine was a local proxy that speaks the Anthropic API in front of a small open model free, private, and the author of that magnificent blank page. The failure turned out to be useful twice. It confirmed the tool renders exactly what it is told and nothing more, and it showed the refine loop healing itself, because the follow-up request came back with the whole page, body and all. The loop held; the engine wobbled.

The second engine was supposed to be the Anthropic API, and it became the day's detour instead. A credential was sitting in my secrets manager labeled as an API key, shaped exactly like one, and the API rejected it every single time. A prefix check finally told the truth: it was an OAuth login token from a Claude Code session, a different species that happens to share a haircut. You cannot spend a subscription login on raw API calls, and dressing the token up in the official client's headers would mean impersonating that client terms-of-service territory I have no interest in building on.

The sanctioned answer turned out nicer than the thing it replaced: run generations through the Claude Code CLI itself, headless, streaming JSON back into the same pipe the other engines use. The CLI is the legitimate spender of the subscription, so the tool rides the plan I already pay for instead of opening a second bill. One gotcha cost me twenty minutes and earned a comment in the source the spawned CLI must not inherit any API environment variables from the server, because inherited configuration silently outranks the CLI's own login. Strip the variables and it just works.

Having two live engines handed me an accidental benchmark. Same tool, same page, same follow-up highlight one pricing tier and add a badge once against the local model, once against Claude.

Same refine request, lines changed
small local model132 lines
claude-sonnet-52 lines

One request, one model each — a measured anecdote, not a benchmark. The local model rewrote the page to make one change; Claude edited the two lines that needed to move.

Both engines completed the loop, and the gap showed up somewhere I had not thought to look: not in how the pages looked, but in how much each engine touched to make the same change. The local model rebuilt the page to move a badge. Claude edited the two lines the request was about and left the rest alone.

Getting it onto GitHub was its own little security project, because the private repo knows too much about my machine. The public copy is a fresh tree with a single commit, since the old history references my planning docs and machine layout. Before pushing I grepped the exact bytes about to be published for names, paths, and key shapes, and the sweep caught exactly one thing a page description carrying my name. Credentials never made it into the tree at all; the API lane keeps a secrets-manager reference on disk, and the real value exists only inside the server process, injected at launch.

The thing is two days old and acts like it: no login, one user, one machine, nowhere near the open internet, and opinions I trust less than the test suite that checks them.

The model turned out to be the commodity in this story. The preview, the forked versions, the tokens, the export none of it cares which brain is plugged in, and the brains will keep changing under all of us. Whatever model I am running a year from now did not exist when I wrote this, and if I built the loop right, nothing but an environment variable will ever notice.

REFERENCES5 LINKS
  • 01
    vzero on GitHub

    The repo this post describes — MIT, runnable on any of the three engines.

    https://github.com/DSmereski/vzero

  • 02
    Vercel v0

    The product that defined the loop this project rebuilds for personal use.

    https://v0.dev

  • 03
    Claude Code

    The agent CLI that both built the tool and serves as its subscription engine.

    https://claude.com/claude-code

  • 04
    Where the tokens go

    The earlier cost-anatomy post; the reason the engine layer here is swappable.

    /blog/where-the-tokens-go

  • 05
    @babel/standalone

    The in-browser compiler behind the sandboxed React live preview.

    https://babeljs.io/docs/babel-standalone