The view contract
The sandbox constraints every view must respect.
A view runs inside a sandboxed iframe with host-owned CSP. This is not a
detail — it shapes everything you build. The host CSP/sandbox is the runtime
security boundary; the @rectsh/rect/vite plugin only catches unsupported URL
forms at build time.
Self-contained output
A view ships as a bundle: an index.html and its assets, which the host
serves together. The versioned Rect protocol runtime is host-owned and injected
separately, so it is not part of this bundle. Prefer relative references —
./assets/app.js, a data: URI, a blob:, or a #anchor. The build fails unsupported URL forms:
http:// and protocol-relative //; runtime https:// loads are governed by
the host CSP/sandbox.
Practically: import assets when you can so the bundler emits them into the
bundle. (Setting base: './' keeps Vite asset URLs relative; the
@rectsh/rect/vite plugin does this for you.)
Prefer a single file? rect({ singleFile: true }) inlines everything into one
index.html instead — handy for pasting or hand-editing. Either output obeys
the same unsupported-URL-form lint.
State goes through the host
Reads and writes go to the parent host over postMessage, and the host does the
persistence. Direct runtime https:// requests are a host policy decision, not
a Vite plugin rule.
For view state, use the store (connect()). It speaks the host protocol for
you.
No same-origin storage
localStorage, sessionStorage, and cookies throw in an opaque-origin document.
Keep all state in the view model — that's what gets persisted and synced anyway.
Size cap
The compiled bundle must be ≤ 20 MB. React + your app is ~200 KB, so there's plenty of room, but large embedded assets add up. Prefer SVG and compressed images; reach for a lighter framework (Preact, Solid) if you need a tiny bundle.
The self-describing spec
Every view embeds an inert spec block so the platform (and agents) know its name, complete state schema, and example view model:
<script type="application/rect-view+json">
{
"name": "Note",
"description": "Use when an agent and a person need to draft a shared note.",
"stateSchema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"body": { "type": "string" }
},
"required": ["title", "body"],
"additionalProperties": false
},
"example": { "title": "Untitled", "body": "" }
}
</script>You don't write this by hand — declare it in rect.view.json (or the plugin's
spec option) and the build injects and validates it. name, example, and
stateSchema are required; description and agentInstructions are optional.
SDK projects normally author agentInstructions in rect.agent.md; the Vite
plugin reads that Markdown automatically. Use description only to tell an
agent when to choose the Rect, and use the Markdown file for instructions on
creating and updating it.
Describe state with stateSchema
Every Rect requires a stateSchema: JSON Schema-shaped metadata that describes
the complete view model to agents, template search, App composition, and other
tooling. Even a small Rect must declare its stored fields explicitly.
{
"name": "Review queue",
"description": "Use when a person needs to review a prepared queue.",
"stateSchema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Human-readable queue title"
},
"status": {
"type": "string",
"enum": ["draft", "ready", "completed"],
"description": "Current queue lifecycle state"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"summary": { "type": "string" }
},
"required": ["id", "summary"],
"additionalProperties": false
}
}
},
"required": ["title", "status", "items"],
"additionalProperties": false
},
"example": {
"title": "Launch review",
"status": "draft",
"items": []
}
}Keep four things aligned: stateSchema, example, the TypeScript state type,
and the fields the UI and actions actually read or write. Prefer an object root,
declare properties and required, use enum for lifecycle values, and add
short field descriptions where meaning or format is not self-evident.
The build and publish gate compile stateSchema and require example to match
it. App publication also validates each Screen's reusable initialViewModel
against the pinned schema. After an instance exists, the host does not reject
rect_issue, merge patches, or UI writes merely because they do not conform to
the schema. Put operating instructions in rect.agent.md, and keep action
argument validation in each action's separate inputSchema.