Practice

A Vibe Coding workflow that ships

· Dieter Li

A personal practice for turning a PRD into a running frontend, API, and database — contract first, then code.

This is a Vibe Coding workflow I have reused across personal projects and day-to-day development. The goal is to make AI coding tools reliable, not occasionally impressive.


1. The idea in one line

PRD in, stack out. Feed in a product requirements document and have AI coding tools produce a runnable full-stack app: frontend, API, and database. The path from requirements to something you can actually run is end to end.

The point is not to build another platform. It is a personal workflow and a prompt method you can repeat — prove it on a demo, then keep using it on real work.


2. Why this method exists

2.1 Where time actually goes

StageWhat goes wrongCost
RequirementsEndless alignment; the implementation drifts from the intentHigh communication overhead
Technical designSchema design and API definition take too longHours to days
ImplementationBoilerplate dominates (CRUD and the rest)60%+ of the cycle
Integration and deployEnvironment setup, contract mismatches, and deploy configSlow and error-prone
Frontend / API boundaryField names and types disagree; integration time is spent reconciling payloadsConstant rework

2.2 What I kept seeing

  • Most day-to-day work is patterned: tables, CRUD endpoints, form screens.
  • AI coding tools can already read a requirement, generate code, and run commands. The hard part is steering them.
  • The missing piece is not another tool. It is a standard prompt and a workflow that turns tool capability into something you can ship.
  • The usual failure mode in AI-generated full-stack apps is a broken contract. When frontend and backend are generated separately, field names and types diverge.

The leverage is in making “use the AI well” into a method, not a mood.


3. The workflow

3.1 Tool roles

ToolRoleWhat it owns here
CursorIDE-level coding agentPrimary environment: generation, refactors, multi-file edits
OpenCodeTerminal agentHeadless work: run commands, scripts, and bulk edits
Hermes CLILightweight assistantFast Q&A, PRD analysis, prompt drafts

Use each for what it is good at. Cursor writes. OpenCode runs. Hermes thinks.

3.2 Six steps, from PRD to a running app

1 Structure the PRD     2 Design prompts      3 Generate code       4 Bring it up
   (Hermes)                (you + Hermes)        (Cursor)              (OpenCode)
+--------------+        +--------------+      +--------------+      +--------------+
| Paste the    | -----> | Break into   | ---> | Generate one | ---> | Migrate,     |
| PRD          |        | a task list  |      | module       |      | install, run |
+--------------+        +--------------+      +--------------+      +--------------+
                                                                         |
                                                                         v
                                                              +--------------+      +--------------+
                                                              | Browser and  | ---> | Iterate until|
                                                              | API checks   |      | it ships     |
                                                              +--------------+      +--------------+
                                                                   5 Verify              6 Deliver
StepWhat you doToolWhy it saves time
1. Structure the PRDExtract entities, features, and business rules into a task listHermes CLIDo not let the model write code until the shape is clear
2. Design promptsTurn the task list into per-module instructions with hard constraintsYou + HermesA good prompt is the method
3. Generate codeOne module at a time: data model, then API, then UICursorSmall steps and small commits keep the model from drifting
4. Bring it upLet the agent create tables, install dependencies, and start both serversOpenCode“Make it run” is also the agent’s job
5. VerifyCheck in the browser and against the API; describe failures back to the modelCursor + OpenCodeYou accept. You do not implement
6. Iterate to doneRepeat generate → verify → feedback until it is usableAll of themEach loop is minutes, not a day

3.3 The contract problem, and the fix

When the UI and the API are generated apart, the model has to guess the shape of the interface. That shows up as:

  • Field names and types that do not match (user_name on the wire, username in the client)
  • Inconsistent pagination and response envelopes (code / message / data)
  • Integration time spent reconciling payloads, then rewriting both sides

Fix: contract first.

MoveHowEffect
Contract before codeDuring prompt design, have the model emit an API contract: paths, methods, request and response fields, types, and example payloads. Both sides treat that document as the only sourceFrontend and backend speak the same language. The model stops guessing
Shared typesAfter the models and API types exist, the client reuses that type file, or types are generated from the contractDrift fails at compile time
One response conventionLock the envelope, pagination params, and error codes in the promptThe model follows the convention. You do not negotiate it during integration

