Appearance
Creative Engine Analytics System
Source:
/Creative-Engine/src/analytics/
Architecture
Three files under src/analytics/:
analytics.ts-- CoreAnalyticssingleton with all event sending, metric tracking, lead capturevisibility.ts--Visibilitysingleton with IntersectionObserver-based viewability trackingTCF.ts-- GDPR/TCF consent framework that can disable visibility tracking
Dual Pipeline
Every sendAnalytics() call fires to two endpoints:
- Legacy Cloudflare Worker (URL from tag stub) using old field names (
id,chatbotId,interactionId,chatbotActions) - Bunny CDN (
analytics-3.cavai.com/analytics/v1.gif) viasendBunnyAnalytics()using modern names (sessionId,creativeId,creativeGroupId,creativeActions) plus theclickscounter. Skipped for dev/test environments.
Field Name Mapping (Legacy to Bunny)
| Legacy (Cloudflare) | Bunny | Source |
|---|---|---|
id | sessionId | UUID v4 per session |
chatbotId | creativeId | Creative database ID |
interactionId | creativeGroupId | Creative group ID |
chatbotActions | creativeActions | numUserActions counter |
| N/A | clicks | clicks counter (Bunny-only) |
Message Types
| Type | Method | Purpose |
|---|---|---|
init | postInit() | Sent once at creative load |
event | sendEvent() | Primary event type for all interactions, visibility, lifecycle |
flow | sendFlow() | Flow component transitions (with sequence number, object ID/type) |
count | sendCount() | Countable element interactions: headerClick (-1), backgroundClick (-2) |
metric | sendMetric() | Custom metrics from creative SDK |
Counters
numUserActions (sent as chatbotActions / creativeActions)
Semantics: Count of meaningful user interactions within the creative. NOT a raw click counter.
Incremented at 6 locations:
| File | Trigger |
|---|---|
MessageHolder.vue:614 | Choice/consent button click or text input submit in conversation flow |
FormClickoutLinkInput.vue:33 | Form clickout link click |
FormSubmitButton.vue:137 | Form submit button click |
CloseButton.vue:161 | Close/collapse button click |
CreativeSliderBlock.vue:956 | Slider slide click-through (old Slider) |
ToggleExpand.ts:20 | Expanding a bubble creative (only expand, not collapse) |
The @user-action event reaching MessageHolder is emitted by:
Choice.vue-- choice selection and link clicksTextInput.vue-- text submissionSliderSlide.vue-- slide link click (SliderV2)
Sent as:
- Every
sendEvent()includes it aschatbotActions: this.numUserActions sendBunnyAnalytics()maps it tocreativeActions
clicks
Semantics: Raw click count on the entire creative container -- every click anywhere, regardless of what was clicked.
Incremented at 1 location:
Creative.vue--creativeContainerClicked()handler on#creative-containerbackground clicks
Known issue: @click.self almost never fires because CreativeBody covers the entire container, so event.target is never the container itself. clicks is effectively always 0. See click-self-issues.md for details and proposed fixes.
Sent: Only in Bunny pipeline (not legacy). NOT sent to Cloudflare.
Key Difference
| Metric | What it counts | Example |
|---|---|---|
numUserActions | Intentional interactions (choices, submits, expand, link clicks) | User selects a flow answer |
clicks | All clicks anywhere on creative container | User clicks the background |
numUserActions should NEVER be incremented by general container clicks. They are fundamentally different metrics.
DataStore State: creativeClickedOnce
Location: dataStore.ts:62 -- ref(false)
Set at: Creative.vue via handleFirstInteraction() -- a @click.capture handler on #creative-container that fires on the first trusted user click anywhere in the creative. Uses capture phase so it fires before any child handlers.
History (PR #735): Previously used
@click.selfoncreativeContainerClicked(), which never fired becauseCreativeBodycovers the entire container and.selfrequires the click target to be the container element itself. The metric was broken across all creative formats.
First click triggers:
- Start goal timer (
Analytics.startGoalTimer()) - Send
secondsToActiveevent (time from page load to first click) - PostMessage
creativeClickedOnce: trueto parent window (for VPAID callbacks)
Once true, never reverts within a session.
Event Data Fields
| Field | Sent from | Meaning |
|---|---|---|
secondsToActive | Creative.vue (first click) | Seconds from load to first interaction |
secondsTotalActive | ToggleExpand.ts (collapse) | Total seconds creative was expanded |
clickedLink | 6 components | User clicked a link |
linkUrl | Same | The clicked URL |
timeGoalMet | analytics.ts goal timer | Goal time elapsed |
reachedGoal | MessageHolder, Middleware | Reached a goal flow component |
reachedEnd | conversationFlow.ts | Reached end of flow |
reachedInput | MessageHolder | Text input component presented |
formSubmitClicked | FormSubmitButton | Form submit clicked |
viewable | Visibility | Creative viewable (>= 1s continuous) |
secondsInView | Visibility | Cumulative seconds in view |
Visibility System
- Uses
IntersectionObserverwiththreshold: 0.5(>= 50% pixels visible) viewablebecomestrueafter 1 continuous second of visibilitysecondsInViewaccumulates total view time (pauses on scroll-out or tab background)- Events fire at time buckets: 5s, 10s, 15s, 30s, 45s, 60s, 120s, 180s, 240s, 300s
- Uses
performance.now()to avoid clock drift
Visual Element Click Tracking
Visual elements (Button, Text, Graphic, HTML) use VisualElementMixin for click handling. The mixin's handleClick() only fires analytics when a clickthrough URL is configured. Without a clickthrough URL, it returns immediately -- no metrics, no identification, nothing.
This means interactive buttons (used for show/hide, navigation, toggles) are invisible to the analytics system. See visual-element-tracking-gap.md for full analysis.
PR #743 decision: numUserActions should NOT be incremented by visual element clicks. It was originally meant for flow interactions only. Visual element clicks should be tracked separately (proposed: via sendMetric).
Countable Metrics (sendCount)
Separate from both numUserActions and clicks:
| objectId | Metric | Source |
|---|---|---|
-1 | headerClick | VisualElementMixin.ts, Tagline.vue |
-2 | backgroundClick | CreativeBody.vue |
These do NOT increment numUserActions. Note that headerClick is a misleading name -- it tracks visual element clickthrough clicks, not header clicks. Renaming to element_clicks has been discussed (PR #743) but not yet done.
Known issue: backgroundClick relies on @click.self on CreativeBody, which doesn't fire when visual elements cover the background. Clickable background is effectively broken for creatives with graphic/text/HTML blocks. See click-self-issues.md.
Flow System
Flow tracking is completely separate from click/action counters:
- User makes a choice ->
@progress-flow->eventEmitter.emitProgression(nextStepId) ConversationFlow.addComponentsToFlow()resolves next step, adds components toDataStore.liveFlow- Component renders -> emits
@block-rendered->MessageHolder.postAnalyticsData() Analytics.sendFlow(block)sendsmessageType: 'flow'withobjectId,objectType,sequenceNumber,position
flowSequenceNumber auto-increments per sendFlow() call.
Swipe Analytics (Temporary)
Separate system for slider block swipes. Gated by DataStore.enableSwipeAnalytics. Pings delivery-3.cavai.com/assets/general/stub.js.gif via blind <img> request. 300ms debounce. Not connected to main analytics pipeline.