Appearance
Master/Child Creative Override System
Overview
Creatives in Cavai can exist in a master/child relationship. The master defines base settings for all formats (sizes). Child creatives inherit all values from master but can selectively override specific sections.
Data Model
Creative Data Structure
creativeGroup
├── masterCreative (e.g. responsive/largest format)
├── childCreative (300x600)
├── childCreative (320x480)
└── childCreative (728x90)Each creative stores its data as a creative_blob JSON containing:
json
{
"creativeSettings": {
"creativeBlocks": { ... },
"creativeProperties": {
"format": { "width": 300, "height": 600 },
"overriddenSettings": ["textProperties-1.style.fontSize", "textProperties-1.style.color"]
}
}
}overriddenSettings
Array of dot-notation paths stored per child creative. Only paths listed here have child-specific values. All other paths inherit from master on build.
Example paths:
textProperties-1.style.fontSizebuttonProperties-1.backgroundSettings.backgroundColorgraphicProperties-2.sizeStyle.width
Frontend Architecture
Key Files
| File | Role |
|---|---|
Configuration/mixins/sectionLogic.ts | Per-section override logic |
Blocks/utils.ts (getSectionSettings) | Maps section component names to setting paths |
store/modules/blocks.ts | Block state, isSectionOverridden getter |
store/modules/builder.ts | creativeFormats, childCreativeData, masterData |
OptionRow/OverrideControls.vue | Lock/unlock UI per section |
OptionRow/OptionRow.vue | Renders locked state, override indicators |
sectionLogic Mixin
Used by 41+ section components. Provides:
typescript
computed: {
inputLocked() // Whether this section's inputs should be disabled
isOverridden() // Whether this section has been overridden (child context)
childOverrides() // Array of format labels that override this section (master context)
overrideProps() // Bundled props to pass to OptionRow
}
methods: {
unlockSection() // Add this section's paths to overriddenSettings
lockSection() // Remove paths and revert to master values
}getSectionSettings(sectionName, blockPath)
Maps a Vue component name (e.g. 'FontSizeSection') to its setting paths relative to the block:
typescript
// Example return for FontSizeSection on textProperties-1:
['textProperties-1.style.fontSize']
// Example return for PaddingSection on buttonProperties-2:
['buttonProperties-2.padding.top', 'buttonProperties-2.padding.right', ...]This mapping lives in Blocks/utils.ts and must be updated when adding new section components.
inputLocked Behavior
| Context | Condition | Locked? |
|---|---|---|
| On master, not viewing child | -- | Never locked |
| On master, viewing child via dropdown | Section IS overridden | Locked (child-specific values) |
| On master, viewing child via dropdown | Section NOT overridden | Unlocked (editing master) |
| On actual child creative | Section NOT overridden | Locked (inherits from master) |
| On actual child creative | Section IS overridden | Unlocked (can edit override) |
Override Indicators
- OverrideControls: Lock/unlock buttons on each section when on a child creative
- OverrideIndicator: 3px purple dot showing a section is overridden
- Child override dots: Purple dots on master showing which children have overrides (new)
Viewing Child Format from Master (Dropdown Preview)
Mechanism
When the user selects a child format in the MobileOptionsBar dropdown while on master:
- Master blocks are snapshot'd (
cloneDeep) - Child's blocks are parsed from
creativeFormats(already in memory, no API call) creativeBlocksis replaced with child's blocks- Config panels show child data, preview renders child appearance
- Overridden sections are locked, non-overridden sections are editable
- Edits write to both visible
creativeBlocksandmasterSnapshot - On restore (tab click, navigation), snapshot is restored with edits intact
Why Edits Target Master
Non-overridden values in child blocks ARE the master values (child inherits them). So editing them while viewing the child is equivalent to editing master. The dual-write to snapshot ensures the edit persists.
Overridden sections are locked because their values are child-specific -- editing them from master context would be ambiguous (which creative would the edit target?).
State Fields (blocks.ts)
typescript
viewingChildId: number | null // Currently viewed child ID (null = master view)
masterSnapshot: { // Frozen master state for restore
creativeBlocks: object
savedBlocks: object
} | nullBuild-Time Behavior
On build (backend), the override system merges:
- Start with master's
creativeBlocksas base - For each child: apply only the paths listed in
overriddenSettings - All other values come from master
This means editing master values propagates to all children that haven't overridden those specific paths.
Common Pitfalls
Missing
getSectionSettingsentry: Every new section component usingsectionLogicmust register its paths inutils.ts, or unlocking/locking will silently fail.setCreativeBlocks is heavy: Merges defaults, fixes mismatches, clears templateModified. Don't use for temporary block swaps -- use direct assignment.
setCreativeProperties side effects: Overwrites master metadata (isTemplate, format type). Never call when temporarily viewing child data.
Auto-save while viewing child: The
blocksUnsavedgetter comparescreativeBlockstosavedBlocks. Since both are set to child data during swap, it correctly shows "no unsaved changes" for the child view. Master edits accumulate in the snapshot and become visible as unsaved when restoring.Container parent field in groups: When a container block (form, conversation, slider, AR) is inside a group, its
parentfield points to the group's blockName. Any operation that replaces block data (e.g. switching form templates) must preserve thisparentfield, or the container will be orphaned from its group. The engine usesparentto determine whether to render the container inside the group or at top-level.