Skip to content

Cavai Architecture Blueprint

Cross-checked against actual codebase March 2026. Docs marked as plans/unimplemented are noted.

MAINTENANCE: This document must be kept in sync with the codebase. After significant PRs or architectural changes, update the relevant sections. Check recent git log main in both repos to verify accuracy before relying on this document in a new session.

Repository Structure

RepoRoleTech
Application-FrontendEverything visual in the platform — builder, reports, navigation, management viewsVue 2 (Options API), Vuex, SCSS
Creative-EngineVue library that renders interactive creatives in an isolated iframe environmentVue 2, TypeScript, mixins
Application-BackendCloud API — authentication, campaign management, data, and integrationsAdonisJS, PostgreSQL
Creative-ComposerBuild pipeline that compresses and optimizes creative data for deploymentNode.js

All repos live under /Users/nicolaykjaernet/CavaiProduct/.

Recent Architectural Changes (as of v8.20.3 / CE 6.7.1)

Unified State System (PR #1779 / CE #693)

The state system was unified across buttons, form submit buttons, and form inputs. All use a shared StateConfig object:

typescript
type StateConfig = {
  transition?: TransitionTiming   // { duration, easing?, delay? }
  hover?: StateStyle
  focus?: StateStyle
  active?: StateStyle
  disabled?: StateStyle
  loading?: StateStyle
  success?: StateStyle
  error?: StateStyle
}

type StateStyle = {
  background?: boolean
  backgroundSettings?: BackgroundSettings
  border?: boolean
  borderStyle?: BorderStyleSettings
  boxShadow?: boolean
  boxShadowSettings?: ShadowSettings
  filter?: boolean
  filterSettings?: FilterSettings
  backdropFilter?: boolean
  backdropFilterSettings?: FilterSettings
  color?: string
  transform?: TransformStyle      // { scale?, rotate?, translateX?, translateY? }
  opacity?: number
}

State enums restrict which states are available per component:

  • ButtonState: default, hover, active
  • SubmitButtonState: default, hover, active, disabled, loading, success, error
  • InputFieldState: default, hover, focus

Key components: StateConfigurationCard.vue, StateSection.vue

Replaces the old separate hoverStyle/focusStyle/disabledStyle top-level properties.

Button Templates (PR #1779)

7 button templates added: Elevated, Minimal, Outline, Glass, Bold, Push, Cavai Classic. Each includes pre-designed state styles and transitions.

Template Modification Tracking (PR #1779)

All template-enabled blocks now track whether the user has modified a template. configurationLogic uses _templateReady flag to skip tracking during initial mount.

Bulk Export Delivery Settings (PR #1802)

Expanded bulk export modal with all delivery settings (VAST, VPAID, clickthrough, z-index, etc.) in collapsible sections. Includes fullscreen mode and conditional field display based on creative type.

New Reusable Section Components (PR #1779)

Added: DurationSection, DelaySection, TransitionEasingSection, ScaleSection, TranslateSection, FilterSection, IterationsSection


State Management: Vuex (NOT Pinia)

Note: Pinia is initialized in main.ts for future use, but no Pinia stores currently exist. All active state management uses Vuex. The planned migration (documented in pinia-store-architecture-migration.md) has not been executed.

Key Vuex Modules

ModulePathPurpose
blocksstore/modules/blocks.tsBlock CRUD, ordering, reordering, creative block state
builderstore/modules/builder.tsBuilder UI state (preview expanded, selected block, etc.)
creativesstore/modules/creatives.tsCreative listing and management
creativeWizardstore/modules/creativeWizard.tsWizard flow state
previewstore/modules/preview.tsPreview iframe state
authstore/modules/auth.tsAuthentication

How Config Components Talk to Store

User interaction
  → Section component emits event
    → Config component receives event
      → configurationLogic.updateValue(value, path)
        → Vuex mutation: updateBlockValue({ path, value })
          → state.creativeBlocks updated

Two Core Mixins

1. configurationLogic (Config Components)

Used by 25 configuration components (*Configuration.vue).

Provides:

  • blockData computed — fetches block from Vuex via getBlockByName
  • updateValue(value, path) — writes to Vuex store
  • title, titleAddition — display name logic
  • path prop — dot-notation path to block (e.g., formProperties.formInputProperties-3)

2. sectionLogic (Section Components)

Used by 41+ section components (reusable UI sections like BorderStyleSection, PaddingSection, etc.).

Provides:

  • inputLocked — master/child creative override logic
  • overrideProps / overrideHandlers — section-level override system
  • narrow — responsive layout for expanded preview
  • cssToNumber(), numberToPx() — utility methods

How They Compose

TextConfiguration.vue (uses configurationLogic)
  └─ BorderStyleSection.vue (uses sectionLogic)
  └─ BackgroundStyleSection.vue (uses sectionLogic)
  └─ AlignmentSection.vue (uses sectionLogic)
  └─ PaddingSection.vue (uses sectionLogic)
  └─ ...

Config component = orchestrator. Section components = reusable building blocks.

Block System

Block Types

Defined in src/constants/blocks.ts:

typescript
export const BLOCKS = {
  BASE: 'baseProperties',
  BUTTON: 'buttonProperties',
  TEXT: 'textProperties',
  GRAPHIC: 'graphicProperties',
  HTML: 'htmlProperties',
  SLIDER: 'sliderProperties',
  SLIDER_V2: 'sliderV2Properties',
  FORM: 'formProperties',
  FORM_INPUT: 'formInputProperties',
  FORM_SUBMIT_BUTTON: 'formSubmitButtonProperties',
  VIDEO: 'videoProperties',
  CONVERSATION: 'conversationProperties',
  AR: 'arProperties',
  // ... more
}

export const VISUAL_ELEMENTS = [BLOCKS.TEXT, BLOCKS.GRAPHIC, BLOCKS.HTML, BLOCKS.BUTTON]

Two Categories

Visual ElementsSpecialized Blocks
ExamplesText, Graphic, HTML, ButtonForm, Slider, SliderV2, Video, Conversation
In VISUAL_ELEMENTS?YesNo
Multiple instances?Yes (textProperties-1, -2)One per creative (static key)
Rendered viaCreativeVisualElements.vueDedicated components in CreativeBody.vue
Sub-blocks?NoYes (form inputs, slider slides)

Key Data Files

FilePurpose
Blocks/data/types.tsTypeScript interfaces for all block properties
Blocks/data/defaults.tsDefault values, factory functions, blockDefaults map
Blocks/utils.tsgetSubBlocks(), getBlockPath(), uniqueBlockId()

Block Data Structure (in Vuex)

json
{
  "creativeBlocks": {
    "baseProperties": { "blockName": "baseProperties", "order": 0, ... },
    "textProperties-1": { "blockName": "textProperties-1", "order": 1, ... },
    "formProperties": {
      "blockName": "formProperties", "order": 2,
      "formInputProperties-1": { "blockName": "formInputProperties-1", "parent": "formProperties", "order": 0, ... },
      "formSubmitButtonProperties": { ... }
    }
  }
}

Sub-blocks are stored as object properties on the parent, NOT in arrays.

Style System ("Lego Blocks")

Each styling aspect is a modular config object combined through Creative-Engine mixins.

Style Config Objects (CE types)

  • BoxShadowConfigboxShadow: boolean, boxShadowSettings: {...}
  • BorderConfigborder: boolean, borderStyle: {...}
  • BackgroundConfigbackground: boolean, backgroundSettings: {...}
  • SizeConfigsize: boolean, sizeStyle: {...}
  • FlexAlignConfig — alignment properties
  • CustomStylesConfig — custom CSS

CE Mixins (rendering)

  • StyleAndClassNameGenerationMixin — core style object + class generation
  • BlockMixin — block-specific styling
  • VisualElementMixin — position, visibility for visual elements

Visual elements use all three mixins. Specialized blocks access DataStore directly and compute their own styles.

SCSS Variables (Frontend)

Location: src/styles/

FileContents
_variables.scssSizes, border radius, transitions, font sizes, line heights
_colors.scssColor palette
_newcolors.scssUpdated colors
_shadows.scssBox shadows
_animations.scssAnimation utilities
_fonts.scssFont declarations

Key transition variables:

scss
$transition-quick: 0.16s ease-in-out;
$transition-medium: 0.25s ease-in-out;
$transition-long: 0.5s ease-in-out;
$transition-pop-quick: 0.16s cubic-bezier(0.34, 1.56, 0.64, 1);

Frontend ↔ Creative-Engine Integration

Architecture

Application-Frontend (main window, Vuex)
  ↕ postMessage / iframe src
Creative-Engine (iframe, isolated DataStore)

They run in separate contexts. Changes don't reflect in real-time — preview requires iframe reload.

Data Flow

Frontend Config UI → Vuex Store → JSON Payload → Backend → Creative-Engine Rendering

Adding/Updating Blocks (Checklist)

Application-Frontend:

  1. constants/blocks.ts — register block type
  2. Blocks/data/types.ts — TypeScript interface
  3. Blocks/data/defaults.ts — default values + register in blockDefaults
  4. Configuration/configs/<Block>Configuration.vue — config panel
  5. AddBlockTool.vue — add button
  6. assets/i18n/en.js — translations

Creative-Engine:

  1. src/utils/constants.ts — register in BLOCKS
  2. src/interfaces/jsonTypes/payload-v2/index.ts — matching interface
  3. Rendering component in src/components/creative/
  4. For visual elements: register in CreativeVisualElements.vue
  5. For specialized: import in CreativeBody.vue + showXxxBlock + blockOrders
  6. npm run build:library after changes

CRITICAL: Types in both repos MUST match. No data transformation — Pinia/Vuex is just a state management layer.

Template System

Templates are type-safe, extensible presets for block configurations.

Pattern:

  1. Define enum in types.ts (e.g., FormTemplate)
  2. Create generator functions returning JSON config
  3. Register in templateRegistry (central map)
  4. Use TemplateButton component in config UI

Currently used for: Forms, HTML blocks, Code editor, SliderV2.

State Preview System

Allows previewing component states (hover, focus, disabled, loading) in builder without affecting runtime.

Key mechanism: isStatePreview boolean flag + previewState enum value on blocks. When isStatePreview is true, Creative-Engine shows preview styling from stateConfig[previewState] but doesn't apply actual disabled/hover behavior.

Unified state system (current):

  • All stateful blocks use stateConfig: StateConfig object (see Recent Changes section)
  • State enums: ButtonState, SubmitButtonState, InputFieldState
  • Helper functions in stateHelpers.ts: getButtonStates(), getSubmitButtonStates(), getInputFieldStates()
  • StateConfigurationCard component renders state tabs with per-state styling sections
  • Reset on beforeDestroy via configurationLogic

Pattern for adding states to a new block:

  1. Define state enum in types.ts
  2. Add stateConfig?: StateConfig + isStatePreview?: boolean + previewState?: YourEnum to block type
  3. Create helpers in stateHelpers.ts
  4. Use StateConfigurationCard in config component
  5. Handle state rendering in Creative-Engine component

Reusable Section Components

All live in Configuration/components/. Each uses sectionLogic mixin.

Styling sections (reuse across blocks): BackgroundStyleSection, BorderStyleSection, BoxShadowSection, PaddingSection, MarginSection, AlignmentSection, FlexAlignmentSection, SizeSection, AdvancedSizeSection, MaxSizeSection, RotationSection, FilterSection

Typography (font settings): FontSizeSection, FontWeightSection, FontColorSection, TextAlignSection, TypefaceSection — all in FontSettings/ subfolder

Animation/transition sections: DurationSection, DelaySection, SpeedSection, TransitionEasingSection, TranslateSection, ScaleSection, IterationsSection, AnimationPresetSection, AnimationTriggerSection, AnimationDirectionSection

Animation system (animation?: AnimationConfig on BlockBase/BasicConfig): All block types can have a per-block animation config with 12 presets, 4 triggers (load/hover/click/scroll), looping, direction, easing. In CE, AnimationMixin provides reusable computeds and trigger handling. Keyframes generated via styles/animations/presets.ts and injected through StyleTreeParser. Designed for future Block Groups (#1805) — per-block config = "what", group childAnimation = "how" (stagger/cycle with reorderable sequence).

Block-specific: SliderArrowsSection, SliderFeedSection, SliderEdgeFadeSection, VideoSection, ConversationAnimationSection, ContentSection, HtmlSection

UI utilities: OptionList, TabNavigation, TemplateButton, StateSection, StateConfigurationCard, OverrideIndicator, DeleteButtonWithExpandingConfirmation, CustomCSSEditor, MultiPostfixInput

Component Composition Pattern (Options API)

The codebase primarily uses Vue 2 Options API. Only a few newer form sub-components use <script setup>.

Standard config component structure:

vue
<script>
import { configurationLogic } from '../mixins/configurationLogic'
import BorderStyleSection from '../components/BorderStyleSection.vue'
// ... more sections

export default {
  name: 'TextConfiguration',
  mixins: [configurationLogic],
  components: { BorderStyleSection, ... },
  // component-specific computed/methods
}
</script>

Standard section component structure:

vue
<script>
import { sectionLogic } from '../mixins/sectionLogic'

export default {
  name: 'BorderStyleSection',
  mixins: [sectionLogic],
  // section-specific logic, emits events to parent config
}
</script>

Master/Child Creative Override System

Creatives can be "child" creatives that inherit from a "master". Sections can be individually overridden.

  • sectionLogic provides inputLocked, isOverridden, overrideProps
  • getSectionSettings() in utils.ts maps section names to block paths
  • Overrides are tracked in Vuex and synced to backend

Backend Communication

  • Routes: Application-Backend/start/routes/
  • Controllers: Application-Backend/app/Controllers/Http/
  • Resources: shape API responses, include links for available actions
  • Frontend services: src/services/ call API endpoints
  • Build artifacts: Application-Backend/tmp/creatives/assets/creatives/[id]/

Build Pipeline

AF config → JSON payload → Backend stores → Composer builds →
  ├── creative-engine.js (CE bundle)
  ├── stub.js (compiled creative)
  ├── creative.json (full data)
  ├── tag.html (embed tag)
  └── log.txt (build logs)

Common Gotchas

  1. No Pinia — migration docs are aspirational, not reality
  2. Sub-blocks are object properties, not arrays (legacy inputs array was removed)
  3. blockName never changes after creation — can diverge from order and typeIndex
  4. CE must be rebuilt after changes: npm run build:library
  5. No real-time preview updates — iframe isolation requires reload
  6. Scoped <style> in CE components breaks preview — use mixin-injected CSS only
  7. Types must match exactly between AF types.ts and CE payload-v2/index.ts
  8. Don't add specialized blocks to VISUAL_ELEMENTS array
  9. SafeFrame viewport units100vw/100vh inside a SafeFrame iframe resolve to the SF's internal dimensions, not the device screen. See SafeFrame Responsive Scaling research and CE #727 for test infrastructure. Not currently a live issue but may resurface.

Internal documentation