HyperGen

Iframe Bootstrap Document

The complete HTML document that MUST be loaded into the HyperGen sandboxed iframe, including HTMX, SSE extension, postMessage bridge, and base styles.

The bootstrap document is the complete HTML page loaded into the sandboxed iframe. It provides the runtime environment for agent-generated HTML: HTMX for interactivity, the SSE extension for streaming, a postMessage bridge for host communication, and base styles with CSS variable support.

Required Elements

A conforming bootstrap document MUST include all of the following:

ElementPurpose
HTMX scriptProcesses hx-* attributes for interactivity
HTMX SSE extension scriptEnables sse-connect and sse-swap for streaming
SSE root element (#hg-root)The container that connects to the SSE endpoint and receives streamed fragments
postMessage bridgeHandles hg:theme and hg:destroy messages from the host
ResizeObserverNotifies the host of content size changes via hg:resize messages
Connection status scriptToggles hg-connected / hg-disconnected CSS classes on <html>
Base stylesResets (box-sizing, margin: 0), CSS variable defaults, loading indicator styles

Minimal Conforming Bootstrap

The following is the minimal HTML document that satisfies all requirements. Implementations MAY add to this but MUST NOT remove any required element.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <script src="https://unpkg.com/htmx.org@2"></script>
  <script src="https://unpkg.com/htmx-ext-sse@2"></script>
  <style>
    :root {
      /* Theme variables injected by host via postMessage */
    }
    *, *::before, *::after { box-sizing: border-box; }
    body {
      margin: 0;
      font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
      line-height: 1.5;
      color: var(--hg-text, #1a1a1a);
      background: var(--hg-background, transparent);
    }
    .hg-indicator { display: none; }
    .htmx-request .hg-indicator,
    .htmx-request.hg-indicator { display: inline-block; }
    .hg-disconnected body { opacity: 0.7; }
  </style>
</head>
<body>
  <div id="hg-root"
       hx-ext="sse"
       sse-connect="/api/agent/stream"
       sse-swap="message"
       hx-swap="innerHTML">
  </div>

  <!-- Connection status -->
  <script>
    (function() {
      var root = document.documentElement;
      function setConnected(v) {
        if (v) {
          root.classList.add("hg-connected");
          root.classList.remove("hg-disconnected");
        } else {
          root.classList.remove("hg-connected");
          root.classList.add("hg-disconnected");
        }
      }
      setConnected(false);
      document.body.addEventListener("htmx:sseOpen", function() {
        setConnected(true);
      });
      document.body.addEventListener("htmx:sseError", function() {
        setConnected(false);
      });
      document.body.addEventListener("htmx:sseClose", function() {
        setConnected(false);
      });
    })();
  </script>

  <!-- postMessage bridge -->
  <script>
    (function() {
      var hostOrigin = "*";

      window.addEventListener("message", function(e) {
        if (hostOrigin !== "*" && e.origin !== hostOrigin) return;
        var msg = e.data;
        if (!msg || typeof msg.type !== "string") return;

        switch (msg.type) {
          case "hg:theme":
            if (msg.vars && typeof msg.vars === "object") {
              Object.keys(msg.vars).forEach(function(key) {
                document.documentElement.style.setProperty(key, msg.vars[key]);
              });
            }
            break;
          case "hg:destroy":
            var root = document.getElementById("hg-root");
            if (root) {
              root.removeAttribute("sse-connect");
              root.removeAttribute("hx-ext");
              if (typeof htmx !== "undefined") {
                htmx.trigger(root, "htmx:sseClose");
              }
            }
            break;
        }
      });

      // ResizeObserver
      var ro = new ResizeObserver(function() {
        window.parent.postMessage({
          type: "hg:resize",
          height: document.documentElement.scrollHeight,
          width: document.documentElement.scrollWidth
        }, hostOrigin);
      });
      ro.observe(document.body);

      // Navigation bridge
      document.addEventListener("click", function(e) {
        var target = e.target;
        while (target && target !== document.body) {
          if (target.getAttribute &&
              target.getAttribute("data-hg-navigate")) {
            e.preventDefault();
            window.parent.postMessage({
              type: "hg:navigate",
              url: target.getAttribute("data-hg-navigate")
            }, hostOrigin);
            return;
          }
          target = target.parentElement;
        }
      });

      // Data bridge
      document.addEventListener("hg:data", function(e) {
        window.parent.postMessage({
          type: "hg:data",
          payload: e.detail
        }, hostOrigin);
      });
    })();
  </script>
</body>
</html>

Sandbox Attribute Requirements

The iframe element MUST use the sandbox attribute with exactly these permissions:

<iframe sandbox="allow-scripts allow-same-origin allow-forms"
        src="about:blank"
        style="width: 100%; border: none;">
</iframe>

Required permissions

PermissionReason
allow-scriptsHTMX requires JavaScript to process hx-* attributes and manage SSE connections.
allow-same-originHTMX must make HTTP requests (GET, POST) to the agent server. Without same-origin, XHR and fetch are blocked.
allow-formsHTMX form submissions (hx-post on <form> elements) require form submission capability.

Explicitly denied permissions

The following permissions MUST NOT be granted unless the host application has a documented, specific need:

PermissionRisk if granted
allow-top-navigationAllows the iframe to redirect the entire page.
allow-popupsAllows the iframe to open new browser windows.
allow-modalsAllows alert(), confirm(), prompt() to block the host application.
allow-pointer-lockAllows the iframe to capture the cursor.
allow-downloadsAllows the iframe to trigger file downloads.

Host applications MAY add permissions beyond the required three by appending to the sandbox attribute. Each additional permission weakens the security boundary and SHOULD be documented.

Content Security Policy

The host application SHOULD serve the bootstrap document with a Content-Security-Policy that restricts the iframe's capabilities.

default-src 'none';
script-src 'self' 'unsafe-inline' https://unpkg.com/htmx.org https://unpkg.com/htmx-ext-sse;
style-src 'self' 'unsafe-inline';
connect-src <agent-server-origin>;
img-src 'self' data: https:;
font-src 'self' data:;
DirectivePurpose
default-src 'none'Denies everything not explicitly allowed.
script-srcAllows inline scripts (needed for the bridge) and HTMX from a trusted CDN.
style-srcAllows inline styles (needed for theme injection and agent-generated <style> blocks).
connect-srcRestricts HTTP/SSE connections to the known agent server origin. This is the primary defense against data exfiltration. Replace <agent-server-origin> with the actual origin.
img-srcAllows images from same origin, data URIs, and HTTPS.
font-srcAllows fonts from same origin and data URIs.

'unsafe-inline' is required for both script-src and style-src because the bootstrap document uses inline scripts and agents generate inline styles. If the implementation can use nonces or hashes for the bootstrap scripts, it SHOULD prefer those over 'unsafe-inline'.

HTMX Version Requirements

  • The bootstrap document MUST load HTMX version 2.x (2.0.0 or later).
  • The bootstrap document MUST load the HTMX SSE extension version 2.x.
  • Both scripts MAY be loaded from a CDN (unpkg, cdnjs, jsdelivr) or served locally.
  • The HTMX script MUST be loaded before the SSE extension script (the extension depends on the HTMX core).

CDN URLs

The reference implementation uses unpkg with major-version pinning:

<script src="https://unpkg.com/htmx.org@2"></script>
<script src="https://unpkg.com/htmx-ext-sse@2"></script>

Production deployments SHOULD pin to a specific version (e.g., htmx.org@2.0.4) and MAY use Subresource Integrity (SRI) hashes for additional security.

Connection Status CSS Classes

The bootstrap document MUST include a script that toggles CSS classes on the <html> element based on the SSE connection state:

ClassApplied when
hg-connectedThe SSE connection is open (htmx:sseOpen event received).
hg-disconnectedThe SSE connection is closed or errored (htmx:sseError or htmx:sseClose received).

The initial state MUST be hg-disconnected (applied before the SSE connection opens).

Agents MAY use these classes to style content conditionally:

.hg-disconnected .status-indicator { color: red; }
.hg-connected .status-indicator { color: green; }

Loading the Bootstrap

The bootstrap document can be loaded into the iframe in two ways:

The host generates the bootstrap HTML server-side and sets it as the iframe's srcdoc attribute. This avoids an extra HTTP request.

iframe.srcdoc = bootstrapHtml;

2. Via src URL

The host points the iframe to a URL that serves the bootstrap document. This is useful when the bootstrap needs to be dynamically generated per-request.

iframe.src = "/api/agent/bootstrap";

When using src, the server MUST respond with Content-Type: text/html and the complete bootstrap document.

Customization Points

Implementations MAY customize the bootstrap document in the following ways:

  • sseEndpoint — The URL in sse-connect. MUST be set to the agent's SSE endpoint.
  • sseEventName — The value of sse-swap. Defaults to "message".
  • swapStrategy — The value of hx-swap on the root element. Defaults to "innerHTML".
  • hostOrigin — The origin used for postMessage validation. Defaults to "*". Production deployments SHOULD set this to the actual host origin.
  • themeVars — Initial CSS variable values set in the :root style block.
  • extraHead — Additional content injected into <head> (stylesheets, meta tags).
  • HTMX CDN URLs — Alternative CDN or self-hosted HTMX URLs.

See the reference server implementation (hypergen-server.ts, bootstrapHtml()) for a parameterized version of the bootstrap document.

On this page