{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "isolated-frame",
  "title": "Isolated Frame",
  "description": "Secure iframe component for rendering untrusted output content. Uses blob URLs with sandbox restrictions to isolate JavaScript execution. Includes message bridge for parent-child communication and widget comm protocol support.",
  "registryDependencies": [
    "@nteract/widget-store"
  ],
  "files": [
    {
      "path": "registry/outputs/isolated/index.ts",
      "content": "export {\n  CommBridgeManager,\n  createCommBridgeManager,\n} from \"./comm-bridge-manager\";\n// Message protocol types\nexport type {\n  ClearMessage,\n  EvalMessage,\n  EvalResultMessage,\n  IframeErrorMessage,\n  // Utilities\n  IframeMessage,\n  // Iframe → Parent\n  IframeToParentMessage,\n  LinkClickMessage,\n  MessageType,\n  // Parent → Iframe\n  ParentToIframeMessage,\n  PingMessage,\n  PongMessage,\n  ReadyMessage,\n  RenderCompleteMessage,\n  RenderMessage,\n  RenderPayload,\n  ResizeMessage,\n  ThemeMessage,\n  WidgetStateMessage,\n  WidgetUpdateMessage,\n} from \"./frame-bridge\";\nexport { isIframeMessage, isMessageType } from \"./frame-bridge\";\nexport type { FrameHtmlOptions } from \"./frame-html\";\n// HTML template generator\nexport { createFrameBlobUrl, generateFrameHtml } from \"./frame-html\";\n// Security testing component\nexport { IsolationTest } from \"./IsolationTest\";\nexport type { IsolatedFrameHandle, IsolatedFrameProps } from \"./isolated-frame\";\nexport { IsolatedFrame } from \"./isolated-frame\";\n// Provider and hook for renderer bundle\nexport {\n  IsolatedRendererProvider,\n  useIsolatedRenderer,\n} from \"./isolated-renderer-context\";\n",
      "type": "registry:lib",
      "target": "components/isolated/index.ts"
    },
    {
      "path": "registry/outputs/isolated/isolated-frame.tsx",
      "content": "\"use client\";\n\nimport {\n  forwardRef,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useRef,\n  useState,\n} from \"react\";\nimport type {\n  IframeToParentMessage,\n  ParentToIframeMessage,\n  RenderPayload,\n} from \"./frame-bridge\";\nimport { isIframeMessage } from \"./frame-bridge\";\nimport { createFrameBlobUrl } from \"./frame-html\";\nimport { useIsolatedRenderer } from \"./isolated-renderer-context\";\n\nexport interface IsolatedFrameProps {\n  /**\n   * Unique ID for this frame (used for message routing).\n   */\n  id?: string;\n\n  /**\n   * Initial content to render when the frame is ready.\n   */\n  initialContent?: RenderPayload;\n\n  /**\n   * Whether to use dark mode styling.\n   */\n  darkMode?: boolean;\n\n  /**\n   * Minimum height of the iframe in pixels.\n   * @default 24\n   */\n  minHeight?: number;\n\n  /**\n   * Maximum height of the iframe in pixels.\n   * @default 2000\n   */\n  maxHeight?: number;\n\n  /**\n   * Additional CSS classes for the iframe container.\n   */\n  className?: string;\n\n  /**\n   * Callback when the iframe is ready to receive messages.\n   */\n  onReady?: () => void;\n\n  /**\n   * Callback when the iframe content resizes.\n   */\n  onResize?: (height: number) => void;\n\n  /**\n   * Callback when a link is clicked in the iframe.\n   */\n  onLinkClick?: (url: string, newTab: boolean) => void;\n\n  /**\n   * Callback when the user double-clicks in the iframe.\n   */\n  onDoubleClick?: () => void;\n\n  /**\n   * Callback when a widget state update is sent from the iframe.\n   */\n  onWidgetUpdate?: (commId: string, state: Record<string, unknown>) => void;\n\n  /**\n   * Callback when an error occurs in the iframe.\n   */\n  onError?: (error: { message: string; stack?: string }) => void;\n\n  /**\n   * Callback for all messages from the iframe (for debugging or custom handling).\n   */\n  onMessage?: (message: IframeToParentMessage) => void;\n}\n\nexport interface IsolatedFrameHandle {\n  /**\n   * Send a message to the iframe.\n   */\n  send: (message: ParentToIframeMessage) => void;\n\n  /**\n   * Send content to render in the iframe.\n   */\n  render: (payload: RenderPayload) => void;\n\n  /**\n   * Evaluate code in the iframe (for bootstrap/injection).\n   */\n  eval: (code: string) => void;\n\n  /**\n   * Update theme settings in the iframe.\n   */\n  setTheme: (isDark: boolean) => void;\n\n  /**\n   * Clear all content in the iframe.\n   */\n  clear: () => void;\n\n  /**\n   * Search for text within the iframe's rendered content.\n   * Pass empty string to clear search highlights.\n   */\n  search: (query: string, caseSensitive?: boolean) => void;\n\n  /**\n   * Navigate to a specific search match by index.\n   */\n  searchNavigate: (matchIndex: number) => void;\n\n  /**\n   * Whether the iframe is ready to receive messages.\n   * True after the React renderer bundle is initialized.\n   */\n  isReady: boolean;\n\n  /**\n   * Whether the iframe bootstrap HTML is loaded.\n   * True before the React renderer bundle is loaded.\n   */\n  isIframeReady: boolean;\n}\n\n/**\n * Sandbox attributes for the isolated iframe.\n *\n * CRITICAL: Do NOT include 'allow-same-origin' - this would give the iframe\n * access to the parent's origin and Tauri APIs.\n */\nconst SANDBOX_ATTRS = [\n  \"allow-scripts\", // Required for rendering interactive content\n  \"allow-downloads\", // Allow file downloads (e.g., from widgets)\n  \"allow-forms\", // Allow form submissions\n  \"allow-pointer-lock\", // For interactive visualizations\n  \"allow-popups\", // Allow window.open (for links)\n  \"allow-popups-to-escape-sandbox\", // Popups should be unrestricted\n  \"allow-modals\", // Allow alert/confirm/prompt\n].join(\" \");\n\n/**\n * IsolatedFrame component - Renders untrusted content in a secure iframe.\n *\n * Uses a blob: URL with sandbox restrictions to ensure the iframe content\n * cannot access Tauri APIs or the parent DOM. Communication happens via\n * postMessage.\n *\n * **Requires** `IsolatedRendererProvider` to be present in the component tree.\n *\n * @example\n * ```tsx\n * // In your app root or layout:\n * <IsolatedRendererProvider basePath=\"/isolated\">\n *   <App />\n * </IsolatedRendererProvider>\n *\n * // Then use IsolatedFrame anywhere:\n * const frameRef = useRef<IsolatedFrameHandle>(null);\n *\n * <IsolatedFrame\n *   ref={frameRef}\n *   darkMode={true}\n *   onReady={() => {\n *     frameRef.current?.render({\n *       mimeType: \"text/html\",\n *       data: \"<h1>Hello from isolated frame!</h1>\"\n *     });\n *   }}\n *   onResize={(height) => console.log(\"New height:\", height)}\n * />\n * ```\n */\nexport const IsolatedFrame = forwardRef<\n  IsolatedFrameHandle,\n  IsolatedFrameProps\n>(function IsolatedFrame(\n  {\n    id,\n    initialContent,\n    darkMode = true,\n    minHeight = 24,\n    maxHeight = 2000,\n    className = \"\",\n    onReady,\n    onResize,\n    onLinkClick,\n    onDoubleClick,\n    onWidgetUpdate,\n    onError,\n    onMessage,\n  },\n  ref,\n) {\n  // Get renderer bundle from context (provided by IsolatedRendererProvider)\n  const {\n    rendererCode,\n    rendererCss,\n    isLoading: providerLoading,\n    error: providerError,\n  } = useIsolatedRenderer();\n  const iframeRef = useRef<HTMLIFrameElement>(null);\n  const [blobUrl, setBlobUrl] = useState<string | null>(null);\n  // Track iframe ready (bootstrap HTML loaded)\n  const [isIframeReady, setIsIframeReady] = useState(false);\n  // Track renderer ready (React bundle initialized)\n  const [isReady, setIsReady] = useState(false);\n  // Use ref to track ready state for send callback (avoids stale closure)\n  const isReadyRef = useRef(false);\n  const [height, setHeight] = useState(minHeight);\n\n  // Queue messages until iframe is ready\n  const pendingMessagesRef = useRef<ParentToIframeMessage[]>([]);\n  // Track if we've started bootstrapping to avoid double-fetch\n  const bootstrappingRef = useRef(false);\n\n  // Track initial darkMode for blob URL (don't recreate blob on theme change)\n  const initialDarkModeRef = useRef(darkMode);\n\n  // Create blob URL on mount (only once, with initial darkMode)\n  useEffect(() => {\n    const url = createFrameBlobUrl({ darkMode: initialDarkModeRef.current });\n    setBlobUrl(url);\n\n    return () => {\n      URL.revokeObjectURL(url);\n    };\n  }, []);\n\n  // Forward theme changes to iframe (without recreating the blob)\n  useEffect(() => {\n    if (isReady && iframeRef.current?.contentWindow) {\n      iframeRef.current.contentWindow.postMessage(\n        { type: \"theme\", payload: { isDark: darkMode } },\n        \"*\",\n      );\n    }\n  }, [darkMode, isReady]);\n\n  // Keep ref in sync with state (ref avoids stale closures in callbacks)\n  useEffect(() => {\n    isReadyRef.current = isReady;\n  }, [isReady]);\n\n  // Surface provider errors to consumers\n  useEffect(() => {\n    if (providerError && !providerLoading) {\n      onError?.({ message: providerError.message, stack: providerError.stack });\n    }\n  }, [providerError, providerLoading, onError]);\n\n  // Send a message to the iframe\n  // Uses ref to check ready state to avoid stale closure issues\n  const send = useCallback(\n    (message: ParentToIframeMessage) => {\n      if (!isReadyRef.current) {\n        // Queue message until ready\n        pendingMessagesRef.current.push(message);\n        return;\n      }\n\n      if (iframeRef.current?.contentWindow) {\n        iframeRef.current.contentWindow.postMessage(message, \"*\");\n      }\n    },\n    [], // No deps - uses ref instead of state\n  );\n\n  // Flush pending messages when ready\n  useEffect(() => {\n    if (isReady && pendingMessagesRef.current.length > 0) {\n      const pending = pendingMessagesRef.current;\n      pendingMessagesRef.current = [];\n      pending.forEach((msg) => {\n        if (iframeRef.current?.contentWindow) {\n          iframeRef.current.contentWindow.postMessage(msg, \"*\");\n        }\n      });\n    }\n  }, [isReady]);\n\n  // Handle messages from iframe\n  useEffect(() => {\n    const handleMessage = (event: MessageEvent) => {\n      // Verify the message is from our iframe\n      if (event.source !== iframeRef.current?.contentWindow) {\n        return;\n      }\n\n      const data = event.data;\n      if (!isIframeMessage(data)) {\n        return;\n      }\n\n      // Call generic message handler\n      onMessage?.(data);\n\n      // Handle specific message types\n      switch (data.type) {\n        case \"ready\":\n          // Iframe bootstrap HTML is loaded\n          // Renderer injection is handled by a separate useEffect\n          setIsIframeReady(true);\n          break;\n\n        case \"renderer_ready\":\n          // React renderer bundle is initialized\n          setIsReady(true);\n          onReady?.();\n          // Render initial content if provided\n          if (initialContent) {\n            iframeRef.current?.contentWindow?.postMessage(\n              { type: \"render\", payload: initialContent },\n              \"*\",\n            );\n          }\n          break;\n\n        case \"resize\":\n          if (data.payload?.height != null) {\n            const newHeight = Math.max(\n              minHeight,\n              Math.min(maxHeight, data.payload.height),\n            );\n            setHeight(newHeight);\n            onResize?.(newHeight);\n          }\n          break;\n\n        case \"link_click\":\n          if (data.payload?.url) {\n            onLinkClick?.(data.payload.url, data.payload.newTab ?? false);\n          }\n          break;\n\n        case \"dblclick\":\n          onDoubleClick?.();\n          break;\n\n        case \"widget_update\":\n          if (data.payload?.commId && data.payload?.state) {\n            onWidgetUpdate?.(data.payload.commId, data.payload.state);\n          }\n          break;\n\n        case \"error\":\n          if (data.payload) {\n            onError?.(data.payload);\n          }\n          break;\n\n        case \"eval_result\":\n          // Surface bundle eval failures to help diagnose injection issues\n          if (data.payload?.success === false) {\n            console.error(\n              \"[IsolatedFrame] Bundle eval failed:\",\n              data.payload.error,\n            );\n            onError?.({ message: `Bundle eval failed: ${data.payload.error}` });\n          }\n          break;\n      }\n    };\n\n    window.addEventListener(\"message\", handleMessage);\n    return () => window.removeEventListener(\"message\", handleMessage);\n  }, [\n    initialContent,\n    minHeight,\n    maxHeight,\n    onReady,\n    onResize,\n    onLinkClick,\n    onDoubleClick,\n    onWidgetUpdate,\n    onError,\n    onMessage,\n  ]);\n\n  // Inject renderer when iframe is ready AND bundle props are available\n  useEffect(() => {\n    if (\n      isIframeReady &&\n      !isReady &&\n      !bootstrappingRef.current &&\n      rendererCode &&\n      rendererCss &&\n      iframeRef.current?.contentWindow\n    ) {\n      bootstrappingRef.current = true;\n\n      // Inject CSS first (idempotent - checks if already loaded)\n      const cssCode = `\n        (function() {\n          if (window.__ISOLATED_CSS_LOADED__) return;\n          window.__ISOLATED_CSS_LOADED__ = true;\n          var style = document.createElement('style');\n          style.textContent = ${JSON.stringify(rendererCss)};\n          document.head.appendChild(style);\n        })();\n      `;\n      iframeRef.current.contentWindow.postMessage(\n        { type: \"eval\", payload: { code: cssCode } },\n        \"*\",\n      );\n      // Then inject JS bundle (idempotent - checks if already loaded)\n      // Use string concatenation instead of template literal to avoid issues\n      // with backticks or ${} in the bundled code\n      const jsWrapper =\n        \"(function() {\" +\n        \"if (window.__ISOLATED_RENDERER_LOADED__) return;\" +\n        \"window.__ISOLATED_RENDERER_LOADED__ = true;\" +\n        rendererCode +\n        \"})();\";\n      iframeRef.current.contentWindow.postMessage(\n        { type: \"eval\", payload: { code: jsWrapper } },\n        \"*\",\n      );\n    }\n  }, [isIframeReady, isReady, rendererCode, rendererCss]);\n\n  // Expose imperative API\n  useImperativeHandle(\n    ref,\n    () => ({\n      send,\n      render: (payload: RenderPayload) => send({ type: \"render\", payload }),\n      eval: (code: string) => send({ type: \"eval\", payload: { code } }),\n      setTheme: (isDark: boolean) =>\n        send({ type: \"theme\", payload: { isDark } }),\n      clear: () => send({ type: \"clear\" }),\n      search: (query: string, caseSensitive?: boolean) => {\n        // Search handler is in bootstrap HTML, so send directly when iframe is loaded\n        // (bypasses the isReady queue which waits for the React renderer)\n        if (iframeRef.current?.contentWindow) {\n          iframeRef.current.contentWindow.postMessage(\n            { type: \"search\", payload: { query, caseSensitive } },\n            \"*\",\n          );\n        }\n      },\n      searchNavigate: (matchIndex: number) => {\n        if (iframeRef.current?.contentWindow) {\n          iframeRef.current.contentWindow.postMessage(\n            { type: \"search_navigate\", payload: { matchIndex } },\n            \"*\",\n          );\n        }\n      },\n      isReady,\n      isIframeReady,\n    }),\n    [send, isReady, isIframeReady],\n  );\n\n  if (!blobUrl) {\n    return null;\n  }\n\n  return (\n    <iframe\n      ref={iframeRef}\n      id={id}\n      src={blobUrl}\n      sandbox={SANDBOX_ATTRS}\n      className={className}\n      data-slot=\"isolated-frame\"\n      style={{\n        width: \"100%\",\n        height: `${height}px`,\n        border: \"none\",\n        display: \"block\",\n      }}\n      title=\"Isolated output frame\"\n    />\n  );\n});\n",
      "type": "registry:component",
      "target": "components/isolated/isolated-frame.tsx"
    },
    {
      "path": "registry/outputs/isolated/frame-bridge.ts",
      "content": "/**\n * Message protocol types for parent ↔ iframe communication.\n *\n * This module defines the contract between the parent window and isolated output frames.\n * All communication happens via postMessage with structured message types.\n */\n\n// --- Message Types: Parent → Iframe ---\n\n/**\n * Bootstrap the iframe with JavaScript code.\n * Used to inject the ESM renderer bundle into the iframe.\n */\nexport interface EvalMessage {\n  type: \"eval\";\n  payload: {\n    /** JavaScript code to evaluate in the iframe context */\n    code: string;\n  };\n}\n\n/**\n * Render output content in the iframe.\n */\nexport interface RenderMessage {\n  type: \"render\";\n  payload: RenderPayload;\n}\n\nexport interface RenderPayload {\n  /** MIME type of the content (e.g., \"text/html\", \"text/markdown\") */\n  mimeType: string;\n  /** The content data (format depends on MIME type) */\n  data: unknown;\n  /** Optional metadata for the output */\n  metadata?: Record<string, unknown>;\n  /** Cell ID this output belongs to (for routing) */\n  cellId?: string;\n  /** Output index within the cell */\n  outputIndex?: number;\n  /** If true, append to existing outputs instead of replacing */\n  append?: boolean;\n  /** If true, replace all existing outputs with this single output */\n  replace?: boolean;\n}\n\n/**\n * Update widget state in the iframe.\n */\nexport interface WidgetStateMessage {\n  type: \"widget_state\";\n  payload: {\n    /** Comm ID of the widget */\n    commId: string;\n    /** Updated state to merge */\n    state: Record<string, unknown>;\n    /** Optional buffers (base64 encoded) */\n    buffers?: string[];\n  };\n}\n\n/**\n * Sync theme with the iframe.\n */\nexport interface ThemeMessage {\n  type: \"theme\";\n  payload: {\n    /** Whether dark mode is active */\n    isDark: boolean;\n    /** Optional CSS variables to inject */\n    cssVariables?: Record<string, string>;\n  };\n}\n\n/**\n * Ping the iframe (for health checks and latency measurement).\n */\nexport interface PingMessage {\n  type: \"ping\";\n  payload?: {\n    sentAt: number;\n  };\n}\n\n/**\n * Clear all rendered content in the iframe.\n */\nexport interface ClearMessage {\n  type: \"clear\";\n}\n\n// --- Widget Comm Protocol: Parent → Iframe ---\n\n/**\n * Forward a comm_open message to the iframe.\n * Sent when a widget model is created by the kernel.\n */\nexport interface CommOpenMessage {\n  type: \"comm_open\";\n  payload: {\n    /** Comm ID of the widget */\n    commId: string;\n    /** Target name (e.g., \"jupyter.widget\") */\n    targetName: string;\n    /** Initial widget state */\n    state: Record<string, unknown>;\n    /** Buffer paths for binary data reconstruction */\n    bufferPaths?: string[][];\n    /** Binary buffers (transferred via structured clone) */\n    buffers?: ArrayBuffer[];\n  };\n}\n\n/**\n * Forward a comm_msg to the iframe.\n * Sent for state updates and custom messages from kernel.\n */\nexport interface CommMsgMessage {\n  type: \"comm_msg\";\n  payload: {\n    /** Comm ID of the widget */\n    commId: string;\n    /** Message method: \"update\" or \"custom\" */\n    method: \"update\" | \"custom\";\n    /** State patch (for update) or custom content (for custom) */\n    data: Record<string, unknown>;\n    /** Buffer paths for binary data reconstruction */\n    bufferPaths?: string[][];\n    /** Binary buffers (transferred via structured clone) */\n    buffers?: ArrayBuffer[];\n  };\n}\n\n/**\n * Forward a comm_close message to the iframe.\n * Sent when a widget is destroyed by the kernel.\n */\nexport interface CommCloseMessage {\n  type: \"comm_close\";\n  payload: {\n    /** Comm ID of the widget to close */\n    commId: string;\n  };\n}\n\n/**\n * Sync all existing widget models to the iframe.\n * Sent on iframe ready to bootstrap existing widgets.\n */\nexport interface CommSyncMessage {\n  type: \"comm_sync\";\n  payload: {\n    /** Array of existing models to sync */\n    models: Array<{\n      commId: string;\n      targetName: string;\n      state: Record<string, unknown>;\n      buffers?: ArrayBuffer[];\n    }>;\n  };\n}\n\n/**\n * Signal that the parent's comm bridge is ready.\n * Iframe should respond with widget_ready to trigger comm_sync.\n */\nexport interface BridgeReadyMessage {\n  type: \"bridge_ready\";\n}\n\n// --- Global Find: Parent → Iframe ---\n\n/**\n * Search for text within the iframe's rendered content.\n * The iframe should highlight all matches and report the count.\n */\nexport interface SearchMessage {\n  type: \"search\";\n  payload: {\n    /** The search query string (empty string clears search) */\n    query: string;\n    /** Whether the search should be case-sensitive */\n    caseSensitive?: boolean;\n  };\n}\n\n/**\n * Navigate to a specific match in the iframe's search results.\n */\nexport interface SearchNavigateMessage {\n  type: \"search_navigate\";\n  payload: {\n    /** The index of the match to navigate to (0-based) */\n    matchIndex: number;\n  };\n}\n\n/**\n * All message types that can be sent from parent to iframe.\n */\nexport type ParentToIframeMessage =\n  | EvalMessage\n  | RenderMessage\n  | WidgetStateMessage\n  | ThemeMessage\n  | PingMessage\n  | ClearMessage\n  | CommOpenMessage\n  | CommMsgMessage\n  | CommCloseMessage\n  | CommSyncMessage\n  | BridgeReadyMessage\n  | SearchMessage\n  | SearchNavigateMessage;\n\n// --- Message Types: Iframe → Parent ---\n\n/**\n * Iframe has finished loading and is ready to receive messages.\n */\nexport interface ReadyMessage {\n  type: \"ready\";\n}\n\n/**\n * Response to a ping message.\n */\nexport interface PongMessage {\n  type: \"pong\";\n  payload: {\n    receivedAt: number;\n    /** Echo back the payload from the ping */\n    echo?: unknown;\n  };\n}\n\n/**\n * Result of evaluating code in the iframe.\n */\nexport interface EvalResultMessage {\n  type: \"eval_result\";\n  payload: {\n    success: boolean;\n    result?: string;\n    error?: string;\n  };\n}\n\n/**\n * Iframe content has finished rendering.\n */\nexport interface RenderCompleteMessage {\n  type: \"render_complete\";\n  payload?: {\n    /** Height of the rendered content */\n    height?: number;\n  };\n}\n\n/**\n * Iframe content size has changed.\n */\nexport interface ResizeMessage {\n  type: \"resize\";\n  payload: {\n    /** New height of the content */\n    height: number;\n    /** New width of the content (optional) */\n    width?: number;\n  };\n}\n\n/**\n * User clicked a link in the iframe.\n */\nexport interface LinkClickMessage {\n  type: \"link_click\";\n  payload: {\n    /** The URL that was clicked */\n    url: string;\n    /** Whether it was a ctrl/cmd click */\n    newTab: boolean;\n  };\n}\n\n/**\n * User double-clicked in the iframe.\n */\nexport interface DoubleClickMessage {\n  type: \"dblclick\";\n}\n\n/**\n * Widget state was updated in the iframe (needs to sync to kernel).\n */\nexport interface WidgetUpdateMessage {\n  type: \"widget_update\";\n  payload: {\n    /** Comm ID of the widget */\n    commId: string;\n    /** Updated state */\n    state: Record<string, unknown>;\n    /** Optional buffers (base64 encoded) */\n    buffers?: string[];\n  };\n}\n\n/**\n * An error occurred in the iframe.\n */\nexport interface IframeErrorMessage {\n  type: \"error\";\n  payload: {\n    message: string;\n    stack?: string;\n  };\n}\n\n/**\n * The React renderer bundle has been loaded and initialized.\n * This is sent after the bundle is eval'd and React is mounted.\n */\nexport interface RendererReadyMessage {\n  type: \"renderer_ready\";\n}\n\n// --- Widget Comm Protocol: Iframe → Parent ---\n\n/**\n * Iframe widget system is ready to receive comm messages.\n * Parent should send comm_sync with existing models after this.\n */\nexport interface WidgetReadyMessage {\n  type: \"widget_ready\";\n}\n\n/**\n * Widget initiated a state update or custom message.\n * Parent should forward to kernel and update its store.\n */\nexport interface WidgetCommMsgMessage {\n  type: \"widget_comm_msg\";\n  payload: {\n    /** Comm ID of the widget */\n    commId: string;\n    /** Message method: \"update\" or \"custom\" */\n    method: \"update\" | \"custom\";\n    /** State patch or custom content */\n    data: Record<string, unknown>;\n    /** Buffer paths */\n    bufferPaths?: string[][];\n    /** Binary buffers */\n    buffers?: ArrayBuffer[];\n  };\n}\n\n/**\n * Widget initiated comm close.\n * Parent should forward to kernel and clean up.\n */\nexport interface WidgetCommCloseMessage {\n  type: \"widget_comm_close\";\n  payload: {\n    /** Comm ID of the widget to close */\n    commId: string;\n  };\n}\n\n// --- Global Find: Iframe → Parent ---\n\n/**\n * Report search results from the iframe.\n * Sent after processing a search message.\n */\nexport interface SearchResultsMessage {\n  type: \"search_results\";\n  payload: {\n    /** Number of matches found */\n    count: number;\n  };\n}\n\n/**\n * All message types that can be sent from iframe to parent.\n */\nexport type IframeToParentMessage =\n  | ReadyMessage\n  | PongMessage\n  | EvalResultMessage\n  | RenderCompleteMessage\n  | ResizeMessage\n  | LinkClickMessage\n  | DoubleClickMessage\n  | WidgetUpdateMessage\n  | IframeErrorMessage\n  | RendererReadyMessage\n  | WidgetReadyMessage\n  | WidgetCommMsgMessage\n  | WidgetCommCloseMessage\n  | SearchResultsMessage;\n\n// --- Utility Types ---\n\n/**\n * All message types (for generic handling).\n */\nexport type IframeMessage = ParentToIframeMessage | IframeToParentMessage;\n\n/**\n * Extract the message type string.\n */\nexport type MessageType = IframeMessage[\"type\"];\n\n/**\n * Type guard to check if a message is from the iframe.\n */\nexport function isIframeMessage(data: unknown): data is IframeToParentMessage {\n  if (typeof data !== \"object\" || data === null) return false;\n  const msg = data as { type?: unknown };\n  return (\n    typeof msg.type === \"string\" &&\n    [\n      \"ready\",\n      \"pong\",\n      \"eval_result\",\n      \"render_complete\",\n      \"resize\",\n      \"link_click\",\n      \"dblclick\",\n      \"widget_update\",\n      \"error\",\n      \"renderer_ready\",\n      \"widget_ready\",\n      \"widget_comm_msg\",\n      \"widget_comm_close\",\n      \"search_results\",\n    ].includes(msg.type)\n  );\n}\n\n/**\n * Type guard for specific message types.\n */\nexport function isMessageType<T extends IframeMessage[\"type\"]>(\n  data: unknown,\n  type: T,\n): data is Extract<IframeMessage, { type: T }> {\n  if (typeof data !== \"object\" || data === null) return false;\n  return (data as { type?: unknown }).type === type;\n}\n",
      "type": "registry:lib",
      "target": "components/isolated/frame-bridge.ts"
    },
    {
      "path": "registry/outputs/isolated/frame-html.ts",
      "content": "/**\n * HTML template generator for isolated output frames.\n *\n * Creates the minimal HTML document that runs inside the blob URL iframe.\n * This document handles the message protocol and provides a render target\n * for output content.\n *\n * Security: This code runs in an isolated origin (blob:) with sandbox\n * restrictions, so it cannot access Tauri APIs or the parent DOM.\n */\n\nexport interface FrameHtmlOptions {\n  /**\n   * Whether to include dark mode styles by default.\n   */\n  darkMode?: boolean;\n  /**\n   * Additional CSS to inject into the frame.\n   */\n  additionalCss?: string;\n  /**\n   * Additional JavaScript to inject (runs after bootstrap).\n   */\n  additionalScript?: string;\n}\n\n/**\n * Generate the HTML template for an isolated output frame.\n *\n * The generated HTML includes:\n * - Basic styling for outputs (respects light/dark mode)\n * - Message handler for parent communication\n * - ResizeObserver for auto-sizing\n * - Ready notification on load\n *\n * @param options - Configuration options for the frame\n * @returns HTML string to be used with a blob URL\n */\nexport function generateFrameHtml(options: FrameHtmlOptions = {}): string {\n  const {\n    darkMode = true,\n    additionalCss = \"\",\n    additionalScript = \"\",\n  } = options;\n\n  return `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self' blob: data:; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; style-src 'unsafe-inline'; img-src * data: blob:; font-src * data:; connect-src *;\">\n  <style>\n    :root {\n      --bg-primary: ${darkMode ? \"#0a0a0a\" : \"#ffffff\"};\n      --bg-secondary: ${darkMode ? \"#1a1a1a\" : \"#f5f5f5\"};\n      --text-primary: ${darkMode ? \"#e0e0e0\" : \"#1a1a1a\"};\n      --text-secondary: ${darkMode ? \"#a0a0a0\" : \"#666666\"};\n      --border-color: ${darkMode ? \"#333333\" : \"#e0e0e0\"};\n      --accent-color: #3b82f6;\n      --error-color: #ef4444;\n      --success-color: #22c55e;\n    }\n\n    * {\n      box-sizing: border-box;\n    }\n\n    html, body {\n      margin: 0;\n      padding: 0;\n      font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n      line-height: 1.5;\n      background: transparent;\n      color: var(--text-primary);\n    }\n\n    /* Output container */\n    #root {\n      min-height: 1px;\n    }\n\n    /* Reset common elements - 0.875rem matches Tailwind's text-sm */\n    pre, code {\n      font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n      font-size: 0.875rem;\n    }\n\n    pre {\n      margin: 0;\n      padding: 8px;\n      background: var(--bg-secondary);\n      border-radius: 4px;\n      overflow-x: auto;\n      white-space: pre-wrap;\n      word-wrap: break-word;\n    }\n\n    /* Table styling for pandas DataFrames - 0.875rem matches Tailwind's text-sm */\n    table {\n      border-collapse: collapse;\n      margin: 8px 0;\n      font-size: 0.875rem;\n    }\n\n    th, td {\n      border: 1px solid var(--border-color);\n      padding: 4px 8px;\n      text-align: left;\n    }\n\n    th {\n      background: var(--bg-secondary);\n      font-weight: 600;\n    }\n\n    /* Image outputs */\n    img {\n      max-width: 100%;\n      height: auto;\n    }\n\n    /* Links */\n    a {\n      color: var(--accent-color);\n      text-decoration: none;\n    }\n\n    a:hover {\n      text-decoration: underline;\n    }\n\n    /* Error styling */\n    .error {\n      color: var(--error-color);\n    }\n\n    .error pre {\n      background: ${darkMode ? \"#1a1010\" : \"#fef2f2\"};\n      color: var(--error-color);\n    }\n\n    ${additionalCss}\n  </style>\n</head>\n<body>\n  <div id=\"root\"></div>\n  <script>\n    (function() {\n      'use strict';\n\n      // --- State ---\n      let isReady = false;\n      const root = document.getElementById('root');\n\n      // --- Message Handler ---\n      // Note: When the React renderer bundle is loaded, it sets window.__REACT_RENDERER_ACTIVE__\n      // and the inline handlers should defer to React for render/theme/clear messages.\n      window.addEventListener('message', function(event) {\n        // Only accept messages from our parent window\n        if (event.source !== window.parent) {\n          return;\n        }\n\n        const { type, payload } = event.data || {};\n\n        try {\n          switch (type) {\n            case 'ping':\n              handlePing(payload);\n              break;\n\n            case 'eval':\n              handleEval(payload);\n              break;\n\n            case 'render':\n              // Skip inline rendering if React renderer is active\n              if (window.__REACT_RENDERER_ACTIVE__) return;\n              handleRender(payload);\n              break;\n\n            case 'theme':\n              // Skip inline theme handling if React renderer is active\n              if (window.__REACT_RENDERER_ACTIVE__) return;\n              handleTheme(payload);\n              break;\n\n            case 'clear':\n              // Skip inline clear if React renderer is active\n              if (window.__REACT_RENDERER_ACTIVE__) return;\n              handleClear();\n              break;\n\n            case 'widget_state':\n              handleWidgetState(payload);\n              break;\n\n            case 'search':\n              handleSearch(payload);\n              break;\n\n            case 'search_navigate':\n              handleSearchNavigate(payload);\n              break;\n\n            // Comm bridge messages - handled by React widget system, ignore here\n            case 'bridge_ready':\n            case 'comm_open':\n            case 'comm_msg':\n            case 'comm_close':\n            case 'comm_sync':\n              // These are handled by widget-bridge-client.ts\n              break;\n\n            default:\n              console.warn('[frame] Unknown message type:', type);\n          }\n        } catch (err) {\n          sendError(err);\n        }\n      });\n\n      // --- Message Handlers ---\n\n      function handlePing(payload) {\n        send('pong', {\n          receivedAt: Date.now(),\n          echo: payload\n        });\n      }\n\n      function handleEval(payload) {\n        const { code } = payload || {};\n        if (!code) {\n          send('eval_result', { success: false, error: 'No code provided' });\n          return;\n        }\n\n        // Store the current message for access during eval\n        window.currentMessage = event;\n        try {\n          const result = eval.call(null, code);\n          send('eval_result', { success: true, result: String(result ?? 'undefined') });\n        } catch (err) {\n          send('eval_result', { success: false, error: err.message });\n        } finally {\n          delete window.currentMessage;\n        }\n      }\n\n      function handleRender(payload) {\n        const { mimeType, data, metadata, append } = payload || {};\n\n        // Create output container\n        const output = document.createElement('div');\n        output.className = 'output-item';\n        output.style.marginBottom = '8px';\n\n        if (mimeType === 'text/html') {\n          // Use createContextualFragment for proper script execution\n          const range = document.createRange();\n          const fragment = range.createContextualFragment(String(data));\n          output.appendChild(fragment);\n        } else if (mimeType === 'text/plain') {\n          const pre = document.createElement('pre');\n          // Handle ANSI escape codes for colored output\n          pre.innerHTML = parseAnsi(String(data));\n          output.appendChild(pre);\n        } else if (mimeType === 'image/svg+xml') {\n          // SVG: render inline\n          const container = document.createElement('div');\n          container.innerHTML = String(data);\n          const svg = container.querySelector('svg');\n          if (svg) {\n            svg.style.maxWidth = '100%';\n            svg.style.height = 'auto';\n            output.appendChild(svg);\n          } else {\n            output.appendChild(container);\n          }\n        } else if (mimeType && mimeType.startsWith('image/')) {\n          const img = document.createElement('img');\n          const imgData = String(data);\n          // Check if it's base64 or a URL\n          if (imgData.startsWith('data:') || imgData.startsWith('http')) {\n            img.src = imgData;\n          } else {\n            img.src = 'data:' + mimeType + ';base64,' + imgData;\n          }\n          if (metadata?.width) img.width = metadata.width;\n          if (metadata?.height) img.height = metadata.height;\n          output.appendChild(img);\n        } else if (mimeType === 'application/json') {\n          // JSON: render as formatted, collapsible tree\n          const pre = document.createElement('pre');\n          try {\n            const parsed = typeof data === 'string' ? JSON.parse(data) : data;\n            pre.textContent = JSON.stringify(parsed, null, 2);\n          } catch (e) {\n            pre.textContent = String(data);\n          }\n          output.appendChild(pre);\n        } else {\n          // Fallback: render as text\n          const pre = document.createElement('pre');\n          pre.textContent = typeof data === 'string' ? data : JSON.stringify(data, null, 2);\n          output.appendChild(pre);\n        }\n\n        // Append or replace\n        if (append) {\n          root.appendChild(output);\n        } else {\n          root.innerHTML = '';\n          root.appendChild(output);\n        }\n\n        // Notify completion\n        requestAnimationFrame(function() {\n          send('render_complete', { height: document.body.scrollHeight });\n        });\n      }\n\n      // Basic ANSI escape code parser\n      function parseAnsi(text) {\n        // Simple ANSI color mapping\n        const colors = {\n          '30': '#000', '31': '#e74c3c', '32': '#2ecc71', '33': '#f1c40f',\n          '34': '#3498db', '35': '#9b59b6', '36': '#1abc9c', '37': '#ecf0f1',\n          '90': '#7f8c8d', '91': '#e74c3c', '92': '#2ecc71', '93': '#f1c40f',\n          '94': '#3498db', '95': '#9b59b6', '96': '#1abc9c', '97': '#fff'\n        };\n\n        // Escape HTML\n        let result = text\n          .replace(/&/g, '&amp;')\n          .replace(/</g, '&lt;')\n          .replace(/>/g, '&gt;');\n\n        // Parse ANSI codes\n        result = result.replace(/\\\\x1b\\\\[(\\\\d+(?:;\\\\d+)*)m/g, function(match, codes) {\n          const codeList = codes.split(';');\n          let style = '';\n          for (const code of codeList) {\n            if (code === '0') return '</span>';\n            if (code === '1') style += 'font-weight:bold;';\n            if (code === '3') style += 'font-style:italic;';\n            if (code === '4') style += 'text-decoration:underline;';\n            if (colors[code]) style += 'color:' + colors[code] + ';';\n          }\n          return style ? '<span style=\"' + style + '\">' : '';\n        });\n\n        // Also handle \\\\e[ format\n        result = result.replace(/\\\\e\\\\[(\\\\d+(?:;\\\\d+)*)m/g, function(match, codes) {\n          const codeList = codes.split(';');\n          let style = '';\n          for (const code of codeList) {\n            if (code === '0') return '</span>';\n            if (code === '1') style += 'font-weight:bold;';\n            if (colors[code]) style += 'color:' + colors[code] + ';';\n          }\n          return style ? '<span style=\"' + style + '\">' : '';\n        });\n\n        return result;\n      }\n\n      function handleTheme(payload) {\n        const { isDark, cssVariables } = payload || {};\n        const rootEl = document.documentElement;\n\n        if (isDark !== undefined) {\n          rootEl.style.setProperty('--bg-primary', isDark ? '#0a0a0a' : '#ffffff');\n          rootEl.style.setProperty('--bg-secondary', isDark ? '#1a1a1a' : '#f5f5f5');\n          rootEl.style.setProperty('--text-primary', isDark ? '#e0e0e0' : '#1a1a1a');\n          rootEl.style.setProperty('--text-secondary', isDark ? '#a0a0a0' : '#666666');\n          rootEl.style.setProperty('--border-color', isDark ? '#333333' : '#e0e0e0');\n        }\n\n        if (cssVariables) {\n          Object.entries(cssVariables).forEach(function([key, value]) {\n            rootEl.style.setProperty(key, value);\n          });\n        }\n      }\n\n      function handleClear() {\n        root.innerHTML = '';\n        send('render_complete', { height: document.body.scrollHeight });\n      }\n\n      function handleWidgetState(payload) {\n        // Widget state updates are handled by the injected renderer bundle\n        // This is a placeholder that fires a custom event\n        window.dispatchEvent(new CustomEvent('widget_state', { detail: payload }));\n      }\n\n      // --- Search ---\n      var searchMarks = [];\n      var currentSearchIndex = -1;\n\n      function handleSearch(payload) {\n        var query = (payload && payload.query) || '';\n        var caseSensitive = payload && payload.caseSensitive;\n        clearSearchMarks();\n        if (!query) {\n          send('search_results', { count: 0 });\n          return;\n        }\n        var marks = [];\n        var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);\n        var node;\n        var compareQuery = caseSensitive ? query : query.toLowerCase();\n        // Collect all text nodes and their match positions\n        var matches = [];\n        while ((node = walker.nextNode())) {\n          var text = node.nodeValue || '';\n          var compareText = caseSensitive ? text : text.toLowerCase();\n          var pos = 0;\n          while ((pos = compareText.indexOf(compareQuery, pos)) !== -1) {\n            matches.push({ node: node, offset: pos, length: query.length });\n            pos += query.length;\n          }\n        }\n        // Highlight matches in reverse order to preserve offsets\n        for (var i = matches.length - 1; i >= 0; i--) {\n          var m = matches[i];\n          try {\n            var range = document.createRange();\n            range.setStart(m.node, m.offset);\n            range.setEnd(m.node, m.offset + m.length);\n            var mark = document.createElement('mark');\n            mark.className = 'global-find-match';\n            mark.style.cssText = 'background: #fbbf24; color: #000; border-radius: 2px; padding: 0;';\n            range.surroundContents(mark);\n            marks.unshift(mark);\n          } catch (e) {\n            // surroundContents can fail if range crosses element boundaries\n          }\n        }\n        searchMarks = marks;\n        currentSearchIndex = -1;\n        send('search_results', { count: marks.length });\n      }\n\n      function handleSearchNavigate(payload) {\n        var matchIndex = (payload && payload.matchIndex) || 0;\n        if (searchMarks.length === 0) return;\n        // Clear previous active highlight\n        if (currentSearchIndex >= 0 && currentSearchIndex < searchMarks.length) {\n          searchMarks[currentSearchIndex].style.cssText = 'background: #fbbf24; color: #000; border-radius: 2px; padding: 0;';\n        }\n        currentSearchIndex = matchIndex;\n        if (currentSearchIndex >= 0 && currentSearchIndex < searchMarks.length) {\n          var active = searchMarks[currentSearchIndex];\n          active.style.cssText = 'background: #f97316; color: #000; border-radius: 2px; padding: 0;';\n          active.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n        }\n      }\n\n      function clearSearchMarks() {\n        for (var i = 0; i < searchMarks.length; i++) {\n          var mark = searchMarks[i];\n          var parent = mark.parentNode;\n          if (parent) {\n            while (mark.firstChild) {\n              parent.insertBefore(mark.firstChild, mark);\n            }\n            parent.removeChild(mark);\n            parent.normalize();\n          }\n        }\n        searchMarks = [];\n        currentSearchIndex = -1;\n      }\n\n      // --- Utilities ---\n\n      function send(type, payload) {\n        window.parent.postMessage({ type: type, payload: payload }, '*');\n      }\n\n      function sendError(err) {\n        send('error', {\n          message: err.message || String(err),\n          stack: err.stack\n        });\n      }\n\n      // --- Resize Observer ---\n      const resizeObserver = new ResizeObserver(function(entries) {\n        const height = document.body.scrollHeight;\n        send('resize', { height: height });\n      });\n      resizeObserver.observe(document.body);\n\n      // --- Link Click Interception ---\n      document.addEventListener('click', function(e) {\n        const link = e.target.closest('a');\n        if (link && link.href) {\n          e.preventDefault();\n          send('link_click', {\n            url: link.href,\n            newTab: e.metaKey || e.ctrlKey\n          });\n        }\n      });\n\n      // --- Double Click Forwarding ---\n      document.addEventListener('dblclick', function(e) {\n        // Don't forward double-clicks on links (user is selecting text)\n        const link = e.target.closest('a');\n        if (!link) {\n          send('dblclick', null);\n        }\n      });\n\n      // --- Error Handler ---\n      window.addEventListener('error', function(e) {\n        sendError(e.error || new Error(e.message));\n      });\n\n      window.addEventListener('unhandledrejection', function(e) {\n        sendError(e.reason || new Error('Unhandled promise rejection'));\n      });\n\n      // --- Additional Script ---\n      ${additionalScript}\n\n      // --- Ready Signal ---\n      isReady = true;\n      send('ready', null);\n    })();\n  </script>\n</body>\n</html>`;\n}\n\n/**\n * Create a blob URL from the frame HTML.\n *\n * @param options - Configuration options for the frame\n * @returns A blob: URL that can be used as iframe src\n */\nexport function createFrameBlobUrl(options?: FrameHtmlOptions): string {\n  const html = generateFrameHtml(options);\n  const blob = new Blob([html], { type: \"text/html\" });\n  return URL.createObjectURL(blob);\n}\n",
      "type": "registry:lib",
      "target": "components/isolated/frame-html.ts"
    },
    {
      "path": "registry/outputs/isolated/comm-bridge-manager.ts",
      "content": "/**\n * Comm Bridge Manager - Parent Side\n *\n * This module manages the communication bridge between the parent window's\n * widget system and an isolated iframe. It:\n * - Buffers comm messages until iframe sends `widget_ready`\n * - Syncs all existing widget models to iframe on ready\n * - Forwards comm messages from kernel to iframe\n * - Handles widget messages from iframe and updates parent store + kernel\n *\n * Security: The iframe cannot access Tauri APIs directly. All widget\n * communication must go through this controlled postMessage bridge.\n */\n\nimport type { WidgetStore } from \"@/registry/widgets/widget-store\";\nimport type {\n  CommCloseMessage,\n  CommMsgMessage,\n  CommOpenMessage,\n  CommSyncMessage,\n  IframeToParentMessage,\n} from \"./frame-bridge\";\nimport type { IsolatedFrameHandle } from \"./isolated-frame\";\n\n// Type for sending messages to kernel\ntype SendUpdate = (\n  commId: string,\n  state: Record<string, unknown>,\n  buffers?: ArrayBuffer[],\n) => void;\n\ntype SendCustom = (\n  commId: string,\n  content: Record<string, unknown>,\n  buffers?: ArrayBuffer[],\n) => void;\n\ntype CloseComm = (commId: string) => void;\n\ninterface CommBridgeManagerOptions {\n  /** The isolated frame handle for sending messages */\n  frame: IsolatedFrameHandle;\n  /** The parent widget store */\n  store: WidgetStore;\n  /** Function to send state updates to kernel */\n  sendUpdate: SendUpdate;\n  /** Function to send custom messages to kernel */\n  sendCustom: SendCustom;\n  /** Function to close a comm with kernel */\n  closeComm: CloseComm;\n}\n\n/**\n * Comm Bridge Manager for proxying widget communication to an isolated iframe.\n *\n * Usage:\n * 1. Create manager when IsolatedFrame is mounted\n * 2. Subscribe to widget store changes to forward to iframe\n * 3. Handle iframe messages via onMessage callback\n * 4. Dispose when iframe is unmounted\n */\nexport class CommBridgeManager {\n  private frame: IsolatedFrameHandle;\n  private store: WidgetStore;\n  private sendUpdateToKernel: SendUpdate;\n  private sendCustomToKernel: SendCustom;\n  private closeCommWithKernel: CloseComm;\n\n  private isWidgetReady = false;\n  private messageBuffer: Array<\n    CommOpenMessage | CommMsgMessage | CommCloseMessage\n  > = [];\n  private storeUnsubscribe: (() => void) | null = null;\n\n  // Track which models have been sent to avoid duplicate sends\n  private sentModels = new Set<string>();\n\n  // Track previous state for each model to detect kernel updates\n  private previousState = new Map<string, Record<string, unknown>>();\n\n  // Flag to prevent echoing iframe updates back to iframe\n  private isProcessingIframeUpdate = false;\n\n  // Track custom message subscriptions for each model\n  private customMessageUnsubscribers = new Map<string, () => void>();\n\n  constructor(options: CommBridgeManagerOptions) {\n    this.frame = options.frame;\n    this.store = options.store;\n    this.sendUpdateToKernel = options.sendUpdate;\n    this.sendCustomToKernel = options.sendCustom;\n    this.closeCommWithKernel = options.closeComm;\n\n    // Subscribe to store changes to forward to iframe\n    this.storeUnsubscribe = this.store.subscribe(() => {\n      if (!this.isWidgetReady) return;\n      // Skip if this change came from iframe (avoid echo)\n      if (this.isProcessingIframeUpdate) return;\n      this.syncModels();\n    });\n\n    // Signal to iframe that parent bridge is ready\n    // Iframe will respond with widget_ready to trigger comm_sync\n    this.frame.send({ type: \"bridge_ready\" });\n  }\n\n  /**\n   * Handle a message from the iframe.\n   * Call this from the IsolatedFrame's onMessage callback.\n   */\n  handleIframeMessage(message: IframeToParentMessage): void {\n    switch (message.type) {\n      case \"widget_ready\":\n        this.handleWidgetReady();\n        break;\n\n      case \"widget_comm_msg\":\n        this.handleWidgetCommMsg(message.payload);\n        break;\n\n      case \"widget_comm_close\":\n        this.handleWidgetCommClose(message.payload);\n        break;\n    }\n  }\n\n  /**\n   * Forward a comm_open to the iframe.\n   * Called when a widget model is created by the kernel.\n   */\n  sendCommOpen(\n    commId: string,\n    targetName: string,\n    state: Record<string, unknown>,\n    buffers?: ArrayBuffer[],\n  ): void {\n    const msg: CommOpenMessage = {\n      type: \"comm_open\",\n      payload: {\n        commId,\n        targetName,\n        state,\n        buffers,\n      },\n    };\n\n    if (this.isWidgetReady) {\n      this.frame.send(msg);\n      this.sentModels.add(commId);\n    } else {\n      this.messageBuffer.push(msg);\n    }\n  }\n\n  /**\n   * Forward a comm_msg (state update or custom message) to the iframe.\n   * Called when the kernel sends a state update or custom message.\n   */\n  sendCommMsg(\n    commId: string,\n    method: \"update\" | \"custom\",\n    data: Record<string, unknown>,\n    buffers?: ArrayBuffer[],\n  ): void {\n    const msg: CommMsgMessage = {\n      type: \"comm_msg\",\n      payload: {\n        commId,\n        method,\n        data,\n        buffers,\n      },\n    };\n\n    if (this.isWidgetReady) {\n      this.frame.send(msg);\n    } else {\n      this.messageBuffer.push(msg);\n    }\n  }\n\n  /**\n   * Forward a comm_close to the iframe.\n   * Called when the kernel closes a widget.\n   */\n  sendCommClose(commId: string): void {\n    const msg: CommCloseMessage = {\n      type: \"comm_close\",\n      payload: { commId },\n    };\n\n    if (this.isWidgetReady) {\n      this.frame.send(msg);\n      this.sentModels.delete(commId);\n    } else {\n      this.messageBuffer.push(msg);\n    }\n  }\n\n  /**\n   * Clean up subscriptions and state.\n   */\n  dispose(): void {\n    if (this.storeUnsubscribe) {\n      this.storeUnsubscribe();\n      this.storeUnsubscribe = null;\n    }\n    // Unsubscribe from all custom message subscriptions\n    for (const unsubscribe of this.customMessageUnsubscribers.values()) {\n      unsubscribe();\n    }\n    this.customMessageUnsubscribers.clear();\n    this.messageBuffer = [];\n    this.sentModels.clear();\n    this.previousState.clear();\n    this.isWidgetReady = false;\n  }\n\n  // --- Private Methods ---\n\n  private handleWidgetReady(): void {\n    this.isWidgetReady = true;\n\n    // Send comm_sync with all existing models\n    const models = this.store.getSnapshot();\n    const modelArray: CommSyncMessage[\"payload\"][\"models\"] = [];\n\n    for (const [commId, model] of models) {\n      modelArray.push({\n        commId,\n        targetName: model.modelModule || \"jupyter.widget\",\n        state: model.state,\n        buffers: model.buffers,\n      });\n      this.sentModels.add(commId);\n      // Store initial state for change detection\n      this.previousState.set(commId, this.cloneStateSnapshot(model.state));\n      // Subscribe to custom messages for this model\n      this.subscribeToModelCustomMessages(commId);\n    }\n\n    if (modelArray.length > 0) {\n      const syncMsg: CommSyncMessage = {\n        type: \"comm_sync\",\n        payload: { models: modelArray },\n      };\n      try {\n        this.frame.send(syncMsg);\n      } catch (e) {\n        console.error(\"[CommBridge] Error sending comm_sync:\", e);\n      }\n    }\n\n    // Flush buffered messages\n    for (const msg of this.messageBuffer) {\n      this.frame.send(msg);\n      if (msg.type === \"comm_open\") {\n        this.sentModels.add(msg.payload.commId);\n      } else if (msg.type === \"comm_close\") {\n        this.sentModels.delete(msg.payload.commId);\n      }\n    }\n    this.messageBuffer = [];\n  }\n\n  private handleWidgetCommMsg(payload: {\n    commId: string;\n    method: \"update\" | \"custom\";\n    data: Record<string, unknown>;\n    bufferPaths?: string[][];\n    buffers?: ArrayBuffer[];\n  }): void {\n    const { commId, method, data, buffers } = payload;\n\n    if (method === \"update\") {\n      // Set flag to prevent echoing this update back to iframe\n      this.isProcessingIframeUpdate = true;\n      try {\n        // Update parent store first (so UI stays in sync)\n        this.store.updateModel(commId, data, buffers);\n        // Update our tracked state\n        const current = this.previousState.get(commId) ?? {};\n        this.previousState.set(\n          commId,\n          this.cloneStateSnapshot({ ...current, ...data }),\n        );\n        // Then forward to kernel\n        this.sendUpdateToKernel(commId, data, buffers);\n      } finally {\n        this.isProcessingIframeUpdate = false;\n      }\n    } else if (method === \"custom\") {\n      // Custom messages go directly to kernel (no store update)\n      this.sendCustomToKernel(commId, data, buffers);\n    }\n  }\n\n  private handleWidgetCommClose(payload: { commId: string }): void {\n    const { commId } = payload;\n\n    // Update parent store\n    this.store.deleteModel(commId);\n    // Forward to kernel\n    this.closeCommWithKernel(commId);\n    // Clean up tracking\n    this.sentModels.delete(commId);\n  }\n\n  /**\n   * Sync models with iframe: new models, deleted models, and state changes.\n   * Called when store changes after widget_ready.\n   */\n  private syncModels(): void {\n    const models = this.store.getSnapshot();\n\n    for (const [commId, model] of models) {\n      if (!this.sentModels.has(commId)) {\n        // New model - send comm_open\n        this.sendCommOpen(\n          commId,\n          model.modelModule || \"jupyter.widget\",\n          model.state,\n          model.buffers,\n        );\n        // Store initial state for change detection\n        this.previousState.set(commId, this.cloneStateSnapshot(model.state));\n        // Subscribe to custom messages for this model\n        this.subscribeToModelCustomMessages(commId);\n      } else {\n        // Existing model - check for state changes\n        const previous = this.previousState.get(commId);\n        if (previous) {\n          const changedKeys = this.getChangedKeys(previous, model.state);\n          if (changedKeys.length > 0) {\n            // Build delta with only changed keys\n            const delta: Record<string, unknown> = {};\n            for (const key of changedKeys) {\n              delta[key] = model.state[key];\n            }\n            // Forward state update to iframe\n            this.sendCommMsg(commId, \"update\", delta, model.buffers);\n            // Update tracked state\n            this.previousState.set(\n              commId,\n              this.cloneStateSnapshot(model.state),\n            );\n          }\n        }\n      }\n    }\n\n    // Check for deleted models\n    for (const commId of this.sentModels) {\n      if (!models.has(commId)) {\n        this.sendCommClose(commId);\n        this.previousState.delete(commId);\n        // Unsubscribe from custom messages\n        this.unsubscribeFromModelCustomMessages(commId);\n      }\n    }\n  }\n\n  /**\n   * Subscribe to custom messages for a model and forward them to iframe.\n   * This is critical for anywidgets like quak that use custom messages for data.\n   */\n  private subscribeToModelCustomMessages(commId: string): void {\n    // Don't double-subscribe\n    if (this.customMessageUnsubscribers.has(commId)) return;\n\n    const unsubscribe = this.store.subscribeToCustomMessage(\n      commId,\n      (content: Record<string, unknown>, buffers?: DataView[]) => {\n        // Convert DataView[] to ArrayBuffer[] for postMessage\n        const arrayBuffers = buffers?.map(\n          (dv: DataView) => dv.buffer as ArrayBuffer,\n        );\n        // Forward custom message to iframe\n        this.sendCommMsg(commId, \"custom\", content, arrayBuffers);\n      },\n    );\n\n    this.customMessageUnsubscribers.set(commId, unsubscribe);\n  }\n\n  /**\n   * Unsubscribe from custom messages for a model.\n   */\n  private unsubscribeFromModelCustomMessages(commId: string): void {\n    const unsubscribe = this.customMessageUnsubscribers.get(commId);\n    if (unsubscribe) {\n      unsubscribe();\n      this.customMessageUnsubscribers.delete(commId);\n    }\n  }\n\n  /**\n   * Get keys that have changed between two state objects.\n   * Uses shallow comparison for performance.\n   */\n  private getChangedKeys(\n    previous: Record<string, unknown>,\n    current: Record<string, unknown>,\n  ): string[] {\n    const changed: string[] = [];\n    const allKeys = new Set([\n      ...Object.keys(previous),\n      ...Object.keys(current),\n    ]);\n    for (const key of allKeys) {\n      if (this.valuesAreDifferent(previous[key], current[key])) {\n        changed.push(key);\n      }\n    }\n    return changed;\n  }\n\n  /**\n   * Create a deep snapshot of model state so future in-place mutations are detectable.\n   */\n  private cloneStateSnapshot(\n    state: Record<string, unknown>,\n  ): Record<string, unknown> {\n    try {\n      return structuredClone(state);\n    } catch {\n      return { ...state };\n    }\n  }\n\n  /**\n   * Compare two state values.\n   * For object/array values, use JSON content comparison so deep snapshots\n   * can detect in-place mutations from live state objects.\n   */\n  private valuesAreDifferent(previous: unknown, current: unknown): boolean {\n    if (\n      typeof previous !== \"object\" ||\n      previous === null ||\n      typeof current !== \"object\" ||\n      current === null\n    ) {\n      return previous !== current;\n    }\n\n    try {\n      return JSON.stringify(previous) !== JSON.stringify(current);\n    } catch {\n      // If value can't be serialized consistently, err on sending an update.\n      return true;\n    }\n  }\n}\n\n/**\n * Create a comm bridge manager for an isolated frame.\n */\nexport function createCommBridgeManager(\n  options: CommBridgeManagerOptions,\n): CommBridgeManager {\n  return new CommBridgeManager(options);\n}\n",
      "type": "registry:lib",
      "target": "components/isolated/comm-bridge-manager.ts"
    },
    {
      "path": "registry/outputs/isolated/IsolationTest.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { IsolatedFrame, type IsolatedFrameHandle } from \"./isolated-frame\";\nimport { IsolatedRendererProvider } from \"./isolated-renderer-context\";\n\n/**\n * Test results from the isolated iframe\n */\ninterface IsolationTestResult {\n  hasTauri: boolean;\n  hasInvoke: boolean;\n  canAccessParentDocument: boolean;\n  canAccessParentLocalStorage: boolean;\n  canUseOwnLocalStorage: boolean;\n  canUseOwnCookies: boolean;\n  canUseIndexedDB: boolean;\n  canFetchParentOrigin: boolean;\n  windowOrigin: string;\n  error?: string;\n}\n\n/**\n * HTML template for the isolation test iframe.\n * This runs inside the iframe and reports back via postMessage.\n * Also handles bidirectional communication (ping/pong, eval, render).\n */\nconst ISOLATION_TEST_HTML = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <style>\n    body {\n      font-family: system-ui, sans-serif;\n      padding: 16px;\n      margin: 0;\n      background: #1a1a1a;\n      color: #e0e0e0;\n    }\n    .test-item {\n      padding: 8px;\n      margin: 4px 0;\n      border-radius: 4px;\n    }\n    .pass { background: #1e3a1e; color: #4ade80; }\n    .fail { background: #3a1e1e; color: #f87171; }\n    .info { background: #1e2a3a; color: #60a5fa; }\n    pre { font-size: 12px; overflow: auto; }\n    #render-target { margin-top: 16px; padding: 8px; border: 1px dashed #444; min-height: 20px; }\n  </style>\n</head>\n<body>\n  <h3>Iframe Isolation Test</h3>\n  <div id=\"results\"></div>\n  <div id=\"messages\"></div>\n  <div id=\"render-target\"></div>\n  <script>\n    const results = {\n      hasTauri: false,\n      hasInvoke: false,\n      canAccessParentDocument: false,\n      canAccessParentLocalStorage: false,\n      canUseOwnLocalStorage: false,\n      canUseOwnCookies: false,\n      canUseIndexedDB: false,\n      canFetchParentOrigin: false,\n      windowOrigin: window.origin || 'null',\n      error: null\n    };\n\n    // Test 1: Check for window.__TAURI__\n    try {\n      results.hasTauri = typeof window.__TAURI__ !== 'undefined';\n    } catch (e) {\n      results.hasTauri = false;\n    }\n\n    // Test 2: Check for invoke function\n    try {\n      results.hasInvoke = typeof window.__TAURI_INTERNALS__?.invoke === 'function' ||\n                          typeof window.__TAURI__?.core?.invoke === 'function';\n    } catch (e) {\n      results.hasInvoke = false;\n    }\n\n    // Test 3: Try to access parent document\n    try {\n      const test = window.parent.document.body;\n      results.canAccessParentDocument = true;\n    } catch (e) {\n      results.canAccessParentDocument = false;\n    }\n\n    // Test 4: Try to access parent localStorage\n    try {\n      const test = window.parent.localStorage.getItem('test');\n      results.canAccessParentLocalStorage = true;\n    } catch (e) {\n      results.canAccessParentLocalStorage = false;\n    }\n\n    // Test 5: Try to use iframe's own localStorage (should fail with opaque origin)\n    try {\n      localStorage.setItem('isolation_test', 'test');\n      localStorage.removeItem('isolation_test');\n      results.canUseOwnLocalStorage = true;\n    } catch (e) {\n      results.canUseOwnLocalStorage = false;\n    }\n\n    // Test 6: Try to use cookies\n    try {\n      document.cookie = 'isolation_test=1';\n      results.canUseOwnCookies = document.cookie.includes('isolation_test');\n    } catch (e) {\n      results.canUseOwnCookies = false;\n    }\n\n    // Test 7: Try to use IndexedDB (should fail with opaque origin)\n    try {\n      const request = indexedDB.open('isolation_test', 1);\n      request.onsuccess = () => {\n        results.canUseIndexedDB = true;\n        request.result.close();\n        indexedDB.deleteDatabase('isolation_test');\n        updateResults();\n      };\n      request.onerror = () => {\n        results.canUseIndexedDB = false;\n        updateResults();\n      };\n    } catch (e) {\n      results.canUseIndexedDB = false;\n    }\n\n    // Test 8: Try to fetch parent origin (will test after display)\n    // This is async, so we'll update results later\n\n    // Display results in iframe\n    const container = document.getElementById('results');\n    const tests = [\n      { name: 'window.__TAURI__ exists', value: results.hasTauri, expectFalse: true },\n      { name: 'invoke() accessible', value: results.hasInvoke, expectFalse: true },\n      { name: 'Can access parent document', value: results.canAccessParentDocument, expectFalse: true },\n      { name: 'Can access parent localStorage', value: results.canAccessParentLocalStorage, expectFalse: true },\n      { name: 'Can use own localStorage', value: results.canUseOwnLocalStorage, expectFalse: true, info: 'Opaque origin blocks storage' },\n      { name: 'Can use cookies', value: results.canUseOwnCookies, expectFalse: true, info: 'Opaque origin blocks cookies' },\n      { name: 'Can use IndexedDB', value: results.canUseIndexedDB, expectFalse: true, info: 'Opaque origin blocks IndexedDB' },\n    ];\n\n    function updateResults() {\n      container.innerHTML = '';\n      const currentTests = [\n        { name: 'window.__TAURI__ exists', value: results.hasTauri, expectFalse: true },\n        { name: 'invoke() accessible', value: results.hasInvoke, expectFalse: true },\n        { name: 'Can access parent document', value: results.canAccessParentDocument, expectFalse: true },\n        { name: 'Can access parent localStorage', value: results.canAccessParentLocalStorage, expectFalse: true },\n        { name: 'Can use own localStorage', value: results.canUseOwnLocalStorage, expectFalse: true },\n        { name: 'Can use cookies', value: results.canUseOwnCookies, expectFalse: true },\n        { name: 'Can use IndexedDB', value: results.canUseIndexedDB, expectFalse: true },\n        { name: 'Can fetch parent origin', value: results.canFetchParentOrigin, expectFalse: true },\n      ];\n      currentTests.forEach(test => {\n        const pass = test.expectFalse ? !test.value : test.value;\n        const div = document.createElement('div');\n        div.className = 'test-item ' + (pass ? 'pass' : 'fail');\n        div.textContent = (pass ? '✓ ' : '✗ ') + test.name + ': ' + test.value;\n        container.appendChild(div);\n      });\n      const originDiv = document.createElement('div');\n      originDiv.className = 'test-item info';\n      originDiv.innerHTML = '<pre>Window origin: ' + results.windowOrigin + '</pre>';\n      container.appendChild(originDiv);\n      window.parent.postMessage({ type: 'isolation_test_result', results }, '*');\n    }\n\n    tests.forEach(test => {\n      const pass = test.expectFalse ? !test.value : test.value;\n      const div = document.createElement('div');\n      div.className = 'test-item ' + (pass ? 'pass' : 'fail');\n      div.textContent = (pass ? '✓ ' : '✗ ') + test.name + ': ' + test.value;\n      container.appendChild(div);\n    });\n\n    const originDiv = document.createElement('div');\n    originDiv.className = 'test-item info';\n    originDiv.innerHTML = '<pre>Window origin: ' + results.windowOrigin + '</pre>';\n    container.appendChild(originDiv);\n\n    // Test fetch to parent origin (async)\n    if (window.parent !== window) {\n      let parentOrigin = '/';\n      try {\n        parentOrigin = window.parent.location?.origin || '/';\n      } catch {\n        // Expected in sandboxed iframe - origin access blocked\n      }\n      fetch(parentOrigin, { mode: 'cors' })\n        .then(() => {\n          results.canFetchParentOrigin = true;\n          updateResults();\n        })\n        .catch(() => {\n          results.canFetchParentOrigin = false;\n          updateResults();\n        });\n    }\n\n    // Send initial results to parent\n    window.parent.postMessage({ type: 'isolation_test_result', results }, '*');\n\n    // --- Bidirectional Communication ---\n    const messagesContainer = document.getElementById('messages');\n    const renderTarget = document.getElementById('render-target');\n\n    function logMessage(text, type = 'info') {\n      const div = document.createElement('div');\n      div.className = 'test-item ' + type;\n      div.textContent = text;\n      messagesContainer.appendChild(div);\n    }\n\n    // Listen for messages from parent\n    window.addEventListener('message', function(event) {\n      // Only accept messages from our parent window\n      if (event.source !== window.parent) {\n        return;\n      }\n\n      const { type, payload } = event.data || {};\n\n      switch (type) {\n        case 'ping':\n          // Respond to ping with pong\n          logMessage('Received ping, sending pong...', 'info');\n          window.parent.postMessage({\n            type: 'pong',\n            payload: {\n              receivedAt: Date.now(),\n              echo: payload\n            }\n          }, '*');\n          break;\n\n        case 'eval':\n          // Bootstrap/eval pattern (like Colab)\n          logMessage('Received eval command', 'info');\n          window.currentMessage = event;\n          try {\n            const result = eval.call(null, payload.code);\n            window.parent.postMessage({\n              type: 'eval_result',\n              payload: { success: true, result: String(result) }\n            }, '*');\n          } catch (e) {\n            window.parent.postMessage({\n              type: 'eval_result',\n              payload: { success: false, error: e.message }\n            }, '*');\n          } finally {\n            delete window.currentMessage;\n          }\n          break;\n\n        case 'render':\n          // Render HTML content\n          logMessage('Received render command: ' + payload.mimeType, 'info');\n          if (payload.mimeType === 'text/html') {\n            const range = document.createRange();\n            const fragment = range.createContextualFragment(payload.data);\n            renderTarget.innerHTML = '';\n            renderTarget.appendChild(fragment);\n            window.parent.postMessage({ type: 'render_complete' }, '*');\n          }\n          break;\n\n        default:\n          logMessage('Unknown message type: ' + type, 'fail');\n      }\n    });\n\n    // Notify parent that iframe is ready for communication\n    window.parent.postMessage({ type: 'ready' }, '*');\n  </script>\n</body>\n</html>`;\n\n/**\n * IsolationTest component - A proof-of-concept to verify that blob URL iframes\n * are properly isolated from Tauri's IPC injection.\n *\n * This component creates an iframe using a blob: URL with sandbox attributes\n * that should prevent access to Tauri APIs while still allowing script execution.\n *\n * Expected results for proper isolation:\n * - window.__TAURI__ should be undefined\n * - invoke() should not be accessible\n * - Parent document should not be accessible\n * - Parent localStorage should not be accessible\n */\n/**\n * Communication test state\n */\ninterface CommTestState {\n  iframeReady: boolean;\n  pingCount: number;\n  lastPongTime: number | null;\n  evalResult: string | null;\n  renderComplete: boolean;\n}\n\nexport function IsolationTest() {\n  const iframeRef = useRef<HTMLIFrameElement>(null);\n  const [blobUrl, setBlobUrl] = useState<string | null>(null);\n  const [testResult, setTestResult] = useState<IsolationTestResult | null>(\n    null,\n  );\n  const [parentHasTauri, setParentHasTauri] = useState<boolean>(false);\n  const [commState, setCommState] = useState<CommTestState>({\n    iframeReady: false,\n    pingCount: 0,\n    lastPongTime: null,\n    evalResult: null,\n    renderComplete: false,\n  });\n\n  // Check if parent has Tauri (for comparison)\n  useEffect(() => {\n    setParentHasTauri(\n      typeof (window as unknown as { __TAURI__?: unknown }).__TAURI__ !==\n        \"undefined\",\n    );\n  }, []);\n\n  // Create blob URL on mount\n  useEffect(() => {\n    const blob = new Blob([ISOLATION_TEST_HTML], { type: \"text/html\" });\n    const url = URL.createObjectURL(blob);\n    setBlobUrl(url);\n\n    return () => {\n      URL.revokeObjectURL(url);\n    };\n  }, []);\n\n  // Listen for messages from iframe\n  useEffect(() => {\n    const handleMessage = (event: MessageEvent) => {\n      const { type, payload } = event.data || {};\n\n      switch (type) {\n        case \"isolation_test_result\":\n          setTestResult(payload ?? event.data.results);\n          break;\n        case \"ready\":\n          setCommState((prev) => ({ ...prev, iframeReady: true }));\n          break;\n        case \"pong\":\n          setCommState((prev) => ({\n            ...prev,\n            pingCount: prev.pingCount + 1,\n            lastPongTime: payload?.receivedAt ?? Date.now(),\n          }));\n          break;\n        case \"eval_result\":\n          setCommState((prev) => ({\n            ...prev,\n            evalResult: payload?.success\n              ? `Success: ${payload.result}`\n              : `Error: ${payload?.error}`,\n          }));\n          break;\n        case \"render_complete\":\n          setCommState((prev) => ({ ...prev, renderComplete: true }));\n          break;\n      }\n    };\n\n    window.addEventListener(\"message\", handleMessage);\n    return () => window.removeEventListener(\"message\", handleMessage);\n  }, []);\n\n  // Send message to iframe\n  const sendToIframe = (type: string, payload?: unknown) => {\n    if (iframeRef.current?.contentWindow) {\n      iframeRef.current.contentWindow.postMessage({ type, payload }, \"*\");\n    }\n  };\n\n  const handlePing = () => {\n    sendToIframe(\"ping\", { sentAt: Date.now() });\n  };\n\n  const handleEval = () => {\n    sendToIframe(\"eval\", { code: \"1 + 2 + 3\" });\n  };\n\n  const handleRender = () => {\n    setCommState((prev) => ({ ...prev, renderComplete: false }));\n    sendToIframe(\"render\", {\n      mimeType: \"text/html\",\n      data: \"<div style='color: #4ade80; padding: 8px;'>HTML rendered via postMessage!</div>\",\n    });\n  };\n\n  const isIsolated =\n    testResult &&\n    !testResult.hasTauri &&\n    !testResult.hasInvoke &&\n    !testResult.canAccessParentDocument &&\n    !testResult.canAccessParentLocalStorage &&\n    !testResult.canUseOwnLocalStorage &&\n    !testResult.canUseOwnCookies &&\n    !testResult.canUseIndexedDB &&\n    !testResult.canFetchParentOrigin;\n\n  return (\n    <div\n      data-testid=\"isolation-test\"\n      className=\"bg-background text-foreground space-y-4 p-4\"\n    >\n      <h2 className=\"text-lg font-semibold\">Blob URL Iframe Isolation Test</h2>\n\n      {/* Parent context info */}\n      <div className=\"bg-muted rounded p-3\">\n        <h3 className=\"mb-2 font-medium\">Parent Window Context:</h3>\n        <p className=\"text-sm\">\n          window.__TAURI__ exists:{\" \"}\n          <span\n            className={parentHasTauri ? \"text-yellow-500\" : \"text-green-500\"}\n          >\n            {parentHasTauri ? \"Yes (expected in Tauri app)\" : \"No\"}\n          </span>\n        </p>\n        <p className=\"text-sm\">\n          Window origin: <code className=\"text-xs\">{window.origin}</code>\n        </p>\n      </div>\n\n      {/* Test results */}\n      {testResult && (\n        <div\n          className={`rounded p-3 ${\n            isIsolated\n              ? \"border border-green-700 bg-green-950\"\n              : \"border border-red-700 bg-red-950\"\n          }`}\n        >\n          <h3 className=\"mb-2 font-medium\">\n            {isIsolated ? \"Iframe is properly isolated!\" : \"Isolation FAILED\"}\n          </h3>\n          <ul className=\"space-y-1 text-sm\">\n            <li>\n              Tauri API blocked:{\" \"}\n              <span\n                className={\n                  !testResult.hasTauri ? \"text-green-500\" : \"text-red-500\"\n                }\n              >\n                {!testResult.hasTauri ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              invoke() blocked:{\" \"}\n              <span\n                className={\n                  !testResult.hasInvoke ? \"text-green-500\" : \"text-red-500\"\n                }\n              >\n                {!testResult.hasInvoke ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              Parent document blocked:{\" \"}\n              <span\n                className={\n                  !testResult.canAccessParentDocument\n                    ? \"text-green-500\"\n                    : \"text-red-500\"\n                }\n              >\n                {!testResult.canAccessParentDocument ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              Parent localStorage blocked:{\" \"}\n              <span\n                className={\n                  !testResult.canAccessParentLocalStorage\n                    ? \"text-green-500\"\n                    : \"text-red-500\"\n                }\n              >\n                {!testResult.canAccessParentLocalStorage ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              Own localStorage blocked (opaque origin):{\" \"}\n              <span\n                className={\n                  !testResult.canUseOwnLocalStorage\n                    ? \"text-green-500\"\n                    : \"text-red-500\"\n                }\n              >\n                {!testResult.canUseOwnLocalStorage ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              Cookies blocked (opaque origin):{\" \"}\n              <span\n                className={\n                  !testResult.canUseOwnCookies\n                    ? \"text-green-500\"\n                    : \"text-red-500\"\n                }\n              >\n                {!testResult.canUseOwnCookies ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              IndexedDB blocked (opaque origin):{\" \"}\n              <span\n                className={\n                  !testResult.canUseIndexedDB\n                    ? \"text-green-500\"\n                    : \"text-red-500\"\n                }\n              >\n                {!testResult.canUseIndexedDB ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              Parent origin fetch blocked:{\" \"}\n              <span\n                className={\n                  !testResult.canFetchParentOrigin\n                    ? \"text-green-500\"\n                    : \"text-red-500\"\n                }\n              >\n                {!testResult.canFetchParentOrigin ? \"Yes\" : \"No\"}\n              </span>\n            </li>\n            <li>\n              Iframe origin:{\" \"}\n              <code className=\"text-xs\">{testResult.windowOrigin}</code>\n            </li>\n          </ul>\n        </div>\n      )}\n\n      {/* Communication Test Controls */}\n      <div className=\"bg-muted space-y-3 rounded p-3\">\n        <h3 className=\"font-medium\">Bidirectional Communication Test:</h3>\n        <div className=\"flex items-center gap-2 text-sm\">\n          <span>\n            Iframe ready:{\" \"}\n            <span\n              className={\n                commState.iframeReady ? \"text-green-500\" : \"text-yellow-500\"\n              }\n            >\n              {commState.iframeReady ? \"Yes\" : \"Waiting...\"}\n            </span>\n          </span>\n        </div>\n        <div className=\"flex flex-wrap gap-2\">\n          <button\n            onClick={handlePing}\n            disabled={!commState.iframeReady}\n            className=\"rounded bg-blue-600 px-3 py-1.5 text-sm hover:bg-blue-700 disabled:bg-gray-600\"\n          >\n            Send Ping\n          </button>\n          <button\n            onClick={handleEval}\n            disabled={!commState.iframeReady}\n            className=\"rounded bg-purple-600 px-3 py-1.5 text-sm hover:bg-purple-700 disabled:bg-gray-600\"\n          >\n            Test Eval (1+2+3)\n          </button>\n          <button\n            onClick={handleRender}\n            disabled={!commState.iframeReady}\n            className=\"rounded bg-green-600 px-3 py-1.5 text-sm hover:bg-green-700 disabled:bg-gray-600\"\n          >\n            Test Render HTML\n          </button>\n        </div>\n        <div className=\"space-y-1 text-sm\">\n          <p>Pong responses received: {commState.pingCount}</p>\n          {commState.evalResult && <p>Eval result: {commState.evalResult}</p>}\n          {commState.renderComplete && (\n            <p className=\"text-green-500\">Render completed successfully</p>\n          )}\n        </div>\n      </div>\n\n      {/* The actual isolated iframe */}\n      {blobUrl && (\n        <div className=\"overflow-hidden rounded border\">\n          <iframe\n            ref={iframeRef}\n            src={blobUrl}\n            sandbox=\"allow-scripts\"\n            className=\"h-80 w-full bg-neutral-900\"\n            title=\"Isolation Test Frame\"\n          />\n        </div>\n      )}\n\n      {/* Sandbox attribute explanation */}\n      <div className=\"text-muted-foreground space-y-2 text-xs\">\n        <p>\n          <strong>Sandbox attributes:</strong> allow-scripts (no\n          allow-same-origin)\n        </p>\n        <p>\n          Without <code>allow-same-origin</code>, the iframe gets an{\" \"}\n          <strong>opaque origin</strong> which:\n        </p>\n        <ul className=\"ml-4 list-disc space-y-1\">\n          <li>Cannot access parent document, localStorage, or cookies</li>\n          <li>Cannot use its own localStorage, cookies, or IndexedDB</li>\n          <li>Blocks Tauri IPC injection (Tauri only injects at app origin)</li>\n        </ul>\n        <p className=\"border-l-2 border-yellow-600 bg-yellow-950/30 p-2\">\n          <strong>⚠️ Web Security Note:</strong> On the web, blob URLs inherit\n          the creator&apos;s origin. The sandbox attribute creates isolation,\n          but for maximum security in production web apps, serve untrusted\n          content from a <strong>separate domain</strong> (e.g.,{\" \"}\n          <code>runtusercontent.com</code>) rather than a subdomain, as\n          subdomains can share cookies in some configurations.\n        </p>\n      </div>\n\n      {/* Production IsolatedFrame Demo */}\n      <ProductionFrameDemo />\n    </div>\n  );\n}\n\n/**\n * Demo of the production IsolatedFrame component.\n */\nfunction ProductionFrameDemo() {\n  const frameRef = useRef<IsolatedFrameHandle>(null);\n  const [isReady, setIsReady] = useState(false);\n  const [height, setHeight] = useState(0);\n\n  const handleRenderHtml = () => {\n    frameRef.current?.render({\n      mimeType: \"text/html\",\n      data: `\n        <h2 style=\"margin: 0 0 8px 0;\">Production IsolatedFrame Test</h2>\n        <p>This content was rendered via the <code>IsolatedFrame</code> component.</p>\n        <table>\n          <tr><th>Feature</th><th>Status</th></tr>\n          <tr><td>Blob URL isolation</td><td style=\"color: #4ade80;\">Works</td></tr>\n          <tr><td>postMessage communication</td><td style=\"color: #4ade80;\">Works</td></tr>\n          <tr><td>Auto-resizing</td><td style=\"color: #4ade80;\">Works</td></tr>\n        </table>\n        <script>console.log('Script executed in isolated frame!');</script>\n      `,\n    });\n  };\n\n  const handleRenderImage = () => {\n    // A small test image (1x1 red pixel in base64)\n    frameRef.current?.render({\n      mimeType: \"image/png\",\n      data: \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==\",\n    });\n  };\n\n  const handleClear = () => {\n    frameRef.current?.clear();\n  };\n\n  return (\n    <IsolatedRendererProvider basePath=\"/isolated\">\n      <div className=\"bg-muted border-border mt-4 space-y-3 rounded border-t p-3\">\n        <h3 className=\"font-medium\">Production IsolatedFrame Component:</h3>\n        <div className=\"flex items-center gap-2 text-sm\">\n          <span>\n            Ready:{\" \"}\n            <span className={isReady ? \"text-green-500\" : \"text-yellow-500\"}>\n              {isReady ? \"Yes\" : \"Waiting...\"}\n            </span>\n          </span>\n          <span className=\"text-muted-foreground\">|</span>\n          <span>Height: {height}px</span>\n        </div>\n        <div className=\"flex flex-wrap gap-2\">\n          <button\n            onClick={handleRenderHtml}\n            disabled={!isReady}\n            className=\"rounded bg-blue-600 px-3 py-1.5 text-sm hover:bg-blue-700 disabled:bg-gray-600\"\n          >\n            Render HTML\n          </button>\n          <button\n            onClick={handleRenderImage}\n            disabled={!isReady}\n            className=\"rounded bg-purple-600 px-3 py-1.5 text-sm hover:bg-purple-700 disabled:bg-gray-600\"\n          >\n            Render Image\n          </button>\n          <button\n            onClick={handleClear}\n            disabled={!isReady}\n            className=\"rounded bg-red-600 px-3 py-1.5 text-sm hover:bg-red-700 disabled:bg-gray-600\"\n          >\n            Clear\n          </button>\n        </div>\n        <div className=\"overflow-hidden rounded border\">\n          <IsolatedFrame\n            ref={frameRef}\n            darkMode={true}\n            minHeight={48}\n            maxHeight={400}\n            onReady={() => setIsReady(true)}\n            onResize={setHeight}\n            onLinkClick={(url, newTab) => {\n              console.log(\"Link clicked:\", url, newTab);\n              window.open(url, newTab ? \"_blank\" : \"_self\");\n            }}\n            onError={(err) => console.error(\"Frame error:\", err)}\n          />\n        </div>\n      </div>\n    </IsolatedRendererProvider>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/isolated/IsolationTest.tsx"
    },
    {
      "path": "registry/outputs/isolated/isolated-renderer-context.tsx",
      "content": "\"use client\";\n\nimport {\n  createContext,\n  type ReactNode,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\n\ninterface IsolatedRendererBundle {\n  rendererCode: string;\n  rendererCss: string;\n}\n\ninterface IsolatedRendererContextValue {\n  rendererCode: string | undefined;\n  rendererCss: string | undefined;\n  isLoading: boolean;\n  error: Error | null;\n}\n\nconst IsolatedRendererContext =\n  createContext<IsolatedRendererContextValue | null>(null);\n\ninterface IsolatedRendererProviderProps {\n  children: ReactNode;\n  /** Base path to fetch isolated-renderer.js and isolated-renderer.css from */\n  basePath?: string;\n  /** Custom loader function (e.g., for Vite virtual modules) */\n  loader?: () => Promise<IsolatedRendererBundle>;\n}\n\n// Module-level cache (shared across all provider instances)\nlet bundleCache: IsolatedRendererBundle | null = null;\nlet loadingPromise: Promise<IsolatedRendererBundle> | null = null;\n\n/**\n * Provider for the isolated renderer bundle.\n *\n * Wrap your app (or the part that uses IsolatedFrame) with this provider.\n *\n * @example\n * // Option A: Fetch from a URL path\n * <IsolatedRendererProvider basePath=\"/isolated\">\n *   <App />\n * </IsolatedRendererProvider>\n *\n * @example\n * // Option B: Use Vite virtual module (for Tauri/bundled apps)\n * <IsolatedRendererProvider loader={() => import(\"virtual:isolated-renderer\")}>\n *   <App />\n * </IsolatedRendererProvider>\n */\nexport function IsolatedRendererProvider({\n  children,\n  basePath,\n  loader,\n}: IsolatedRendererProviderProps) {\n  const [state, setState] = useState<IsolatedRendererContextValue>(() => ({\n    rendererCode: bundleCache?.rendererCode,\n    rendererCss: bundleCache?.rendererCss,\n    isLoading: !bundleCache,\n    error: null,\n  }));\n\n  useEffect(() => {\n    if (bundleCache) {\n      // Already loaded, update state if needed\n      if (state.isLoading) {\n        setState({\n          rendererCode: bundleCache.rendererCode,\n          rendererCss: bundleCache.rendererCss,\n          isLoading: false,\n          error: null,\n        });\n      }\n      return;\n    }\n\n    let cancelled = false;\n\n    if (!loadingPromise) {\n      if (loader) {\n        // Use custom loader (Vite plugin, etc.)\n        loadingPromise = loader();\n      } else if (basePath) {\n        // Fetch from URL\n        loadingPromise = Promise.all([\n          fetch(`${basePath}/isolated-renderer.js`).then((r) => {\n            if (!r.ok)\n              throw new Error(`Failed to fetch renderer JS: ${r.status}`);\n            return r.text();\n          }),\n          fetch(`${basePath}/isolated-renderer.css`).then((r) => {\n            if (!r.ok)\n              throw new Error(`Failed to fetch renderer CSS: ${r.status}`);\n            return r.text();\n          }),\n        ]).then(([js, css]) => ({ rendererCode: js, rendererCss: css }));\n      } else {\n        const error = new Error(\n          \"IsolatedRendererProvider requires either 'basePath' or 'loader' prop. \" +\n            \"See: https://elements.nteract.io/docs/outputs/isolated-frame#setup\",\n        );\n        setState((s) => ({ ...s, isLoading: false, error }));\n        return;\n      }\n    }\n\n    loadingPromise\n      .then((bundle) => {\n        bundleCache = bundle;\n        if (!cancelled) {\n          setState({\n            rendererCode: bundle.rendererCode,\n            rendererCss: bundle.rendererCss,\n            isLoading: false,\n            error: null,\n          });\n        }\n      })\n      .catch((error) => {\n        console.error(\"[IsolatedRendererProvider] Bundle load failed:\", error);\n        loadingPromise = null; // Allow retry on next mount\n        if (!cancelled) {\n          setState((s) => ({ ...s, isLoading: false, error }));\n        }\n      });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [basePath, loader, state.isLoading]);\n\n  return (\n    <IsolatedRendererContext.Provider value={state}>\n      {children}\n    </IsolatedRendererContext.Provider>\n  );\n}\n\n// Default state when no provider is present (e.g., during SSR)\nconst NO_PROVIDER_STATE: IsolatedRendererContextValue = {\n  rendererCode: undefined,\n  rendererCss: undefined,\n  isLoading: true,\n  error: null,\n};\n\n/**\n * Hook to access the isolated renderer bundle.\n *\n * Returns a \"loading\" state if used outside IsolatedRendererProvider,\n * which allows components to render safely during SSR.\n * In development, logs a warning when no provider is present.\n */\nexport function useIsolatedRenderer(): IsolatedRendererContextValue {\n  const context = useContext(IsolatedRendererContext);\n  if (!context) {\n    // During SSR or when provider is missing, return a \"not ready\" state\n    // This allows components to render without crashing\n    if (\n      process.env.NODE_ENV === \"development\" &&\n      typeof window !== \"undefined\"\n    ) {\n      console.warn(\n        \"useIsolatedRenderer: No IsolatedRendererProvider found. \" +\n          \"Wrap your app with <IsolatedRendererProvider>. \" +\n          \"See: https://elements.nteract.io/docs/outputs/isolated-frame#setup\",\n      );\n    }\n    return NO_PROVIDER_STATE;\n  }\n  return context;\n}\n\n/**\n * Reset the bundle cache (useful for testing).\n * @internal\n */\nexport function _resetBundleCache() {\n  bundleCache = null;\n  loadingPromise = null;\n}\n",
      "type": "registry:component",
      "target": "components/isolated/isolated-renderer-context.tsx"
    }
  ],
  "type": "registry:component"
}