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

On this page