rect.sh

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. 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 and an 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.",
    "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 and example are required; description, agentInstructions, and stateSchema 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

stateSchema is optional JSON Schema-shaped metadata that describes the complete view model to agents, template search, and other tooling. Add it when field names alone do not make the state shape and allowed values obvious.

{
  "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.

stateSchema is currently descriptive metadata, not runtime validation: the host does not reject rect_issue, merge patches, or UI writes merely because they do not conform to it. Put operating instructions in rect.agent.md, and keep action argument validation in each action's separate inputSchema.

On this page