{"record":{"id":"127fb4fca16b8c05","repo":"windmill-labs/windmill","slug":"app-preview-is-not-ready","errorCode":null,"errorMessage":"App preview is not ready","messagePattern":"App preview is not ready","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/raw_apps/RawAppEditor.svelte","lineNumber":1873,"sourceCode":"\t\t\tel.style.setProperty('white-space', replacement, 'important')\n\t\t\treturn () => {\n\t\t\t\tif (prev) el.style.setProperty('white-space', prev, prio)\n\t\t\t\telse el.style.removeProperty('white-space')\n\t\t\t}\n\t\t})\n\t\treturn () => restores.forEach((r) => r())\n\t}\n\n\t// Capture the live preview as a PNG data URL. The preview iframe\n\t// (/ui_builder/app-preview.html) is same-origin with no sandbox, so its rendered\n\t// document is reachable and can be serialized from here. There is no native\n\t// element-screenshot API; modern-screenshot reconstructs the DOM into an SVG\n\t// foreignObject, so a WebGL canvas is only captured when its context was created\n\t// with preserveDrawingBuffer. Lazy-imported so the library only loads on demand.\n\tconst captureScreenshot: RawAppScreenshotRequester = async () => {\n\t\tconst target = previewIframe?.contentDocument?.body\n\t\tif (!previewIframe || !previewIframeLoaded || !target) {\n\t\t\tthrow new Error('App preview is not ready')\n\t\t}\n\t\t// Collapsing the preview leaves the iframe mounted and populated at zero\n\t\t// width, which passes every check above and then fails inside the rasteriser\n\t\t// as an opaque decode error. Name the cause so the agent can act on it.\n\t\tif (!target.clientWidth || !target.clientHeight) {\n\t\t\tthrow new Error(\n\t\t\t\t'The app preview is collapsed, so there is nothing to capture. Ask the user to expand the preview panel, then try again.'\n\t\t\t)\n\t\t}\n\t\tconst { domToPng } = await import('modern-screenshot')\n\t\t// Above CSS resolution for small previews (a 1× capture of a ~900px preview\n\t\t// reads blurry next to the live render), sub-1× for oversized bodies — see\n\t\t// captureScale. maximumCanvasSize is the belt over that math: the rasterised\n\t\t// box can exceed the body's client size, and an unbounded canvas on a tall\n\t\t// scrolling app can freeze the tab before normalize ever bounds the pixels.\n\t\tconst scale = captureScale(Math.max(target.clientWidth, target.clientHeight))\n\t\tconst restore = pinSingleLineText(target)\n\t\ttry {","sourceCodeStart":1855,"sourceCodeEnd":1891,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/raw_apps/RawAppEditor.svelte#L1855-L1891","documentation":"captureScreenshot in RawAppEditor.svelte grabs the app preview via the preview iframe's contentDocument.body. It throws 'App preview is not ready' when the iframe reference is missing, the iframe has not reported load completion (previewIframeLoaded false), or the document body is unavailable — i.e. a screenshot was requested before the preview finished rendering.","triggerScenarios":"Requesting a screenshot (e.g. an AI/agent screenshot step) while the preview iframe is still loading, the iframe element is unmounted, or contentDocument.body is null (cross-origin/blocked document or very early load).","commonSituations":"Agent automation triggering screenshot immediately after deploy before the preview finishes loading; preview collapsed or hidden at mount time; slow app load (heavy queries) racing the screenshot request.","solutions":["Wait for the preview iframe to finish loading, then retry the screenshot","Ensure the preview pane is expanded (zero-size iframes fail downstream) and rendered","Reload the app editor and retry once the preview is visibly rendered","If it persists, check that the preview iframe is not blocked (browser extensions/iframe security)","Retry with a small delay after app deploy so the preview has time to mount"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const ready = !!previewIframe?.contentDocument?.body && previewIframeLoaded\nif (!ready) { await waitForPreviewLoad(5000) }","typeGuard":"function isPreviewReady(iframe: HTMLIFrameElement | null, loaded: boolean): boolean {\n  return !!iframe && loaded && !!iframe.contentDocument?.body\n}","tryCatchPattern":"try {\n  await captureScreenshot()\n} catch (e) {\n  if (e.message === 'App preview is not ready') {\n    await waitForEvent(previewLoaded, 10000)\n    await captureScreenshot()\n  }\n}","preventionTips":["Trigger screenshots only after the preview iframe's load event","Expand the preview pane before capturing (zero-size body breaks rasterisation)","Add a readiness wait/poll before agent-driven screenshot steps","Retry with backoff on slow-loading apps"],"tags":["screenshot","iframe","async"],"backgroundTag":"preview-not-ready","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}