Anything you can agree up front, agree up front. Do not leave it for the model to invent in the moment.

3.4 Prompt habits that hold up

  1. Structure before code. Always get a task breakdown, a schema, and an API contract, and confirm them, before any implementation.
  2. One module at a time. Model, then API, then page. A single huge generation blows the context and the result.
  3. Constraints, not freedom. Name the stack, the directory layout, the naming rules, and the response shape. Cut the space where the model improvises.
  4. Let the agent run and check. If a command can verify it, do not eyeball it. Hand execution to OpenCode or the terminal.
  5. Commit while it still works. Each usable slice gets a git commit, so a bad generation is a rollback, not a rewrite.

4. Pick a stack the model can actually use

Most stacks were designed for people. Models trip over them. I changed the frontend and backend choices so Vibe Coding stays accurate and can still reach non-standard UI.

4.1 Frontend: packaged component libraries to headless components

Libraries like Ant Design and Element Plus are heavily wrapped. The model has to guess props, behavior, and how to override styles:

FailureWhat it looks like
Invented APIProps that do not exist, or the wrong behavior, so the page does not run
Style black boxInternals are closed. Custom styling means override hacks the model cannot reason about
Low ceilingNon-standard interaction cannot be composed, so the model ships a compromise

Switch to headless components (shadcn/ui, Radix, TanStack headless):

Packaged libraries (Ant Design, Element Plus)Headless (shadcn/ui and similar)
Where the code livesBuried in node_modules, a black boxIn the repo, fully visible
How the model understands itFrom training-data memory of the APIFrom the source and the types in front of it
CustomizationWhatever props were exposedLogic and style are separate, so you can reshape both
Error rateHallucinated props, wrong behaviorGrounded in the real source; it tends to compile
CeilingYou get what the library already supportsCompose freely, including non-standard interaction

What changes in practice:

  • Flexibility goes up. The component source is yours to change. You are not stuck at the library’s boundary.
  • Quality goes up. Generation is based on real source, so first-pass success is much higher.
  • Fewer wasted loops. Less generate → error → rewrite the prompt → generate again. The same feature lands in fewer turns.

4.2 Backend: contract and types first

ChangePracticeWhy
One model sourceSchema or ORM models are the source of truth; API types are derived from themSchema and payload fields stop drifting
Shared API typesClient and server share the type definitions, or the client types are generated from the contractMismatches show up at compile time
Conventions up frontEnvelope, pagination, and error codes are fixed in the promptLess room for the model to invent a second style

If it can be specified, specify it. Do not leave it as a guess. The first question about a stack is no longer “am I fluent in it?” It is “can the model use it without inventing APIs?”


5. What it changed in practice

5.1 Before and after

By handThis workflowEffect
A simple CRUD admin2–3 days30–60 minutesOrder of magnitude
Schema and API designHalf a dayAbout 5 minutesOrder of magnitude
Frontend / API integrationHalf a day to a day of field-matchingContract first; almost no reworkMostly gone
Environment and startupAbout 2 hoursThe agent runs itAlmost no manual setup
Your jobWrite the codeAccept it and make the calls—

5.2 From demo to real projects

  • This is not a demo-only trick. I have used it repeatedly on real product work.
  • Patterned code goes to the model. I stay on business rules and acceptance.
  • The output is readable, editable, and maintainable. It can live in a long-lived codebase.
  • What stuck: prompt templates, an API contract convention, and a short stack checklist. Easy to reuse, easy to hand to someone else.

5.3 Production systems, not sample apps

The loop is the same. The stack follows the job. These are systems I have taken to production with this workflow. The model writes the patterned layers. I keep the boundaries, the contracts, and acceptance.

