> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vendo.run/llms.txt
> Use this file to discover all available pages before exploring further.

# In-client venue and approvals

> How approved apps render in the host page instead of the sandboxed iframe, how approvals pin the app version hash, and dropping back on version change.

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](#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:

```ts theme={null}
const diff = await client.apps.shipDiff(appId);
```

Or on the server:

```ts theme={null}
// ctx is the RunContext for the request: { principal, venue, presence, sessionId }
const diff = await vendo.apps.inClient.shipDiff(appId, ctx);
```

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](#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

```ts theme={null}
await vendo.apps.inClient.approve({ appId, approvedBy: reviewerId }, ctx);
```

`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

```bash theme={null}
curl -X POST \
  http://localhost:3000/api/vendo/dev/inclient-approval \
  -H "content-type: application/json" \
  -d '{ "appId": "app_…" }'
```

`approvedBy` is optional here and defaults to `"local-dev"`. The route
returns the approval record, and follows the rules in
[Dev route rules](#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:

| State                               | Field on `payload.inClient`                                         | Rendering                                                  |
| ----------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
| Approved                            | `{ granted: true, versionHash, approvedBy, at, review? }`           | Host-page mount                                            |
| Approval exists but version changed | `{ granted: false, versionHash, reason: "version-changed" }`        | Jailed, with a loud in-surface notice                      |
| Review-kind app not yet granted     | `{ granted: false, versionHash, reason: "pending-review", review }` | No source shipped; the review standing is rendered instead |
| No approval ever recorded           | field omitted                                                       | Jailed, silent (the default)                               |

`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:

```ts theme={null}
type InClientApproval = {
  appId: string;
  versionHash: string;   // pins the exact content approved
  approvedBy: string;    // principal id
  at: string;            // ISO timestamp
};
```

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

```json theme={null}
{
  "appId": "app_…",
  "versionHash": "…",
  "pins": [{ "slot": "…", "component": "…",
             "baseHash": "…", "baselineHash": "…",
             "drifted": false, "diff": "…" }],
  "generated": [{ "component": "…", "diff": "…" }]
}
```

### 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.

## Related reading

* [Generated UI and apps](/capabilities/generated-ui): the ladder and the default
  jail.
* [Tools and safety](/concepts/tools-and-safety): how host tool calls are
  gated regardless of venue.
* [HTTP routes](/reference/http-routes): wire endpoints for ship-diff and the
  dev injection seam.
