{"version":3,"file":"buildShellSrcDoc.mjs","names":[],"sources":["../../src/HtmlPreview/buildShellSrcDoc.ts"],"sourcesContent":["import { buildAutoHeightScript } from './injectAutoHeightScript';\nimport { STORAGE_SHIM_SCRIPT } from './injectStorageShim';\n\nexport const SHELL_UPDATE_MESSAGE_TYPE = 'lobe-html-shell-update';\n\ninterface BuildShellSrcDocOptions {\n  background?: string;\n  frameId: string;\n}\n\n/**\n * Build the iframe's one-and-only document.\n *\n * Why a \"shell\" doc:\n * The iframe is loaded *once* and never reloads during a streaming session.\n * All subsequent body updates arrive via `postMessage` from the parent\n * (see `SHELL_UPDATE_MESSAGE_TYPE`). The script in this document morphs the\n * live DOM in place, so already-painted nodes stay untouched — only nodes\n * that are *new* to this commit get a `.lobe-html-new` class and a CSS\n * fade-in. No iframe reload means no white flash, no script reboots, and\n * no jitter from height resets.\n */\nexport const buildShellSrcDoc = ({ background, frameId }: BuildShellSrcDocOptions): string => {\n  const baseRules = `html,body{margin:0;padding:0;${background ? `background:${background};` : ''}color-scheme:light dark;}`;\n  const fadeRules = `@keyframes lobe-html-fade{from{opacity:0}to{opacity:1}}.lobe-html-new{animation:lobe-html-fade 240ms ease-out both;}`;\n\n  const morphScript = `\n(function () {\n  var FRAME_ID = ${JSON.stringify(frameId)};\n  var UPDATE_TYPE = ${JSON.stringify(SHELL_UPDATE_MESSAGE_TYPE)};\n\n  function cloneScript(src) {\n    // <script> elements parsed via DOMParser are inert. Rebuild them as\n    // proper DOM scripts so the browser executes them.\n    //\n    // Important: only set .text for inline scripts. Setting it on a\n    // src-bearing script (even to an empty string) causes some browser /\n    // extension combinations to treat the element as an inline script\n    // with empty body and skip the external fetch — so the CDN never\n    // loads. We just copy attributes; the browser will fetch the src on\n    // append.\n    var s = document.createElement('script');\n    for (var i = 0; i < src.attributes.length; i++) {\n      var a = src.attributes[i];\n      s.setAttribute(a.name, a.value);\n    }\n    if (!src.hasAttribute('src')) {\n      var text = src.textContent;\n      if (text) s.text = text;\n    }\n    return s;\n  }\n\n  function importNode(node) {\n    if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT') {\n      return cloneScript(node);\n    }\n    return document.importNode(node, true);\n  }\n\n  function markFadeIn(node) {\n    if (node.nodeType === Node.ELEMENT_NODE && node.classList) {\n      node.classList.add('lobe-html-new');\n    }\n  }\n\n  // Recursive prefix-match morph. For each parent we match leading children\n  // that already exist (by outerHTML or recursive morph), then we remove\n  // trailing children that no longer exist, and finally append the new\n  // tail with a fade-in class.\n  function morph(oldEl, newEl) {\n    if (oldEl.nodeType !== newEl.nodeType) return false;\n    if (oldEl.nodeType !== Node.ELEMENT_NODE) return false;\n    if (oldEl.tagName !== newEl.tagName) return false;\n\n    // Sync attributes\n    var oldAttrs = oldEl.attributes;\n    for (var i = oldAttrs.length - 1; i >= 0; i--) {\n      var an = oldAttrs[i].name;\n      if (!newEl.hasAttribute(an)) oldEl.removeAttribute(an);\n    }\n    var newAttrs = newEl.attributes;\n    for (var j = 0; j < newAttrs.length; j++) {\n      var na = newAttrs[j];\n      if (oldEl.getAttribute(na.name) !== na.value) {\n        oldEl.setAttribute(na.name, na.value);\n      }\n    }\n\n    var oldKids = oldEl.childNodes;\n    var newKids = newEl.childNodes;\n    var commonLen = 0;\n\n    while (commonLen < oldKids.length && commonLen < newKids.length) {\n      var o = oldKids[commonLen];\n      var n = newKids[commonLen];\n      if (o.nodeType !== n.nodeType) break;\n      if (o.nodeType === Node.TEXT_NODE) {\n        if (o.textContent !== n.textContent) {\n          // Update text content in place — no fade for text.\n          o.textContent = n.textContent;\n        }\n        commonLen++;\n      } else if (o.nodeType === Node.ELEMENT_NODE) {\n        // Cheap identity check before recursing.\n        if (o.outerHTML === n.outerHTML) {\n          commonLen++;\n        } else if (morph(o, n)) {\n          commonLen++;\n        } else {\n          break;\n        }\n      } else {\n        commonLen++;\n      }\n    }\n\n    // Trim old trailing children that no longer exist.\n    while (oldEl.childNodes.length > commonLen) {\n      oldEl.removeChild(oldEl.lastChild);\n    }\n\n    // Append the new tail with fade-in markers, batched through a\n    // DocumentFragment. A flat sequence of appendChild calls fires one\n    // mutation per element — MutationObserver libraries (Tailwind Play\n    // CDN, Stimulus, etc.) that batch their work can drop intermediate\n    // notifications if they arrive too quickly. Going through a fragment\n    // delivers exactly one childList mutation that lists all new nodes\n    // at once, which observers handle reliably.\n    if (commonLen < newKids.length) {\n      var frag = document.createDocumentFragment();\n      for (var k = commonLen; k < newKids.length; k++) {\n        var imported = importNode(newKids[k]);\n        markFadeIn(imported);\n        frag.appendChild(imported);\n      }\n      oldEl.appendChild(frag);\n    }\n\n    return true;\n  }\n\n  // Track which head extras (scripts/links/meta/title/base) we've already\n  // mounted so re-arriving chunks don't re-execute scripts or duplicate\n  // resources. Keyed by outerHTML — for streaming partial URLs each\n  // partial-and-then-complete tag is a distinct key, which means a partial\n  // CDN URL may briefly 404 before the complete one succeeds. That's\n  // acceptable; the alternative (waiting for the closing tag heuristic) is\n  // fragile and would defeat the live-CDN use case entirely.\n  var headSeen = Object.create(null);\n\n  function syncHeadExtras(headExtrasHtml) {\n    if (typeof headExtrasHtml !== 'string') return;\n    var parser = new DOMParser();\n    var doc = parser.parseFromString(\n      '<!doctype html><html><head>' + headExtrasHtml + '</head></html>',\n      'text/html',\n    );\n    var children = doc.head ? doc.head.children : [];\n    for (var i = 0; i < children.length; i++) {\n      var src = children[i];\n      var key = src.outerHTML;\n      if (headSeen[key]) continue;\n      headSeen[key] = true;\n      var clone = importNode(src);\n      // Tag for debugging — also keeps these distinguishable from the\n      // shell's own head children if anything ever needs to inspect them.\n      if (clone.setAttribute) clone.setAttribute('data-lobe-user', '');\n      document.head.appendChild(clone);\n    }\n  }\n\n  function applyUpdate(payload) {\n    if (!payload) return;\n\n    // 1) Inline user styles: merged into a single growing <style> element.\n    //    Streaming partial CSS just keeps overwriting this text until the\n    //    rules become complete, so we don't stack half-parsed <style>\n    //    blocks in the head.\n    var styleEl = document.getElementById('lobe-user-style');\n    if (styleEl && styleEl.textContent !== payload.styleContent) {\n      styleEl.textContent = payload.styleContent || '';\n    }\n\n    // 2) Everything else in the user's <head> (scripts, links, meta, …):\n    //    append-with-dedupe so head-loaded resources actually run.\n    syncHeadExtras(payload.headExtrasHtml);\n\n    // 3) Body: in-place morph with fade-in on new nodes.\n    var bodyParser = new DOMParser();\n    var newDoc = bodyParser.parseFromString(\n      '<!doctype html><html><body>' + (payload.bodyHtml || '') + '</body></html>',\n      'text/html',\n    );\n\n    // morph() returns false only for type mismatch on the root — body to\n    // body always matches, so this is safe.\n    morph(document.body, newDoc.body);\n\n    // Nudge class-engine CDNs (Tailwind Play CDN, Stimulus, etc.) into\n    // re-scanning the document. They watch via MutationObserver but some\n    // implementations only consider the directly-mutated nodes from each\n    // record and skip recursing into nested descendants, so deeply-styled\n    // subtrees can end up with un-generated utility classes. Toggling a\n    // throwaway class on body produces an attribute mutation that prompts\n    // a fresh full-document scan.\n    try {\n      document.body.classList.add('_lobe-rescan');\n      document.body.classList.remove('_lobe-rescan');\n    } catch (_) {}\n  }\n\n  window.addEventListener('message', function (event) {\n    var data = event.data;\n    if (!data || data.type !== UPDATE_TYPE || data.frameId !== FRAME_ID) return;\n    applyUpdate(data.payload);\n  });\n\n  // Signal the parent that the listener is wired up so it can flush any\n  // pending content that was queued before this script ran.\n  try {\n    parent.postMessage({ type: UPDATE_TYPE + ':ready', frameId: FRAME_ID }, '*');\n  } catch (_) {}\n})();\n`;\n\n  return `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<style>${baseRules}${fadeRules}</style>\n<style id=\"lobe-user-style\"></style>\n<script>${STORAGE_SHIM_SCRIPT}</script>\n<script>${buildAutoHeightScript(frameId)}</script>\n<script>${morphScript}</script>\n</head>\n<body></body>\n</html>`;\n};\n"],"mappings":";;;AAGA,MAAa,4BAA4B;;;;;;;;;;;;;AAmBzC,MAAa,oBAAoB,EAAE,YAAY,cAA+C;CAC5F,MAAM,YAAY,gCAAgC,aAAa,cAAc,WAAW,KAAK,GAAG;CAChG,MAAM,YAAY;CAElB,MAAM,cAAc;;mBAEH,KAAK,UAAU,QAAQ,CAAC;sBACrB,KAAK,UAAU,0BAA0B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqM9D,QAAO;;;;SAIA,YAAY,UAAU;;UAErB,oBAAoB;UACpB,sBAAsB,QAAQ,CAAC;UAC/B,YAAY"}