Skip to main content
Your user asks for something. One app.tsx screen comes back, live on real data.

One ask, one app

The plan’s skeleton paints in seconds. The finished screen replaces it in place.
A savings goals screen built in the Maple panel, one card per goal with the amount saved, the target, and a progress bar

The screen re-runs on every open, so it opens on today's numbers. There is no snapshot to go stale.

An app is one file, holding one default-exported React component. Saving it repaints the person’s screen.
app.tsx
useQuery is synchronous and hands back the tool’s own result, so field names come off the tool’s schema. tools.<name>(args) is the only way a screen changes anything.

When an ask is bigger than a screen

Most asks are one screen. When one is not, the tool says so and stops: it raises a standing approval card and returns, having spent nothing. The card waits for the person, however long that takes. On yes, a disposable sandbox builds the app once — npm from the registry, a coding agent inside the box, tested against reality — and then the box is released and dies. What comes home is sealed: an immutable, content-addressed bundle stored as blobs, alongside its source and lockfile. Every seal is a version.
The gate. A build is gated on one thing, a configured sandbox adapter. Without one the ask is refused rather than offered.
A sealed bundle renders in a sandboxed iframe: sandbox="allow-scripts" without allow-same-origin, so the frame has an opaque origin, and a Content-Security-Policy: default-src 'none' header, so it makes no network request at all. Your brand tokens and fonts are injected at render, never baked into the seal, so one seal follows your palette rather than pinning the one it was built under. Host data reaches the frame through one postMessage bridge into the guarded tool door, with the viewer’s own permissions. An edit reseals from the stored source in a fresh box.

What a screen may write

A small closed surface, enforced at save rather than advised in a style guide.

Allowed

  • react and @vendo/screen, and nothing else
  • useQuery("tool_name", { literal }), read tools only
  • tools.tool_name(args), from a handler
  • <Stack> <Row> <Grid> <Text> <Stat> <Button>
  • the components you registered
  • <div> <p> <h2>, children and an inline style
  • React state through useState

Refused

  • any third import, import(…), require(…)
  • a query input from a prop, state, or another query
  • a write tool inside useQuery
  • a tool call in the render body
  • document fetch setTimeout process
  • <img> <script>, or className on a display tag
  • a component you never registered
Every save is compiled, scanned, type checked, run once, and its tree validated. The first stage that finds something is the last one that runs, and the last good screen keeps serving. A refusal names the line and says what to write instead. Nothing paints, and no app row lands.

Own it

Nobody uses someone else’s app. They get their own copy of it.

Import and fork each mint a fresh app_ id.

Ana shares a link, and the link carries the app and nothing else. Importing it mints a fresh id in your account, reading your rows under your approvals. Fork it and you mint another id. A built app is the exception. Sharing, forking, exporting, and placing one are all refused server-side.

Where to go next

In-client venue & approvals

An approved version can render in your page instead of the sandbox.approve → pinned version hash

Host components

Register your own components and a screen renders your real UI.Kit + the ones you registered

Import & fork

How a copy is minted, and what it deliberately leaves behind.import → app_c0d6b562…