Building an app
This page follows a project from the dashboard to a zip: making one, what the editor gives you, the rules a file obeys, how a change is saved, how to go back, and what the preview loads from other people's servers. The agent that writes most of the code has its own page.
The dashboard and the projects list
/dashboard greets you by username, or by a short wallet address with a link to set one, shows the credit balance, and offers four ways to start: Start with AI, which opens a blank project on the composer; Blank project, which opens one on the code; Templates; and Import from GitHub. Below them are the six most recently updated projects.
/projects is the whole list: the search box is bound to ?q= and the sort to ?sort=updated or ?sort=name, and each card shows the icon, the name, the file count and when it last changed, with a menu of Open, Rename and Delete. An account may hold 200 projects and create 30 an hour.
Creating one
The new project dialog takes a name of up to 80 characters (empty means "Untitled project"), an icon from a grid of 24, and one of four templates: blank, todo, landing or dashboard. POST /api/projects makes it, with a slug from the name, unique within the account and given a numeric suffix on a collision. Nothing is provisioned in PostgreSQL here: a project gets its schema on first use; see databases.
The editor
/editor/<id> is a server component: it reads the project, the last 100 messages, the publishing state and the generated data client, and answers 404 for an id that is missing, malformed or somebody else's.
Three columns: the transcript with the composer under it, the file tree, and the workspace, which shows one of Code, Preview, Data, Assets or History and remembers that choice per project in the browser. On a narrow screen the columns become a bottom bar with the same names.
The composer sends on Enter, makes a newline on Shift+Enter, sends on ⌘⏎ and stops a run on Esc. It grows to six rows, holds at most 8000 characters, and offers three suggestions while empty. During a run Send becomes Stop.
The transcript renders your message as a block, the agent's text as Markdown, and each tool call as a row saying what it did (Edit src/App.tsx +3 −1) that opens to show its input and output. Thinking rows start collapsed. Older messages page in through GET /api/projects/<id>/messages.
The file tree creates folders implicitly from a path, validates a new path as you type, and offers Rename and Delete on a file. Monaco colours a file by its extension and goes read only behind a banner while the agent is writing. Edits save themselves 1.5 seconds after you stop typing, ⌘S saves at once, and a failed save retries after 1 second, 3 seconds and 9 seconds before showing an inline Retry.
File rules
They live in src/lib/contracts/files.ts, and a hand edit and an agent write meet the same function:
- At most 200 files.
- At most 200 KB per file, and 2 MB for the whole project.
- A path of at most 200 characters made of letters, digits, dots, underscores, dashes and slashes (
^[A-Za-z0-9_\-./]+$), with no leading or trailing slash and no empty,.or..segment. Paths are unique. - Three paths belong to Creora and are refused:
index.tsx, which the preview generates, andcreora.tsandsrc/creora.ts, which carry the project's database key. That is what keeps the key out of the file tree and the version history.
Versions, and a write that arrives late
Every project carries a version and every write bumps it. A save sends the version it started from; if the row has moved on since, the update matches nothing and the answer is 409 stale_project, carrying the server's copy of the project.
The editor turns that into a dialog, "This project changed somewhere else". Load that version replaces what is open; Keep mine saves the local files over it, using the server's version. Saving is paused until you choose, and Download stays reachable. A 404 on a save means the project was deleted, and the editor says that instead.
A save carrying files is refused with 409 run_in_progress while a run holds the project, and retried; a name or icon change carries no files and goes through.
Going back
src/lib/checkpoints/service.ts remembers the file set as it was before each change, so Restore beside "add a dark mode toggle" undoes that change rather than landing on its result.
- A run takes one, labelled with what was asked for, on its first file write. A run that only read takes none.
- A hand edit takes one, unless the newest point is less than five minutes old.
- A restore takes one of the current state first, which is what makes going back itself undoable.
Twenty are kept per project; older ones are pruned and sequence numbers are never reused. GET /api/projects/<id>/checkpoints lists them with a label, a file count and which one the project is in now; POST with {"restore": <seq>} puts one back, at up to 20 restores a minute. Restoring to the current state answers 409.
The preview
The Preview pane runs the project in Sandpack's react-ts template. It looks for src/App.tsx, then App.tsx, and for src/styles.css, then styles.css, and generates the /index.tsx that mounts them. Five packages are pinned: react and react-dom at 18.3.1, lucide-react at 0.563.0, clsx at 2.1.1 and tailwind-merge at 3.6.0. Nothing else can be installed.
With no entry file the pane offers Create App.tsx and Ask the agent, and a bundler error opens a panel along the bottom. The pane stays mounted and is hidden with CSS, so switching panes reinstalls nothing.
Assets and the cover
GET and POST /api/projects/<id>/assets are a project's image library: PNG, JPEG or WebP, up to 4 MB each, 40 per project, 60 uploads an hour, and what is stored is what the bytes were sniffed to be. Each card copies the image's address, or a sentence to paste into the composer; DELETE /api/projects/<id>/assets/<assetId> removes one for good. POST /api/projects/<id>/cover stores a picture of a published site, drawn by the app itself in the browser, at up to 40 an hour.
Remix, download, delete
POST /api/remix names somebody's published app by username and slug, copies that snapshot into a new project of yours and answers where to open it. It costs a project slot and is limited to 30 an hour.
Download builds the zip in the browser: the project's files at their own paths, plus a Vite scaffold of package.json, index.html, src/main.tsx, vite.config.ts, tsconfig.json and a README.md. A project file at a scaffold path wins over the generated one. A project with a database also gets creora.ts in the zip, the one place that key is ever written to a file, and the README says so.
DELETE /api/projects/<id> answers 204 and takes the publishing rows, the transcript, the data keys, the asset library and the checkpoint history with it, then schedules the PostgreSQL schema to be dropped by a retried job, so nothing is left behind even when the cluster is down.
What the preview loads from elsewhere
Three origins are fetched at runtime and the editor depends on all three:
- Sandpack's bundler at
*.codesandbox.iocompiles and runs the preview in an iframe. Without it the pane is blank. - The Tailwind Play CDN at
cdn.tailwindcss.comis loaded inside that iframe, which is why generated apps use Tailwind classes whilestyles.cssstays plain CSS. - Monaco's loader fetches the editor from
cdn.jsdelivr.net. Until it arrives the code pane shows a skeleton.
All three are named in the Content Security Policy, which is enforced. Self hosting them is not planned; see configuration.