From Prompt to React Native Component Tree: Inside Our AI Pipeline

(156 chars):** Trace one prompt through RapidNative's four-stage AI code generation pipeline — from context gathering to a live React Native component tree.

SA

By Suraj Ahmed

19th Jun 2026

Last updated: 19th Jun 2026

From Prompt to React Native Component Tree: Inside Our AI Pipeline

Most "AI app builder" demos hide the interesting part. You type a sentence, a video cuts, and a screen appears. The actual work — the path from a sentence to a real, navigable React Native component tree — is where the engineering lives. This post walks through that path inside RapidNative.

We'll trace one request — "Build me a fitness app with a login screen and a workout list" — through every layer of our AI code generation pipeline: the four LLM stages, the tool calls that build context, the deterministic file emitters, the JSX block parser that runs while the model is still streaming, and finally the moment those files become a live, swipeable Expo Router screen tree inside a sandboxed iframe.

If you're building anything that asks a language model to produce executable code — not just text — the architecture below is more interesting than the prompt.

Developer working on a React Native app with code on screen Generating production-grade React Native code is less about prompting and more about plumbing — Photo by Clément Hélardot on Unsplash

Why a Single Model Isn't Enough

The naive approach is to hand one frontier model the user's prompt, give it tools to read files and search the codebase, and let it generate everything in one streamed response. We tried that. Two problems surfaced immediately.

First, context starvation. By the time the model finished reading project files, scanning the database schema, and deciding whether to scaffold auth pages, it had spent thousands of tokens before writing a single line of JSX. The screens it eventually produced were thinner, with weaker layouts and more hallucinated imports.

Second, cost asymmetry. Reading a package.json or grepping for an import path doesn't need a frontier model. Generating a polished mobile screen does. Paying Sonnet-tier rates for ls() is a waste.

So we split the pipeline into four discrete stages, each routed to the model that fits its job. Each stage's output becomes the next stage's input — a classic pipes-and-filters architecture, but with LLMs instead of Unix tools.

Pipeline diagram concept — multiple stages connected by arrows Splitting AI work into specialized stages is closer to a Unix pipeline than a single chat completion — Photo by Carlos Muza on Unsplash

The Four-Stage Pipeline

Here's the contract. Each row is one LLM call (or, in stage 2 and stage 3 partially, no LLM call at all).

StageJobModel classToolsOutput
1. Context gatheringRead project, decide intentFast (Haiku-class)grep, glob, read_files, list_skillsPlain-text signals
2. Signal parsingRegex over stage-1 outputNoneNoneBooleans + strings
3. Deterministic generationEmit auth, schema, app.jsonMixedJSON-only schema toolPre-formed files
4. Code generationWrite screens & componentsStrong (Sonnet-class, GLM, Qwen)None<CodeProject> blocks

The most counter-intuitive choice is stage 4 has no tools. The strongest, most expensive model in the pipeline is also the one we trust the least with side effects. We've already gathered everything it needs in stage 1, parsed the intent in stage 2, and pre-built the deterministic skeleton in stage 3. By the time we hit stage 4, the model's job is narrow: emit JSX. Removing tools eliminates a whole class of hallucinated tool calls and cuts latency by a large margin.

Stage 1: Context Gathering

The fast model gets six tools, defined as Vercel AI SDK tool() wrappers: batch_grep, glob, get_files_content, list_dir, get_images_by_keywords, and a skills-loading triplet (list_skills, search_skills, read_skills). The system prompt for this stage isn't asking it to write code. It's asking it to produce semantic signals — short answers like:

AUTH: yes
DB: yes
APP_NAME: FitTrack
NEW_SCREEN: yes

Why text instead of structured JSON output? Two reasons. First, it streams cleanly — we can show the user "Reading project files..." as the tool calls happen, then "Planning..." as the signals come in. Second, regex over short signal lines is cheaper and more robust than enforcing a JSON schema on a smaller model that sometimes drifts. We trade structured guarantees for resilience, and re-parse defensively in stage 2.

Stage 2: Signal Parsing

There's no LLM here. A small regex pass extracts each KEY: value pair, normalizes booleans, and produces a typed config object: { needsAuth, needsDb, appName, isNewScreen, vision }. Boolean decisions don't deserve a model call.

This is also where free-tier screen limits get enforced. If NEW_SCREEN: yes and the project is already at five screens on a free plan, we short-circuit before stage 4 ever runs. The user sees a clean "you've hit your screen limit" message instead of a half-generated file.

Stage 3: Deterministic Generation

This is the most under-appreciated stage. Auth flows and database schemas are not creative work. We don't need a $0.015/1K-token model to write a sign-up screen with email and password fields. We need it to be correct, on-brand, and consistent with the project's chosen stack — Nativewind vs StyleSheet, Supabase vs PocketBase, etc.

