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