'use client'; /** * IsolationFrame — Phase 3 full implementation * * Renders an isolated view of a single React component inside an iframe. * The CLI proxy serves the isolation page at: * `/__om_isolation__?component=&file=` * * When the indexer is not ready, shows an informative placeholder. When it is * ready, renders the isolation iframe. The host sends UPDATE_ISOLATION_PROPS * messages so the designer can tweak props live from the Inspector. * * spec: SOURCE-AWARE-CANVAS.md Phase 3 §3.5 "Component Isolation" */ import { useRef, useEffect, useCallback } from 'react'; import { useCanvas } from '@/store/canvas'; import { useCanvasTheme } from '@/store/canvasTheme'; import { createHostEnvelope } from '@originmain/renderer'; interface IsolationFrameProps { /** Artboard ID (used for message routing). */ artboardId: string; /** Display name of the component to isolate — passed as ?component= param. */ componentName: string; /** Workspace-relative source file path — passed as ?file= param. */ componentFile: string; /** Base URL of the CLI proxy (e.g. "http://localhost:4170"). */ proxyUrl: string; /** Current prop overrides to forward into the isolation page. */ isolationProps?: Record; width: number; height: number; } /** Builds the isolation page URL from the proxy base and component params. */ function buildIsolationUrl( proxyUrl: string, componentName: string, componentFile: string, ): string { const base = proxyUrl.replace(/\/$/, ''); const params = new URLSearchParams({ component: componentName, file: componentFile, }); return `${base}/__om_isolation__?${params.toString()}`; } export function IsolationFrame({ artboardId, componentName, componentFile, proxyUrl, isolationProps, width, height, }: IsolationFrameProps) { const T = useCanvasTheme(); const { indexerStatus } = useCanvas(); const iframeRef = useRef(null); // ── Forward prop overrides to the isolation iframe ───────────────────────── // Sends UPDATE_ISOLATION_PROPS whenever isolationProps changes so the // component re-renders with the new values without a full page reload. const sendIsolationProps = useCallback(() => { const iframe = iframeRef.current; if (!iframe?.contentWindow) return; try { const msg = createHostEnvelope(artboardId, { type: 'UPDATE_ISOLATION_PROPS', props: isolationProps ?? {}, }); iframe.contentWindow.postMessage(msg, '*'); } catch { /* iframe may not be ready yet — will retry on next onLoad */ } }, [artboardId, isolationProps]); useEffect(() => { sendIsolationProps(); }, [sendIsolationProps]); // ── Indexer not running — show placeholder ───────────────────────────────── if (indexerStatus !== 'ready') { return (
{/* Isolation icon */}
Isolation mode requires CLI indexer Run{' '} npx @originmain/cli dev {' '}to enable component isolation.
{/* Status badge */}
Indexer offline
); } // ── Indexer ready — render isolation iframe ──────────────────────────────── const src = buildIsolationUrl(proxyUrl, componentName, componentFile); return (