Skip to content

Preview Restart System

Overview

The builder has two preview modes, each with a different restart mechanism:

ModeComponentWhen usedRestart method
Local buildLocalBuildPreview.vueBuilder (editing)Dispatches creative-data-update event to iframe
StandalonePreviewIframe.vueStandalone preview pageReloads iframe by changing its src URL

Both are rendered by PreviewPanel.vue, which switches between them based on isStandalonePreview.

Local Build Preview (Builder)

Files:

  • src/pages/Chatbots/components/BuilderVisuals/Preview/LocalBuildPreview.vue
  • src/preview-iframe.ts (runs inside the iframe)
  • preview-frame.html (iframe entry point)

How it works

  1. The iframe loads preview-frame.html, which runs preview-iframe.ts as a module script
  2. preview-iframe.ts registers a creative-data-update listener and posts preview-iframe-ready to the parent
  3. LocalBuildPreview watches Vuex block state changes and dispatches creative-data-update to the iframe's contentDocument
  4. Inside the iframe, handleEvent unmounts the previous Vue 3 creative app, clears the #preview container, and builds + mounts a new one via buildCreative() from Creative-Engine

Restart flow

Restart button (PreviewPanel)
  → $refs.previewIframe.reloadCreative()
  → LocalBuildPreview.localBuildCreative() [debounced 400ms]
  → Creates payload via createPayload()
  → Dispatches 'creative-data-update' to iframe contentDocument
  → preview-iframe.ts handleEvent():
    → creative.unmount()
    → preview.innerHTML = '' (clear leftover DOM)
    → buildCreative() → new Vue 3 app
    → requestAnimationFrame → creative.mount('#preview')

Key detail: preview-iframe-ready

The iframe loads preview-iframe.ts as a module script, which means event listeners are registered AFTER the iframe's load event fires. LocalBuildPreview listens for the preview-iframe-ready postMessage to know when the iframe is ready to receive creative-data-update events.

Standalone Preview

File: src/pages/Chatbots/components/BuilderVisuals/Preview/PreviewIframe.vue

How it works

  1. The iframe loads a built creative's tagFile URL (e.g., https://delivery.cavai.com/assets/creatives/{id}/{buildId}/built/assets/stub.js)
  2. This is a fully built creative running independently — no local build, no creative-data-update events
  3. The iframe is cross-origin (delivery CDN), so the parent cannot access its DOM or dispatch events

Restart flow

Restart button (PreviewPanel)
  → $refs.previewIframe.reloadCreative()
  → PreviewIframe.reloadCreativeCORSWorkaround()
  → Sets iframeUrl to null (removes iframe)
  → After 100ms timeout:
    → Sets iframeUrl to original URL + cache-bust query param (?t=timestamp)
    → Browser fetches fresh copy of the creative
    → iframe renders the creative from scratch

Why cache-bust is needed

The original implementation set iframeUrl back to the exact same URL. Modern browsers optimize this by serving the cached version, so the creative never actually reloaded. Adding ?t={timestamp} forces a fresh fetch. The server ignores the query parameter — it serves the same static creative files regardless.

Why we can't use location.reload()

The iframe loads from a different origin (delivery CDN), so window.frames[name].location.reload() throws a SecurityError. The workaround removes and re-adds the iframe by toggling its src attribute via Vue reactivity.

Known limitations

  • Local development CORS: When running locally, Creative-Engine's fetchImage() uses fetch() to load images from the delivery CDN as blobs. This fails due to CORS (localhost origin vs. delivery CDN). Images loaded via CSS background-image or <img> tags are not affected. This only impacts local development — production serves everything from the same domain.
  • Standalone preview requires a build: The standalone preview loads a pre-built creative via tagFile. If the creative has never been built (buildId is null), the iframe gets a 404. Builder preview (local build) does not have this limitation.

Internal documentation