So we ask a model for the schema as a JSON object (just the tables, columns, and relationships), then a small TypeScript function turns that JSON into schema.ts, migrations/*.sql, RLS policies, and a typed client. The model writes the data model. We write the files.

The same pattern applies to auth. The model decides which routes the app needs ((auth)/signin, (auth)/signup, (auth)/forgot-password). A deterministic emitter writes each file from a battle-tested template, slotted with the chosen styling library and the app name from stage 1.

Every file produced here is marked with a deterministic: true flag so downstream code skips the post-processing pass — there's no need to re-validate JSX we wrote ourselves.

Stage 4: Code Generation

By the time stage 4 starts, the strong model has a system prompt that includes:

  • Template-specific conventions (file paths, import patterns, styling library)
  • A compact summary of relevant existing files (injected from stage 1's reads)
  • A list of pre-generated deterministic files (so it doesn't recreate them)
  • The user's original prompt and the last few conversation turns
  • Auto-loaded skills (small reusable patterns we've found the model gets wrong otherwise)

It produces a single streamed response that wraps file outputs inside <CodeProject> blocks — a custom XML-ish tag that gives us deterministic boundaries when parsing. Inside the block, each file looks like a fenced code block with a path attribute:

import { View, Text, FlatList } from 'react-native';
// ...

This stage uses streamText() from the Vercel AI SDK, with temperature 0.6 and a 32K-token output budget. For Anthropic models, we enable ephemeral prompt caching on the system message — the project context doesn't change between turns in a session, so caching the system block alone trims a meaningful slice off every follow-up's bill.

Code streaming on a monitor with React Native syntax highlighting The strongest model in the pipeline runs without tools — context is pre-loaded, output is pure code — Photo by Markus Spiske on Unsplash

Parsing JSX While It's Still Streaming

The moment stage 4 produces its first byte, the client starts parsing — not after the response finishes. This is where the component tree starts forming.

Our streaming server uses SSE (server-sent events) with a custom event schema: start, text, tool_call, tool_result, usage, done, screen_limit_exceeded, error. Each text event carries an incremental delta. The browser thunk accumulates deltas into a buffer and scans for <CodeProject> boundaries on every chunk.

When the parser sees a complete fenced block inside a project — open backticks, path attribute, body, close backticks — it extracts the file and forwards it to a save queue. Files don't wait for the stream to finish.

There are three subtle rules in the save logic, learned from production failure modes:

  1. Debounce .tsx files for 100ms. JSX files trigger Babel transforms in the browser bundler, which is expensive. If the model is still actively writing to index.tsx when we save it, we'll bundle three or four times in a row for nothing.
  2. Save .ts, .json, .mjs immediately. Non-JSX files don't transform, and downstream files (like screen imports) often depend on these being on disk.
  3. Defer _layout.tsx until the stream ends. Expo Router treats layout files as the routing skeleton; saving a half-written layout mid-stream causes the live preview to flash a broken navigator. We hold it until the done event arrives.

The save itself goes through a resilient persistentFetch wrapper that retries on transient failures and reconciles across browser tabs using a tabId header. The destination is a Supabase files table, scoped by project_id.

The Component Tree, Materialized

A React Native app is a tree. The root is a navigator (in Expo Router, this is the top-level _layout.tsx). The branches are screens, tab groups, stacks, modals. The leaves are the actual View / Text / Pressable components that render pixels. Our pipeline's whole purpose is to materialize that tree from a single prompt — and the way it gets there isn't through one big code-gen call. It's through the file system.

Expo Router uses file-based routingapp/(tabs)/workouts/index.tsx becomes a tab named "workouts" with an index screen. When the AI emits files in stage 4, the layout of the file tree IS the component tree. There's no separate "navigation graph" the model has to produce. By writing files into the right folder structure — app/(auth)/signin.tsx, app/(tabs)/_layout.tsx, app/(tabs)/profile.tsx — the model is, transitively, drawing the navigation graph.

This is why the deterministic auth emitter writes files at fixed paths and why we inject the existing layout.md and theme.md summary into stage 4's context. The model has to know what tree already exists before it can extend it correctly. The component tree is implicit in the file system; we just have to keep the model honest about it.

From File to Live Preview

Once a file lands on disk, the in-browser bundler picks it up. We don't ship a server-side build pipeline — every preview compiles client-side in a Web Worker using Almost Metro, a Metro-compatible bundler that targets React Native Web.

The flow:

  1. File save fires a virtual file system event.
  2. The bundler worker recompiles the entry point.
  3. The new bundle is exposed as a blob URL.
  4. The SandboxPreview iframe's src updates to the blob URL plus a route hash (#/app/(tabs)/workouts).
  5. The user sees the new screen mounted in the live preview, with state preserved where possible.

The iframe is sandboxed with allow-scripts, allow-same-origin, allow-forms, allow-popups, allow-modals. We need allow-same-origin for asset require() calls to resolve and allow-scripts because the whole point is to execute the generated React Native Web code. Everything else is locked down. No top-level navigation, no parent DOM access.

Runtime errors thrown inside the iframe get caught, deduplicated by hash, and surfaced in a side panel. If you've used our editor and seen "Fix with AI" appear after a generation, that's a one-click follow-up turn that injects the runtime error as the next user message, with isAutoRestart: true so the user doesn't get charged twice for the recovery loop.

Smartphone preview running a mobile app The iframe-based preview compiles React Native to React Native Web in a browser worker — Photo by Plann on Pexels

Why Not Just Use One Big Agent?

There's a school of thought that the right answer is to give one big agentic model all the tools and let it figure it out — read files, run tests, iterate. Cursor and Devin lean that direction. We respect the elegance.

But for generating an app from a single user prompt in seconds, agentic loops are the wrong shape. They're optimized for problems where the answer is unknown until the agent explores. Generating a fitness app from "build me a fitness app" isn't an exploration problem. We already know the shape of the answer. The right tool is a pipeline — predictable, fast, easy to cache, easy to debug — not a loop.

Where agentic behavior shows up in our system, it's narrow and bounded. Stage 1 can call up to ten tools in sequence. Stage 4 has none. Recovery flows can trigger a follow-up turn if the bundler reports an error. There's no open-ended ReAct loop where the model decides to "think more" indefinitely. Every stage has a contract; every step terminates.

What This Means for Latency

The four-stage split has an obvious cost: round trips. Each stage is an HTTP boundary, sometimes a different provider. You'd think this would feel slow.

It doesn't, because we exploit two properties. First, stage 1 streams. The user sees Reading files… and the actual tool-call traces in real time. Perceived latency starts dropping at chunk one, not at completion. Second, stage 4 streams JSX into files that compile incrementally. The first screen file lands and starts rendering in the preview while later files are still being generated. By the time the model emits its final byte, the first three screens are already navigable.

If your AI product also generates code, the lesson is: don't optimize total wall-clock time. Optimize time-to-first-useful-thing. A four-second response that shows nothing for four seconds feels slower than a six-second response that's useful at second two.

Internal Cross-References

If you want more detail on adjacent pieces of this system, we've written about them separately:

What Counts as "Production-Ready"

A fair question: code generated by a pipeline like this — is it actually production-ready, or is it demo-ware that breaks the moment you push it to TestFlight?

Three things tilt the answer toward "yes, with caveats":

  1. The output is real Expo code. Not a custom DSL, not a JSON blob a runtime interprets. The files we write are what you'd write by hand — TypeScript React Native components inside an Expo Router structure — and you can export the project at any point and continue in your own editor.
  2. The deterministic skeleton catches the failure-prone cases. Auth and database wiring are the parts AI most commonly hallucinates. By generating those deterministically, the parts most likely to break are the parts least exposed to the model.
  3. The bundler runs the same code that ships. The preview isn't a different runtime that lies about what your app does. If it renders correctly in the iframe, it'll render correctly in Expo Go, and almost certainly correctly in a built .ipa.

That said, an AI builder isn't a free pass on engineering judgement. Custom business logic, third-party integrations beyond what we scaffold, and performance-sensitive screens still need a human reading the diff before they ship to real users.

Try the Pipeline

The fastest way to feel the difference between a single-shot agent and a pipeline is to use one. Open the editor, type a prompt, and watch the four stages happen in order — context gathering, signals, deterministic skeleton, streamed JSX into a live component tree. You can also start from a whiteboard sketch or a PRD — both routes feed the same pipeline, just with different stage-1 inputs.

The interesting engineering inside an AI app builder isn't the prompt. It's the plumbing between the model and the file system, and the file system and the screen. That's where a sentence becomes a tree.

Start now

Ready to build your app?

Turn your idea into a production-ready React Native app in minutes.

Free tools to get you started

Questions

Frequently asked questions

What is RapidNative?

RapidNative is an AI-powered mobile app builder. Describe the app you want in plain English and RapidNative generates real, production-ready React Native screens you can preview, edit, and publish to the App Store or Google Play.

Can I export the code?

Yes. RapidNative generates clean React Native and Expo code that you can export at any time. No lock-in, no proprietary format. Hand it to your developers or keep building inside RapidNative.

Is RapidNative free to use?

Yes. You can build apps on the free plan with no credit card required. Paid plans unlock unlimited AI generations, code export, and direct publishing to the App Store and Google Play.

Do I need to know how to code?

No. Most users build apps by describing what they want in plain English. Developers can drop into the code whenever they want more control, but coding is optional.

How long does it take to build an app?

Most users have a working first screen in under a minute. A full MVP usually takes a few hours instead of the weeks or months traditional development requires.