# Rendering Architecture > Definitive design document for Originmain's two live-rendering modes. > Created 2026-04-29. Supersedes the original Layer 2 approach (direct > cross-origin iframe script injection, which is non-functional — see > "Why the Previous Approach Failed" below). --- ## Table of Contents 1. [Overview](#1-overview) 2. [Why the Previous Approach Failed](#2-why-the-previous-approach-failed) 3. [Architecture Overview](#3-architecture-overview) 4. [Mode A — CLI Proxy (Live Development)](#4-mode-a--cli-proxy-live-development) 5. [Mode B — Live SDK (Preview Deployments / GitHub)](#5-mode-b--live-sdk-preview-deployments--github) 6. [PostMessage Protocol (Unchanged)](#6-postmessage-protocol-unchanged) 7. [Artboard ID Routing via `window.name`](#7-artboard-id-routing-via-windowname) 8. [Sequence Diagrams](#8-sequence-diagrams) 9. [Edge Cases](#9-edge-cases) 10. [Package Reference](#10-package-reference) 11. [Migration Notes](#11-migration-notes) --- ## 1. Overview Originmain needs to render a user's **running React application** inside an artboard iframe on the canvas, while simultaneously extracting the React **fiber tree** (component names, props, DOM rects) so the Inspector, SelectionOverlay, and Diff Engine can operate on live data. Two rendering modes serve different workflows: | Mode | When to use | How the app loads | Who installs the fiber hook | |------|-------------|-------------------|----------------------------| | **A — CLI Proxy** | Active local development | User's dev server proxied through `@originmain/cli` | The proxy injects it into every HTML response | | **B — Live SDK** | Preview deployments, CI, async review | Vercel/Netlify preview URL or any hosted app | `@originmain/live` npm package (imported before React) | Both modes use the **same postMessage protocol** (`packages/renderer/src/protocol.ts`) and the **same `LiveArtboard.tsx`** component. The only difference is who is responsible for installing the fiber hook before React loads. --- ## 2. Why the Previous Approach Failed The original Layer 2 implementation attempted direct cross-origin script injection via `iframe.contentDocument`. This fails for three independent reasons, any one of which is fatal: ### 2.1 Cross-Origin DOM Access Is Blocked `LiveArtboard.tsx` called `iframe.contentDocument.createElement('script')`. When the iframe's origin (`http://localhost:3000`) differs from the host (`http://localhost:3001` or `https://app.originmain.com`), the browser returns `null` for `contentDocument`. The try/catch in `injectFiberHook()` silently swallowed this error. ### 2.2 Circular READY Handshake The `READY` message was generated by the injected fiber hook script itself (line 106 in `fiber-hook.ts`). But the host waited for `READY` before injecting the script. Neither side could proceed first. ### 2.3 Hook Must Precede React Module Evaluation `__REACT_DEVTOOLS_GLOBAL_HOOK__` must exist before React's module body runs. React checks for this object exactly once, at import time. Any script injected after React has loaded is too late — React will never register with a hook installed after the fact. ### 2.4 X-Frame-Options / CSP Blocking Many apps (including GitHub pages) send `X-Frame-Options: DENY` or `Content-Security-Policy: frame-ancestors 'none'`. The iframe renders blank. No browser-side workaround exists. --- ## 3. Architecture Overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ Originmain Canvas (host) │ │ │ │ ┌──────────────┐ postMessage ┌──────────────────────┐ │ │ │ LiveArtboard │ ◄──────────────── │ iframe │ │ │ │ (listens) │ READY │ name="om:{id}" │ │ │ │ │ FIBER_TREE_UPDATE │ │ │ │ │ │ COMPONENT_SELECTED │ ┌────────────────┐ │ │ │ └──────────────┘ │ │ Fiber Hook │ │ │ │ │ │ │ (reads │ │ │ │ ▼ │ │ window.name) │ │ │ │ ┌──────────┐ │ └────────────────┘ │ │ │ │Inspector │ │ ▲ │ │ │ │Overlay │ │ │ installed │ │ │ │DiffEngine│ │ ┌──┴─────────┐ │ │ │ └──────────┘ │ │ Mode A: │ │ │ │ │ │ CLI Proxy │ │ │ │ │ │ Mode B: │ │ │ │ │ │ Live SDK │ │ │ │ │ └────────────┘ │ │ │ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` Key invariant: **the fiber hook is always installed before React loads**. The two modes differ only in *who* ensures this. --- ## 4. Mode A — CLI Proxy (Live Development) ### 4.1 User Experience ```bash # Terminal 1 — user's dev server (as usual) npm run dev # → http://localhost:3000 # Terminal 2 — Originmain proxy npx @originmain/cli dev --target http://localhost:3000 # → Proxy listening on http://localhost:4170 # → Paste this URL into your Originmain artboard ``` The user enters `http://localhost:4170` as the artboard's render URL in Originmain. The iframe loads through the proxy. ### 4.2 What the Proxy Does For every HTTP request: 1. **Forward** the request to the target dev server (`localhost:3000`) 2. **Strip** response headers that block iframing: - `X-Frame-Options` (any value) - `Content-Security-Policy` `frame-ancestors` directive 3. **For HTML responses only** (Content-Type contains `text/html`): - Strip `Accept-Encoding` from the outgoing request so the target sends uncompressed HTML (avoids decompressing gzip/brotli) - Buffer the full response body - Inject the fiber hook `