Appearance
Format Dropdown: Child Override Indicators
Status
Shelved. Attempted on branch block-grouping but reverted. Too many conflicts with existing reactive architecture.
Two features were explored:
- Purple override dots on master sections (simple, worked)
- Block swap to preview child appearance from master (complex, broke things)
What Worked (Simple Approach)
Purple override dots on OptionRow
Shows which child formats have overridden each section when viewing master.
Files:
sectionLogic.ts: AddedchildOverridescomputed (checksoverriddenSettingsintersection per child)OptionRow.vue: AddedchildOverridesprop + purple dot renderingMobileOptionsBar.vue: Added "(master)" label on largest format in dropdown
Implementation was clean and non-invasive. Can be re-applied independently.
How it works
typescript
// sectionLogic.ts
childOverrides() {
if (!this.isMasterCreative || !this.selectedBlockPath) return []
const sectionPaths = getSectionSettings(this.$options.name, this.selectedBlockPath)
return this.childCreativeData
.filter(({ creativeSettings }) => {
const overrides = creativeSettings?.creativeProperties?.overriddenSettings || []
return !isEmpty(intersection(sectionPaths, overrides))
})
.map(({ creativeSettings }) => getFormatLabel(creativeSettings.creativeProperties.format))
}What Failed (Block Swap Approach)
Goal
Preview child creative's actual appearance in the iframe while staying on master, with non-overridden sections editable (writing to master).
Approach
- Snapshot master blocks before swap
- Replace
creativeBlockswith child's parsed blob - Lock only overridden sections (child-specific values)
- Write edits to both visible blocks and master snapshot
- Restore snapshot when switching back
Issues Encountered
1. Reactive watchers overwrite master snapshot
When blocks are swapped, section component watchers fire and re-emit child values through updateBlockValue. These writes hit the master snapshot, overwriting master edits.
Attempted fix: snapshotWriteLocked flag + setTimeout(0) to unlock after watcher flush. Result: Still didn't fully prevent overwrites. Vue 2's watcher timing is unreliable for this pattern.
2. Block title shows "undefined"
visuals.blocks.undefined appeared as title when viewing child blocks. The child's creative_blob has different block structure/naming than what configurationLogic expects from the selectedBlockPath.
Root cause: selectedBlockPath references master block names. Child blocks may have different internal naming or missing blocks entirely.
3. Override values not reflected in child preview
Child showed master's 64px font-size even though it had overridden to 32px. The child's creative_blob stores the FULL block data (both inherited and overridden), but the blob might be stale or the override merging happens at build time, not in the stored blob.
4. Previous session issues (fixed but informative)
setCreativeBlocksis too heavy for swaps (merges defaults, fixes mismatches)setCreativePropertieswith child data overwrites master metadata (isTemplate, format)- FormatSelector
navigate()needs MouseEvent parameter - Master tab loses white background styling during swap
Why Block Swap Fundamentally Conflicts
The architecture assumes creativeBlocks is a single source of truth for BOTH:
- Preview rendering (iframe gets data from this state)
- Config panel display (components read from this state via
getBlockByName) - Save operations (compares against
savedBlocks) - Template tracking (watches for changes)
Swapping blocks temporarily breaks all these assumptions:
- Config panels try to display child data using master's block paths
- Watchers detect "changes" that are actually just the swap
- Template tracking fires false positives
- Auto-save logic gets confused about what's dirty
Proper Solution (Future)
To make this work correctly would require one of:
A. Separate preview data channel
- Keep master in
creativeBlocks(config panels read this) - Send child blocks to preview iframe via separate postMessage
- Preview renders child, config shows/edits master
- Requires preview communication refactor
B. Read-only overlay state
- Add
previewingBlocksto state (separate fromcreativeBlocks) configurationLogic.blockDatareads frompreviewingBlocksfor display- Edits still write to
creativeBlocks(master) - Preview reads from
previewingBlocks - Complex but doesn't break existing save/tracking logic
C. Build-and-preview approach
- When selecting child in dropdown, trigger a quick build of master+child
- Preview the BUILT output (which correctly merges master values + overrides)
- Config panels untouched (stay on master)
- Slowest but most correct
Key Files Reference
| File | Role |
|---|---|
store/modules/blocks.ts | Block state, mutations, getters |
store/modules/builder.ts | creativeFormats, childCreativeData, getCreativeData |
Configuration/mixins/sectionLogic.ts | Override logic per section |
Configuration/mixins/configurationLogic.ts | Block data access for config panels |
BuilderVisuals/MobileOptionsBar.vue | Format dropdown |
BuilderVisuals/FormatSelector.vue | Format tabs (full navigation) |
OptionRow/OptionRow.vue | Config rows with override indicators |
Blocks/utils.ts | getSectionSettings() mapping |
mixins/responsivePreviewControls.ts | Preview size management |
Architecture Context
How overrides work at build time
- Master
creativeBlocks= base for all formats - Each child has
overriddenSettingsarray (dot-notation paths) - On build: start with master blocks, overlay child's values only for overridden paths
- Non-overridden paths always come from master
What the dropdown currently does (without our changes)
- Changes preview iframe dimensions (width/height)
- Stores
masterDevicePresetfor persistence - Does NOT swap block data or change config panels
- Master stays fully editable
What format TABS do (full navigation)
- Auto-save current creative
- Route to child creative URL
- Triggers full
fetchChatBotData()API call - Loads child's blocks via
setCreativeBlocks(with default merging) - Config panels show child data, override system enables unlock/lock
Lessons Learned
- Don't swap
creativeBlockstemporarily -- too many side effects from watchers, template tracking, and save logic - Vue 2 watcher timing is unreliable --
setTimeout(0)doesn't reliably run after all watchers - Child
creative_blobmay be stale -- it reflects the state at last build, not live master values selectedBlockPathis master-specific -- child blocks may not have matching paths- The simple approach (dots only) is safe and useful -- can be shipped independently
- Block swap needs architectural refactoring -- would need separate preview data channel or overlay state