# SandboxedFrame
A locked box for a mini app made by someone else, with clear limits and asks before it does anything.
Also known as: iframe, sandbox, embed, artifact frame.
Status: stable. Page: https://lkb00.github.io/tattva/#component-sandboxed-frame
Sometimes an assistant makes a whole small page, not a screen from our parts. SandboxedFrame runs it in a locked box that cannot see your account. The page can ask to open a link or take a step, but only if it declared that up front, and only after the person says yes. It says who made it and what it can do in plain words.
## When to use it

A locked box for a small page the assistant wrote itself, outside our approved parts. The page cannot see the account, and anything it asks for needs a yes from the person each time.

## Use it for

- A mini app, like a budget calculator, that our approved parts cannot build.
- A page from another site that should only show things.
- A page that may ask to open a link or run a tool, with the person's approval.

## Not for

- A screen our approved parts can draw: use `gen-ui-renderer`
- Asking for passwords or payment details

## Anatomy

- Generated by label
- List of what the page asked for
- The locked frame
- Permission question (Allow, Don't allow)
- What this can do line

## Do

- Use it only when our approved parts cannot do the job.
- Declare exactly what the page may ask for, and keep the list short.
- Keep the sandbox without allow-same-origin.
- Show who made the page and what it can do, next to the frame.

## Avoid

- Don't build the page's HTML into our own page. Never use dangerouslySetInnerHTML for it.
- Don't let the page open links or take steps without asking, even for permissions it declared.
- Don't trust a message just because it says it is from the frame. Check who sent it and from where.
- Don't ask for passwords or payment details inside a generated page.

## On a phone

- The frame fills the width, and the page inside decides its own layout, so build it for a narrow screen.
- The frame height follows the page up to the maximum, 640px by default, and scrolls inside after that.
- The permission buttons wrap under the question. They keep their size and have a 44px tap area on a touch screen.

## Accessibility: built in

- The frame has a name, so screen readers say what it holds.
- Who made it, what it asked for and what it can do are plain text next to the frame.
- A permission question moves focus to Allow, with Don't allow next to it.
- A clear outline shows when someone tabs into the frame.
- The frame grows to fit the page but stays between a smallest and a largest height.

## Accessibility: what you need to do

- Give it a title that says what the page holds. Screen readers use it to name the frame.
- Check the page inside for accessibility yourself. The frame cannot fix it.
- Prefer GenUIRenderer when it can do the job, because its parts are already accessible.

## Properties

| Name | Type | Default | Description |
|---|---|---|---|
| source (required) | `string` |  | Who made the page. Shown as 'Generated by <source>'. |
| title (required) | `string` |  | Accessible name of the frame. |
| srcDoc | `string` |  | The page as a string. Its origin is opaque (event.origin is 'null'). Use this or src. |
| src | `string` |  | Address of a page. Messages are accepted only from that URL's origin. Use this or srcDoc. |
| permissions | `("open-links" \| "call-tools")[]` | `[]` | What the page may ask for. Shown to the person. Each request still needs a yes. |
| allowedTools | `string[]` | `[]` | Tool names the page may ask for. Other names are ignored. |
| onOpenLink | `(url: string) => void` |  | Called after the person agrees. Default opens a new tab with noopener and noreferrer. Only http and https links get this far. |
| onCallTool | `(name: string, args: Record<string, unknown>) => unknown \| Promise<unknown>` |  | Called after the person agrees. The page gets a 'tattva:tool-result' message with ok true or false. |
| minHeight | `number` | `120` | Smallest height in px. Resize messages are clamped to this. |
| maxHeight | `number` | `640` | Largest height in px. Resize messages are clamped to this. |
| initialHeight | `number` | `240` | Height before the page sends a resize message. |
| allowForms | `boolean` | `false` | Adds allow-forms to the sandbox. |
| allowSameOrigin | `boolean` | `false` | Adds allow-same-origin to the sandbox. With scripts this lets the page remove its own sandbox. Leave off for pages you did not write. |
| summary | `string` |  | Replaces the plain-language 'What this can do' sentence. |
| labels | `SandboxedFrameLabels` |  | Overrides: generatedBy, whatItCanDo, allow, deny. |
| className | `string` |  | Extra classes on the wrapper. |

## States

- permission request: When the page asks to open a link or call a tool you allowed, a prompt with Allow and Don't allow appears below the frame.
- no permissions: With no permissions, the note says the page can only show things on screen.
- resizing: The frame grows or shrinks to the height the page reports, between minHeight and maxHeight.
- focus: The frame shows a focus ring on keyboard focus.

## Tokens

- `--surface`
- `--border`
- `--attention-soft`
- `--attention-fg`
- `--radius-card`

## Examples

### A small page that asks to open a link

The page tells us how tall it is, so the frame fits it. Press its button and the frame asks before opening the link.

```tsx
<SandboxedFrame
  source="Trip helper"
  title="Trip budget"
  srcDoc={html}
  permissions={["open-links"]}
/>

// Inside the page:
parent.postMessage({ type: "tattva:resize", height: document.documentElement.scrollHeight }, "*");
parent.postMessage({ type: "tattva:open-link", url: "https://example.com/" }, "*");
```

### A page that can only show things

No permissions were declared, so every request from the page is ignored.

```tsx
<SandboxedFrame source="Trip helper" title="Packing list" srcDoc={html} />
```

Source: src/organisms/SandboxedFrame.tsx
