Skip to main content
Generated apps render inside a sandboxed iframe by default. When you approve a specific version of an app, that version can also render in the host page, sharing the page’s authority and network: the in-client venue. Approvals pin the app’s content hash, so any edit invalidates the grant and the surface drops back to the sandbox until it is re-approved. Zero config: the jail. With no approval ever recorded, open() omits the inClient field and the app renders in the sandboxed iframe. Nothing to switch on, and no way into the host page except a stored approval pinning the current content hash.

Two venues, one default

  • Jail (default). Every app runs inside an iframe with connect-src 'none', cannot reach the host page’s DOM, and cannot make network calls. This is the surface for every unreviewed or model-generated component.
  • In-client (approved). After a human review, an approved version of the app renders natively in the host DOM. It participates in the page’s origin, cookies, and CSP.
The server decides the venue on every open(). A forged inClient field on a stored, imported, or streamed tree is stripped before the runtime attaches its own verdict. In-thread previews always render in the jail; the approved venue is never used for conversation surfaces.

Approvals pin a version

An approval records that a named principal approved one exact content hash of one app. The hash covers all app content, so any edit (a pin change, a generated-component change, a tree edit) produces a new hash and invalidates every existing approval for that app. One exception to the jail fallback: a review-kind app — one whose pins were captured with review: true — never drops to the jail while it waits. Anything short of a grant becomes pending-review, the surface ships no executable source at all, and the user sees the review standing. If an approved component fails to compile or render, the surface drops back to the jail with an error notice. Every verdict and how it renders is in the Approval reference.

Reviewing what changed: ship-diff

Before minting an approval, a reviewer reads the ship-diff: approvable code diffed against the host baselines vendo sync captured, hash-pinned to the current version. The baseline is always the shipped product, so the diff answers how far the app has drifted from what your users run. It is never a changelog since the last approval. A pin whose captured baseline has since moved is flagged drifted; the diff still returns, and approving is still possible. Net-new generated components appear as pure additions. The tree structure itself is not in the diff; the reviewable code is. Fetch the ship-diff over the wire:
Or on the server:
Both are owner-scoped: only the app’s owner can read the diff. The one exception is the review queue — vendo.apps.review.queue(ctx) hands the ship-diffs of other users’ review-kind apps to a caller your composition’s apps.review.reviewer(ctx) hook asserts. The response shape is in Ship-diff shape.

Minting approvals

For local demos and development, Vendo exposes a documented injection seam that pins the app’s current version hash: you approve what is running, never a hand-crafted hash. For production hosts, Vendo Cloud provides the human review console that mints approvals.

Server-side

approvedBy is required — the record names who reviewed. A review-kind app refuses to be approved by its own owner unless your composition asserts a reviewer through apps.review.reviewer(ctx); without that hook the call is blocked with a pointer to it.

Development wire route

approvedBy is optional here and defaults to "local-dev". The route returns the approval record, and follows the rules in Dev route rules. It never mounts in production. Approvals are audited through guard.report. The approved verdict reaches the client on the open() payload, as inClient.

What runs in the host page

Approved content runs in a closed module space: sucrase-compiled code with a controlled require that resolves React and captured sub-sources only, the same evaluation model the jail uses, in the venue the approved verdict unlocks. Venue parity extends to props: a node with no live props falls back to its captured sampleProps rehearsal stub, exactly as it would in the jail. The $action dispatch continues to route through the tree chokepoint, so host tools remain gated by the same policy, grants, and audit path as the jailed venue.

Approval reference

Verdict states

open() attaches a payload.inClient field to the tree surface: review is the standing: { status: "pending", versionHash } or { status: "rejected", versionHash, note, by, at }.

The approval record

An in-client approval is a small audit-trail record:
Records live in the vendo_inclient_approvals collection. The verdict is simply “some stored approval pins the current version’s content hash”. Corrupt or hand-crafted rows can never grant. Deleting an app clears its approvals.

Ship-diff shape

Dev route rules

POST /api/vendo/dev/inclient-approval is the only /dev/* route Vendo serves, and it is mounted in development compositions only (production returns 404). It also refuses a request whose principal is marked ephemeral: true (401) — approving a host-page mount is a host trust decision, and the flag doesn’t clear it even in dev.