{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "isolated-renderer",
  "title": "Isolated Renderer",
  "description": "Complete isolated iframe renderer bundle for Jupyter outputs and widgets. Runs inside a sandboxed iframe with its own React app, receiving render commands via postMessage. Includes widget bridge client for two-way comm protocol support. This is a standalone entry point that can be built separately (includes vite.config.ts).",
  "devDependencies": [
    "@tailwindcss/vite",
    "@vitejs/plugin-react",
    "vite"
  ],
  "registryDependencies": [
    "@nteract/error-boundary",
    "@nteract/ansi-output",
    "@nteract/html-output",
    "@nteract/image-output",
    "@nteract/isolated-frame",
    "@nteract/json-output",
    "@nteract/markdown-output",
    "@nteract/svg-output",
    "@nteract/widget-view",
    "@nteract/widget-controls"
  ],
  "files": [
    {
      "path": "registry/isolated-renderer/index.tsx",
      "content": "/**\n * Isolated Renderer Entry Point\n *\n * This module runs inside an isolated iframe and renders Jupyter outputs\n * using React and the existing output components. It receives render\n * commands from the parent via postMessage and displays them.\n *\n * Security: This code runs in a sandboxed iframe with an opaque origin.\n * It cannot access Tauri APIs, the parent DOM, or localStorage.\n */\n\nimport { StrictMode, useCallback, useEffect, useState } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\n// Import styles (Tailwind + theme variables)\nimport \"./styles.css\";\n\n// Import error boundary for fault isolation\nimport { ErrorBoundary } from \"@/registry/lib/error-boundary\";\nimport { OutputErrorFallback } from \"@/registry/lib/output-error-fallback\";\n\n// Import output components directly (not through MediaRouter's lazy loading)\n// This ensures all components are bundled inline for the isolated iframe\nimport {\n  AnsiErrorOutput,\n  AnsiOutput,\n  AnsiStreamOutput,\n} from \"@/registry/outputs/ansi-output\";\nimport { HtmlOutput } from \"@/registry/outputs/html-output\";\nimport { ImageOutput } from \"@/registry/outputs/image-output\";\nimport type { RenderPayload } from \"@/registry/outputs/isolated/frame-bridge\";\nimport { JsonOutput } from \"@/registry/outputs/json-output\";\nimport { MarkdownOutput } from \"@/registry/outputs/markdown-output\";\nimport { SvgOutput } from \"@/registry/outputs/svg-output\";\nimport { WidgetView } from \"@/registry/widgets/widget-view\";\n// Import widget support\nimport { IframeWidgetStoreProvider } from \"./widget-provider\";\n\n// Import widget controls to register them in the widget registry\n// This import has side effects that register all built-in widgets\nimport \"@/registry/widgets/controls\";\n\n// --- Types ---\n\ninterface OutputEntry {\n  id: string;\n  payload: RenderPayload;\n}\n\ninterface RendererState {\n  outputs: OutputEntry[];\n  isDark: boolean;\n}\n\n// --- Theme Management ---\n\n/**\n * Update the document theme so components can detect it via isDarkMode().\n * Sets class and data-theme on documentElement (html tag).\n */\nfunction updateDocumentTheme(isDark: boolean) {\n  const root = document.documentElement;\n\n  // Set class for Tailwind dark: variant detection\n  if (isDark) {\n    root.classList.add(\"dark\");\n    root.classList.remove(\"light\");\n  } else {\n    root.classList.add(\"light\");\n    root.classList.remove(\"dark\");\n  }\n\n  // Set data-theme for components that check this attribute\n  root.setAttribute(\"data-theme\", isDark ? \"dark\" : \"light\");\n\n  // Set color-scheme to influence prefers-color-scheme media queries\n  // Some widgets (like drawdata) use @media (prefers-color-scheme: dark)\n  root.style.colorScheme = isDark ? \"dark\" : \"light\";\n\n  // Update CSS variables for base styles (background kept transparent for cell focus colors to show through)\n  if (isDark) {\n    root.style.setProperty(\"--bg-primary\", \"#0a0a0a\");\n    root.style.setProperty(\"--bg-secondary\", \"#1a1a1a\");\n    root.style.setProperty(\"--text-primary\", \"#e0e0e0\");\n    root.style.setProperty(\"--text-secondary\", \"#a0a0a0\");\n    root.style.setProperty(\"--foreground\", \"#e0e0e0\");\n  } else {\n    root.style.setProperty(\"--bg-primary\", \"#ffffff\");\n    root.style.setProperty(\"--bg-secondary\", \"#f5f5f5\");\n    root.style.setProperty(\"--text-primary\", \"#1a1a1a\");\n    root.style.setProperty(\"--text-secondary\", \"#666666\");\n    root.style.setProperty(\"--foreground\", \"#1a1a1a\");\n  }\n}\n\n// --- Message Handling ---\n\ntype MessageHandler = (type: string, payload: unknown) => void;\n\nlet messageHandler: MessageHandler | null = null;\n\nfunction setupMessageListener() {\n  window.addEventListener(\"message\", (event) => {\n    // Only accept messages from parent\n    if (event.source !== window.parent) return;\n\n    const { type, payload } = event.data || {};\n    if (messageHandler) {\n      messageHandler(type, payload);\n    }\n  });\n}\n\n// --- React App ---\n\nfunction IsolatedRendererApp() {\n  const [state, setState] = useState<RendererState>({\n    outputs: [],\n    isDark: true,\n  });\n\n  // Handle messages from parent\n  const handleMessage = useCallback((type: string, payload: unknown) => {\n    switch (type) {\n      case \"render\": {\n        const renderPayload = payload as RenderPayload;\n\n        // Generate stable ID when cellId is provided for better React reconciliation\n        const id = renderPayload.cellId\n          ? `${renderPayload.cellId}-${renderPayload.outputIndex ?? 0}`\n          : `output-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n\n        setState((prev) => {\n          if (renderPayload.replace) {\n            // Replace all outputs with this single new output\n            return { ...prev, outputs: [{ id, payload: renderPayload }] };\n          }\n          // Default: append to existing outputs\n          return {\n            ...prev,\n            outputs: [...prev.outputs, { id, payload: renderPayload }],\n          };\n        });\n\n        // Notify parent of render completion after next paint\n        requestAnimationFrame(() => {\n          window.parent.postMessage(\n            {\n              type: \"render_complete\",\n              payload: { height: document.body.scrollHeight },\n            },\n            \"*\",\n          );\n        });\n        break;\n      }\n\n      case \"clear\":\n        setState((prev) => ({ ...prev, outputs: [] }));\n        requestAnimationFrame(() => {\n          window.parent.postMessage(\n            {\n              type: \"render_complete\",\n              payload: { height: document.body.scrollHeight },\n            },\n            \"*\",\n          );\n        });\n        break;\n\n      case \"theme\": {\n        const themePayload = payload as { isDark?: boolean };\n        if (themePayload?.isDark !== undefined) {\n          setState((prev) => ({ ...prev, isDark: themePayload.isDark! }));\n          // Update theme on document.documentElement so theme detection works\n          updateDocumentTheme(themePayload.isDark);\n        }\n        break;\n      }\n    }\n  }, []);\n\n  // Register message handler and notify parent when ready\n  useEffect(() => {\n    messageHandler = handleMessage;\n\n    // Now that the handler is registered, notify parent that renderer is ready\n    // This ensures messages won't be dropped due to race conditions\n    window.parent.postMessage({ type: \"renderer_ready\" }, \"*\");\n\n    return () => {\n      messageHandler = null;\n    };\n  }, [handleMessage]);\n\n  return (\n    <div\n      className=\"isolated-renderer\"\n      data-theme={state.isDark ? \"dark\" : \"light\"}\n    >\n      {state.outputs.map((entry, index) => (\n        <ErrorBoundary\n          key={entry.id}\n          resetKeys={[JSON.stringify(entry.payload)]}\n          fallback={(error, reset) => (\n            <OutputErrorFallback\n              error={error}\n              outputIndex={index}\n              onRetry={reset}\n            />\n          )}\n          onError={(error, errorInfo) => {\n            // Send error back to parent\n            window.parent.postMessage(\n              {\n                type: \"error\",\n                payload: {\n                  message: error.message,\n                  stack: errorInfo.componentStack,\n                },\n              },\n              \"*\",\n            );\n          }}\n        >\n          <OutputRenderer payload={entry.payload} />\n        </ErrorBoundary>\n      ))}\n    </div>\n  );\n}\n\n/**\n * Render a single output based on its MIME type.\n * Uses direct component imports (not lazy loading) for isolated iframe compatibility.\n */\nfunction OutputRenderer({ payload }: { payload: RenderPayload }) {\n  const { mimeType, data, metadata } = payload;\n  const content = data;\n\n  // Handle stream output (plain text with potential ANSI)\n  if (mimeType === \"text/plain\" && metadata?.streamName) {\n    return (\n      <AnsiStreamOutput\n        text={String(data)}\n        streamName={metadata.streamName as \"stdout\" | \"stderr\"}\n      />\n    );\n  }\n\n  // Handle error output\n  if (mimeType === \"text/plain\" && metadata?.isError) {\n    return (\n      <AnsiErrorOutput\n        ename={String(metadata.ename || \"Error\")}\n        evalue={String(metadata.evalue || \"\")}\n        traceback={\n          Array.isArray(metadata.traceback)\n            ? metadata.traceback.map(String)\n            : [String(data)]\n        }\n      />\n    );\n  }\n\n  // Route to appropriate component based on MIME type\n  // (Direct rendering without MediaRouter's lazy loading)\n\n  // Widget view - render interactive Jupyter widget\n  if (mimeType === \"application/vnd.jupyter.widget-view+json\") {\n    const widgetData = data as { model_id: string };\n    return <WidgetView modelId={widgetData.model_id} />;\n  }\n\n  // Markdown\n  if (mimeType === \"text/markdown\") {\n    return <MarkdownOutput content={String(content)} />;\n  }\n\n  // HTML\n  if (mimeType === \"text/html\") {\n    return <HtmlOutput content={String(content)} />;\n  }\n\n  // SVG\n  if (mimeType === \"image/svg+xml\") {\n    return <SvgOutput data={String(content)} />;\n  }\n\n  // Images (PNG, JPEG, GIF, WebP)\n  if (mimeType.startsWith(\"image/\")) {\n    return (\n      <ImageOutput\n        data={String(content)}\n        mediaType={\n          mimeType as \"image/png\" | \"image/jpeg\" | \"image/gif\" | \"image/webp\"\n        }\n        width={metadata?.width as number | undefined}\n        height={metadata?.height as number | undefined}\n      />\n    );\n  }\n\n  // JSON\n  if (mimeType === \"application/json\") {\n    const jsonData =\n      typeof content === \"string\" ? JSON.parse(content) : content;\n    return <JsonOutput data={jsonData} />;\n  }\n\n  // Plain text / ANSI\n  if (mimeType === \"text/plain\") {\n    return <AnsiOutput>{String(content)}</AnsiOutput>;\n  }\n\n  // Fallback: render as plain text\n  return (\n    <pre style={{ whiteSpace: \"pre-wrap\", wordWrap: \"break-word\" }}>\n      {typeof content === \"string\" ? content : JSON.stringify(content, null, 2)}\n    </pre>\n  );\n}\n\n// --- Bootstrap ---\n\nlet root: Root | null = null;\n\n/**\n * Initialize the renderer. Called when the bundle is eval'd in the iframe.\n */\n// Declare the global flag type for TypeScript\ndeclare global {\n  interface Window {\n    __REACT_RENDERER_ACTIVE__?: boolean;\n  }\n}\n\nexport function init() {\n  // Signal to the inline handler that React is taking over\n  // This prevents the inline handler from processing render/theme/clear messages\n  window.__REACT_RENDERER_ACTIVE__ = true;\n\n  // Set up message listener\n  setupMessageListener();\n\n  // Initialize theme to dark (default) - will be updated by parent if needed\n  updateDocumentTheme(true);\n\n  // Create root element if needed\n  let rootEl = document.getElementById(\"root\");\n  if (!rootEl) {\n    rootEl = document.createElement(\"div\");\n    rootEl.id = \"root\";\n    document.body.appendChild(rootEl);\n  }\n\n  // Create React root and render with widget provider\n  root = createRoot(rootEl);\n  root.render(\n    <StrictMode>\n      <IframeWidgetStoreProvider>\n        <IsolatedRendererApp />\n      </IframeWidgetStoreProvider>\n    </StrictMode>,\n  );\n\n  // Set up resize observer\n  const resizeObserver = new ResizeObserver(() => {\n    window.parent.postMessage(\n      { type: \"resize\", payload: { height: document.body.scrollHeight } },\n      \"*\",\n    );\n  });\n  resizeObserver.observe(document.body);\n\n  // Note: \"renderer_ready\" is sent from the React component's useEffect\n  // to ensure the message handler is registered before parent sends messages\n}\n\n// Auto-init if this is the main module being eval'd\n// The parent will send us via eval, so we auto-start\nif (typeof window !== \"undefined\") {\n  // Check if we're being eval'd (window.currentMessage exists from bootstrap)\n  // If so, init immediately. Otherwise, export for manual init.\n  init();\n}\n",
      "type": "registry:component",
      "target": "isolated-renderer/index.tsx"
    },
    {
      "path": "registry/isolated-renderer/widget-provider.tsx",
      "content": "/**\n * Widget Provider for Isolated Iframe\n *\n * This module provides the same React context interface as the main app's\n * WidgetStoreProvider, but uses the CommBridgeClient to proxy communication\n * through postMessage to the parent window.\n *\n * This allows existing widget components to work unchanged inside the iframe.\n * We provide to both the main WidgetStoreContext (so WidgetView works) and\n * our own IframeWidgetStoreContext (for iframe-specific code if needed).\n */\n\nimport {\n  createContext,\n  type ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useSyncExternalStore,\n} from \"react\";\nimport { createCanvasManagerRouter } from \"@/registry/widgets/canvas-manager-subscriptions\";\nimport { createLinkManager } from \"@/registry/widgets/link-subscriptions\";\nimport {\n  resolveModelRef,\n  type WidgetModel,\n  type WidgetStore,\n} from \"@/registry/widgets/widget-store\";\nimport { WidgetStoreContext } from \"@/registry/widgets/widget-store-context\";\nimport {\n  createWidgetBridgeClient,\n  type WidgetBridgeClient,\n} from \"./widget-bridge-client\";\n\n// === Context Types ===\n\ninterface IframeWidgetStoreContextValue {\n  store: WidgetStore;\n  /** Send a state update to the parent (which forwards to kernel) */\n  sendUpdate: (\n    commId: string,\n    state: Record<string, unknown>,\n    buffers?: ArrayBuffer[],\n  ) => void;\n  /** Send a custom message to the parent (which forwards to kernel) */\n  sendCustom: (\n    commId: string,\n    content: Record<string, unknown>,\n    buffers?: ArrayBuffer[],\n  ) => void;\n  /** Close a comm channel via parent */\n  closeComm: (commId: string) => void;\n}\n\n// === Context ===\n\nconst IframeWidgetStoreContext =\n  createContext<IframeWidgetStoreContextValue | null>(null);\n\n// === Provider ===\n\ninterface IframeWidgetStoreProviderProps {\n  children: ReactNode;\n}\n\n/**\n * Provider component for widgets in the isolated iframe.\n *\n * Creates a CommBridgeClient that:\n * - Maintains a local WidgetStore for state\n * - Receives comm messages from parent via postMessage\n * - Sends state updates/custom messages back to parent\n *\n * Also provides to WidgetStoreContext so existing widget components work unchanged.\n */\nexport function IframeWidgetStoreProvider({\n  children,\n}: IframeWidgetStoreProviderProps) {\n  // Create bridge client once\n  const clientRef = useRef<WidgetBridgeClient | null>(null);\n  if (!clientRef.current) {\n    clientRef.current = createWidgetBridgeClient();\n  }\n  const client = clientRef.current;\n\n  // Clean up on unmount\n  useEffect(() => {\n    return () => {\n      client.dispose();\n    };\n  }, [client]);\n\n  // Set up link subscriptions (jslink/jsdlink)\n  // Pass sendUpdate so linked values sync back to the kernel via the parent\n  useEffect(\n    () => createLinkManager(client.store, client.sendUpdate),\n    [client.store, client.sendUpdate],\n  );\n\n  // Set up canvas manager router (ipycanvas)\n  useEffect(() => createCanvasManagerRouter(client.store), [client.store]);\n\n  // Value for iframe-specific context\n  const iframeValue = useMemo<IframeWidgetStoreContextValue>(\n    () => ({\n      store: client.store,\n      sendUpdate: client.sendUpdate,\n      sendCustom: client.sendCustom,\n      closeComm: client.closeComm,\n    }),\n    [client],\n  );\n\n  // Value for main WidgetStoreContext (so existing widget components work)\n  // sendMessage parses Jupyter protocol and routes to bridge functions\n  const mainContextValue = useMemo(\n    () => ({\n      store: client.store,\n      handleMessage: () => {\n        // No-op: messages come through postMessage in iframe\n      },\n      sendMessage: (msg: {\n        content: {\n          comm_id?: string;\n          data?: {\n            method?: string;\n            state?: Record<string, unknown>;\n            content?: Record<string, unknown>;\n          };\n        };\n        buffers?: ArrayBuffer[];\n      }) => {\n        // Route Jupyter protocol messages to bridge functions\n        const commId = msg.content?.comm_id;\n        const method = msg.content?.data?.method;\n        const buffers = msg.buffers;\n\n        if (!commId) return;\n\n        if (method === \"update\" && msg.content?.data?.state) {\n          client.sendUpdate(commId, msg.content.data.state, buffers);\n        } else if (method === \"custom\" && msg.content?.data?.content) {\n          client.sendCustom(commId, msg.content.data.content, buffers);\n        }\n      },\n      sendUpdate: client.sendUpdate,\n      sendCustom: client.sendCustom,\n      closeComm: client.closeComm,\n    }),\n    [client],\n  );\n\n  return (\n    <WidgetStoreContext.Provider value={mainContextValue}>\n      <IframeWidgetStoreContext.Provider value={iframeValue}>\n        {children}\n      </IframeWidgetStoreContext.Provider>\n    </WidgetStoreContext.Provider>\n  );\n}\n\n// === Hooks ===\n\n/**\n * Access the iframe widget store context.\n * Returns null if used outside of IframeWidgetStoreProvider.\n */\nexport function useIframeWidgetStore(): IframeWidgetStoreContextValue | null {\n  return useContext(IframeWidgetStoreContext);\n}\n\n/**\n * Access the iframe widget store context, throwing if not available.\n */\nexport function useIframeWidgetStoreRequired(): IframeWidgetStoreContextValue {\n  const ctx = useContext(IframeWidgetStoreContext);\n  if (!ctx) {\n    throw new Error(\n      \"useIframeWidgetStoreRequired must be used within IframeWidgetStoreProvider\",\n    );\n  }\n  return ctx;\n}\n\n/**\n * Subscribe to all widget models.\n */\nexport function useIframeWidgetModels(): Map<string, WidgetModel> {\n  const { store } = useIframeWidgetStoreRequired();\n\n  return useSyncExternalStore(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot,\n  );\n}\n\n/**\n * Subscribe to a specific widget model.\n */\nexport function useIframeWidgetModel(modelId: string): WidgetModel | undefined {\n  const { store } = useIframeWidgetStoreRequired();\n\n  const subscribe = useCallback(\n    (callback: () => void) => store.subscribe(callback),\n    [store],\n  );\n\n  const getSnapshot = useCallback(\n    () => store.getModel(modelId),\n    [store, modelId],\n  );\n\n  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Subscribe to a specific key in a widget model's state.\n */\nexport function useIframeWidgetModelValue<T = unknown>(\n  modelId: string,\n  key: string,\n): T | undefined {\n  const { store } = useIframeWidgetStoreRequired();\n\n  const subscribe = useCallback(\n    (callback: () => void) => store.subscribeToKey(modelId, key, callback),\n    [store, modelId, key],\n  );\n\n  const getSnapshot = useCallback(\n    () => store.getModel(modelId)?.state[key] as T | undefined,\n    [store, modelId, key],\n  );\n\n  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Get a value from a widget model, resolving IPY_MODEL_ references.\n */\nexport function useIframeResolvedModelValue<T = unknown>(\n  modelId: string,\n  key: string,\n): T | WidgetModel | undefined {\n  const { store } = useIframeWidgetStoreRequired();\n  const value = useIframeWidgetModelValue(modelId, key);\n\n  const resolved = resolveModelRef(value, (id) => store.getModel(id));\n\n  return resolved as T | WidgetModel | undefined;\n}\n\n/**\n * Check if a widget model was explicitly closed.\n */\nexport function useIframeWasWidgetClosed(modelId: string): boolean {\n  const { store } = useIframeWidgetStoreRequired();\n  return store.wasModelClosed(modelId);\n}\n",
      "type": "registry:component",
      "target": "isolated-renderer/widget-provider.tsx"
    },
    {
      "path": "registry/isolated-renderer/widget-bridge-client.ts",
      "content": "/**\n * Widget Bridge Client - Iframe Side\n *\n * This module runs inside the isolated iframe and manages widget communication\n * with the parent window. It:\n * - Creates a local WidgetStore for widget state management\n * - Listens for comm_open/comm_msg/comm_close from parent via postMessage\n * - Provides methods to send state updates and custom messages back to parent\n * - Sends `widget_ready` when initialized\n *\n * Security: This code runs in a sandboxed iframe with an opaque origin.\n * It cannot access Tauri APIs, the parent DOM, or localStorage.\n */\n\nimport type {\n  CommCloseMessage,\n  CommMsgMessage,\n  CommOpenMessage,\n  CommSyncMessage,\n  WidgetCommCloseMessage,\n  WidgetCommMsgMessage,\n} from \"@/registry/outputs/isolated/frame-bridge\";\nimport {\n  createWidgetStore,\n  type WidgetStore,\n} from \"@/registry/widgets/widget-store\";\n\n// Type for method parameter in comm messages\ntype CommMethod = \"update\" | \"custom\";\n\n/**\n * Interface for the widget bridge client.\n * Provides access to the local store and methods to communicate with parent.\n */\nexport interface WidgetBridgeClient {\n  /** The local widget store for this iframe */\n  store: WidgetStore;\n\n  /**\n   * Send a state update to the parent (to be forwarded to kernel).\n   * Called when a widget's state changes due to user interaction.\n   */\n  sendUpdate: (\n    commId: string,\n    state: Record<string, unknown>,\n    buffers?: ArrayBuffer[],\n  ) => void;\n\n  /**\n   * Send a custom message to the parent (to be forwarded to kernel).\n   * Used for widget-specific protocols (e.g., ipycanvas draw commands).\n   */\n  sendCustom: (\n    commId: string,\n    content: Record<string, unknown>,\n    buffers?: ArrayBuffer[],\n  ) => void;\n\n  /**\n   * Request to close a comm (to be forwarded to kernel).\n   */\n  closeComm: (commId: string) => void;\n\n  /**\n   * Clean up the bridge (remove event listeners).\n   */\n  dispose: () => void;\n}\n\n/**\n * Create a widget bridge client for the iframe.\n * This sets up:\n * - A local WidgetStore instance\n * - Message listener for parent → iframe comm messages\n * - Methods to send iframe → parent messages\n */\nexport function createWidgetBridgeClient(): WidgetBridgeClient {\n  // Create local widget store\n  const store = createWidgetStore();\n\n  // Message handler for parent → iframe messages\n  function handleMessage(event: MessageEvent) {\n    // Only accept messages from parent\n    if (event.source !== window.parent) return;\n\n    const message = event.data;\n    if (!message || typeof message.type !== \"string\") return;\n\n    switch (message.type) {\n      case \"bridge_ready\":\n        // Parent's comm bridge is ready, re-send widget_ready to trigger sync\n        sendWidgetReady();\n        break;\n      case \"comm_open\":\n        handleCommOpen(message as CommOpenMessage);\n        break;\n      case \"comm_msg\":\n        handleCommMsg(message as CommMsgMessage);\n        break;\n      case \"comm_close\":\n        handleCommClose(message as CommCloseMessage);\n        break;\n      case \"comm_sync\":\n        handleCommSync(message as CommSyncMessage);\n        break;\n    }\n  }\n\n  function sendWidgetReady() {\n    window.parent.postMessage({ type: \"widget_ready\" }, \"*\");\n  }\n\n  function handleCommOpen(msg: CommOpenMessage) {\n    const { commId, state, buffers } = msg.payload;\n    store.createModel(commId, state, buffers);\n  }\n\n  function handleCommMsg(msg: CommMsgMessage) {\n    const { commId, method, data, buffers } = msg.payload;\n\n    if (method === \"update\") {\n      // State update from kernel\n      store.updateModel(commId, data, buffers);\n    } else if (method === \"custom\") {\n      // Custom message from kernel (e.g., ipycanvas commands)\n      store.emitCustomMessage(commId, data, buffers);\n    }\n  }\n\n  function handleCommClose(msg: CommCloseMessage) {\n    const { commId } = msg.payload;\n    store.deleteModel(commId);\n  }\n\n  function handleCommSync(msg: CommSyncMessage) {\n    const { models } = msg.payload;\n\n    for (const model of models) {\n      store.createModel(model.commId, model.state, model.buffers);\n    }\n  }\n\n  // Set up message listener\n  window.addEventListener(\"message\", handleMessage);\n\n  // Send initial widget_ready to parent\n  // (Parent may not be listening yet; it will send bridge_ready when ready, and we'll re-send)\n  sendWidgetReady();\n\n  return {\n    store,\n\n    sendUpdate(\n      commId: string,\n      state: Record<string, unknown>,\n      buffers?: ArrayBuffer[],\n    ) {\n      // Update local store immediately for responsive UI (optimistic update)\n      store.updateModel(commId, state, buffers);\n\n      const msg: WidgetCommMsgMessage = {\n        type: \"widget_comm_msg\",\n        payload: {\n          commId,\n          method: \"update\" as CommMethod,\n          data: state,\n          buffers,\n        },\n      };\n      // Transfer buffers for efficiency (note: buffers are consumed by transfer)\n      window.parent.postMessage(msg, \"*\", buffers ?? []);\n    },\n\n    sendCustom(\n      commId: string,\n      content: Record<string, unknown>,\n      buffers?: ArrayBuffer[],\n    ) {\n      const msg: WidgetCommMsgMessage = {\n        type: \"widget_comm_msg\",\n        payload: {\n          commId,\n          method: \"custom\" as CommMethod,\n          data: content,\n          buffers,\n        },\n      };\n      // Transfer buffers for efficiency\n      window.parent.postMessage(msg, \"*\", buffers ?? []);\n    },\n\n    closeComm(commId: string) {\n      const msg: WidgetCommCloseMessage = {\n        type: \"widget_comm_close\",\n        payload: { commId },\n      };\n      window.parent.postMessage(msg, \"*\");\n    },\n\n    dispose() {\n      window.removeEventListener(\"message\", handleMessage);\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "isolated-renderer/widget-bridge-client.ts"
    },
    {
      "path": "registry/isolated-renderer/styles.css",
      "content": "/**\n * Styles for the isolated renderer.\n * Includes Tailwind CSS and theme variables matching the main app.\n */\n\n@import \"tailwindcss\";\n\n/* Source paths for Tailwind to scan for class names */\n@source \"../outputs/**/*.{ts,tsx}\";\n@source \"../widgets/**/*.{ts,tsx}\";\n@source \"../../components/ui/**/*.{ts,tsx}\";\n@source \"./**/*.{ts,tsx}\";\n\n/* Dark mode variant */\n@custom-variant dark (&:is(.dark *, [data-theme=\"dark\"] *));\n\n/* Tailwind v4 theme mapping (maps --color-* to CSS variables) */\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n}\n\n/* Light mode theme variables (matching main app) */\n:root {\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --destructive-foreground: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --radius: 0.625rem;\n}\n\n/* Dark mode theme variables (matching main app) */\n.dark,\n[data-theme=\"dark\"] {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.922 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.269 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --destructive-foreground: oklch(0.637 0.237 25.331);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n}\n\n/* Base styles */\n* {\n  border-color: var(--border);\n}\n\nhtml, body {\n  margin: 0;\n  padding: 0;\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  font-size: 14px;\n  line-height: 1.5;\n  background: transparent;\n  color: var(--foreground);\n}\n\nbody {\n  padding: 8px;\n}\n\n/* Code font - uses Tailwind's text-sm to match main app output styling */\npre, code {\n  @apply font-mono text-sm;\n}\n\n/* Output item spacing */\n.output-item {\n  margin-bottom: 8px;\n}\n\n/* Ensure images don't overflow */\nimg {\n  max-width: 100%;\n  height: auto;\n}\n\n/* Links */\na {\n  color: oklch(0.546 0.245 262.881);\n  text-decoration: none;\n}\n\n.dark a,\n[data-theme=\"dark\"] a {\n  color: oklch(0.707 0.165 254.624);\n}\n\na:hover {\n  text-decoration: underline;\n}\n\n/* Error styling */\n.error pre {\n  color: #ef4444;\n  background: #1a1010;\n}\n",
      "type": "registry:file",
      "target": "isolated-renderer/styles.css"
    },
    {
      "path": "registry/isolated-renderer/vite.config.ts",
      "content": "import path from \"node:path\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport react from \"@vitejs/plugin-react\";\nimport { defineConfig } from \"vite\";\n\n/**\n * Vite configuration for building the isolated-renderer bundle.\n *\n * This creates a single IIFE bundle that can be:\n * 1. Imported as a string\n * 2. Sent to an isolated iframe via postMessage eval\n * 3. Self-executes to initialize the React renderer\n *\n * The bundle includes React and all dependencies inline.\n */\nexport default defineConfig({\n  plugins: [react(), tailwindcss()],\n  resolve: {\n    alias: {\n      \"@/\": `${path.resolve(__dirname, \"../..\")}/`,\n    },\n  },\n  build: {\n    // Output as a library in IIFE format (self-executing)\n    lib: {\n      entry: path.resolve(__dirname, \"index.tsx\"),\n      name: \"IsolatedRenderer\",\n      formats: [\"iife\"],\n      fileName: () => \"isolated-renderer.js\",\n    },\n    outDir: path.resolve(__dirname, \"../../public/isolated\"),\n    emptyOutDir: true,\n    // Inline all dependencies (don't externalize anything)\n    rollupOptions: {\n      output: {\n        // Ensure everything is in one file\n        inlineDynamicImports: true,\n        // Control asset naming (for CSS)\n        assetFileNames: \"isolated-renderer.[ext]\",\n      },\n    },\n    // Don't minify for easier debugging (can enable in production)\n    minify: false,\n    // Generate source maps for debugging\n    sourcemap: true,\n  },\n  // Define to help with React production build\n  define: {\n    \"process.env.NODE_ENV\": JSON.stringify(\"production\"),\n  },\n});\n",
      "type": "registry:file",
      "target": "isolated-renderer/vite.config.ts"
    }
  ],
  "type": "registry:component"
}