SystemWhat it actually doesStackWhere the workflow bites
IoT to bird recognitionDevices wake, capture, and ship media. A pipeline turns that into species-level recognition an operator can review.C and Zig on the device, Go for ingest and APIs, SvelteKit for the consoleFirmware, protocol frames, and the recognition API are separate contracts. The console never guesses field names.
News and politics deconstruction agentIngest a stream of reporting, cluster it, and take a political claim apart into who said it, what it asserts, and what it leaves out.RustThe agent’s tool schema and the article record are fixed before generation. Rust’s types make a drifted payload fail the build.
Ontology and semantic AIA durable model of entities, relations, and meaning, with an AI layer that queries that model instead of free-text guessing.Java for the ontology service, Rust for the query and inference pathThe ontology is the contract. Java and Rust share it. Neither side invents a second set of types.
Mobile productOne product surface on the phone, a web console beside it, and one API behind both.UniApp on device, SvelteKit for the web console, Go for the APIUniApp and SvelteKit both consume the Go contract. Screen code is generated against that contract, not against a remembered payload.
ERP monitoring and operationsWatch the ERP’s health, latency, and failures, and give operators a place to see and act.Go for exporters and the ops API, Prometheus for metrics, Grafana for dashboardsMetric names, labels, and alert thresholds are specified first. Go instrumentation and Grafana panels are generated from that list, so a panel cannot reference a series that was never emitted.

A few constraints show up only at this scale:

  • Device code stays small and explicit. C and Zig are not a place to let a model wander. The prompt names the frame layout, the wake path, and what the media SoC is forbidden to do while idle.
  • Agents get a schema, not a vibe. The news agent and the ontology service both refuse free-form tool calls. Inputs and outputs are typed before the first implementation pass.
  • One contract, many clients. Mobile (UniApp), the SvelteKit console, and the Go API are one payload. If the phone and the browser disagree, the contract was skipped.
  • Ops is a contract too. A Prometheus metric name is an API. Write it down, then generate the Go instrumentation and the Grafana dashboard from the same list.

5.4 Why it travels

  • No new platform. Cursor, OpenCode, and Hermes CLI are enough. Nothing custom has to exist first.
  • Not tied to one domain. The same loop works on frontend and backend work.
  • It becomes a personal standard. Contract first, headless UI, and a fixed workflow beat one-off prompting.

6. Takeaway

The point of “PRD in, stack out” is to turn Vibe Coding from a lucky session into a method you can run again:

  1. Method. Cursor, OpenCode, and Hermes, plus a six-step loop and a prompt discipline, so a PRD comes out as a running stack.
  2. Engineering. Contract first, so separately generated frontend and backend stop disagreeing.
  3. Stack. Choose tools the model can read. Headless components remove the guesswork of packaged UI libraries and raise the ceiling on what you can build.

For me this is personal practice, already exercised on real projects: the model produces the patterned work, and I keep judgment and acceptance.

Questions this note answers

What is a practical Vibe Coding workflow from a PRD to a running app?

Structure the PRD into entities and a task list, write constrained prompts, generate one module at a time (data model, then API, then UI), let a terminal agent create tables and boot both servers, then verify in the browser. Cursor writes, OpenCode runs, and Hermes drafts.

How do you stop AI-generated frontend and backend code from disagreeing on fields?

Write the API contract before either side is generated. Paths, methods, field names, types, and example payloads are the only source. Share those types, or generate the client types from the contract, and lock the response envelope, pagination, and error codes in the prompt.

Why prefer headless components over Ant Design or Element Plus when coding with an agent?

Packaged libraries hide their implementation in node_modules, so the model invents props and cannot restyle internals. Headless code such as shadcn/ui and Radix lives in the repo, so the model reads real source and types and can compose non-standard UI.

How should Cursor, OpenCode, and Hermes be split?

Hermes turns a PRD into a task list and prompt drafts. Cursor generates and edits code one module at a time. OpenCode runs migrations, installs dependencies, and starts the servers. You accept the result; you do not implement the boilerplate.

Where has this workflow been used beyond a CRUD demo?

On production systems, not sample apps. An IoT path from device firmware to bird recognition (C, Zig, Go, SvelteKit). A Rust agent that aggregates news and takes political claims apart. An ontology and semantic-AI service in Java and Rust. A mobile product on UniApp, SvelteKit, and Go. ERP monitoring and operations on Go, Prometheus, and Grafana. The same contract-first loop applies. The stack changes with the job.

More from the continuum on Notes. For selective architecture reviews, see Advisory.