Appearance
Block Grouping Progress
Last updated: 2026-05-05 Branch:
block-grouping(AF + CE) Issue: #1805 / Linear: CAV-16 PRs: AF #1859, CE TBD
Done
Core Architecture
- [x] Flat data model (no syncGroupChildren, no nested copies)
- [x] Fractional indexing system (AF-internal, serialized to integers for CE/backend)
- [x]
migrateNumericOrders()for legacy creatives - [x]
promoteGroupChildren()to flatten nested data on load - [x]
serializeOrdersForSave()to nest children back + convert to integers - [x] Block undo/redo (Cmd+Z / Cmd+Shift+Z) with snapshot-based approach
- [x] Undo stack cleared on tab switch (prevents conflicts with CavaiFlow)
Store Mutations
- [x]
createBlockGroup-- create group from selected blocks - [x]
moveBlockToGroup-- drag block into group - [x]
moveBlockOutOfGroup-- drag block out of group - [x]
ungroupBlocks-- dissolve group, return children to top level - [x]
moveBlockToContainer-- move top-level block into nested container (slider/form) - [x]
setBlockOrder-- fractional reorder - [x]
updateBlockOrder-- legacy reorder (uses fractional internally)
Builder UI
- [x] Multi-select with Cmd/Ctrl+click and Shift+click (range select)
- [x] Group toolbar with "Group N blocks" button and shortcut hint
- [x] Keyboard shortcuts: Cmd+G (group), Shift+G (group), Cmd+Z (undo), Cmd+Shift+Z (redo)
- [x] Collapsible groups in block list (chevron toggle)
- [x] Group/Ungroup in block actions menu
- [x] BlockGroupConfiguration panel (size, alignment, name, background, border)
DnD System
- [x] Cross-container drop prevention (slider->form, group->slider, etc.)
- [x] Container-locked sub-blocks (form inputs can't leave form, slider slides can't leave slider)
- [x] Extended drop zones on disabled containers (top/bottom half drop-through)
- [x] Container-drag-catcher suppressed for container-locked drags (no blue lines)
- [x] DnD into non-group containers (drag allowed blocks into slider/form)
- [x] Sub-block reordering adjacency check (no redundant sort areas)
- [x] Group-in-group support with depth limit (MAX_GROUP_DEPTH = 3)
- [x] Drag blocks in/out of groups
- [x] Block shortcodes on hover (positioned outside block button)
- [x] Form-input-aware shortcodes (FP1, FT1, FE1, FD1 etc.)
- [x] Form submit button shortcode (FB)
Engine (CE)
- [x]
CreativeGroupBlock.vuecomponent (renders wrapper div with children) - [x] Group constant
GROUP: 'blockGroupProperties' - [x]
getBlockPrefixhandlesblockGroup*prefix - [x] Group children filtered from top-level visual elements rendering
Bug Fixes (18 total)
- [x] Scrambled order on old creatives (migrateNumericOrders includes ALL blocks)
- [x] Can't add blocks (hardened compareOrder with String())
- [x] Debug styles leaking (reverted debug outline)
- [x] 422 save error (serialize back to integers)
- [x] Group not rendering in CE (constant mismatch)
- [x] emitAnimationTriggers TypeError (animation defaults on group)
- [x] z-index -1 on all blocks (reverted CE z-index changes)
- [x] Reorder not updating preview (replace creativeBlocks reference)
- [x] Blocks stuck after save (dual data model root cause)
- [x] Can't delete blocks (dual data model root cause)
- [x] Duplicate block names (dual data model root cause)
- [x] Group-in-group crash (guard added)
- [x] Group size properties in CE (uses styleProps.sizeProperties)
- [x] Video controls missing inside groups (CreativeVideoControls not rendered in CreativeGroupBlock -- added
<CreativeVideoControls v-if="hasVideo" />in CE) - [x] Box shadow angle/distance not applying (general bug in BoxShadowSection -- watcher was overwriting user-set angle via round-trip recalculation; fixed with
updatingFromInputflag) - [x] Group padding defaults wrong (override keys were
top/right/bottom/leftinstead ofpaddingTop/paddingRight/paddingBottom/paddingLeft) - [x] Delete group crash (
state.blocks.creativeBlocksshould bestate.creativeBlocksin Vuex module action) - [x] Delete group dialog missing warning (added i18n key
confirmDeleteGroupto warn that children will also be deleted)
Design Decisions
- Video controls (play/pause, mute) intentionally render above all other blocks inside a group (
z-index: 999). This ensures controls are always clickable even when other blocks visually overlap the video. The group'sisolation: isolateprevents the z-index from escaping the group's stacking context.
Bug Fixes Found During Testing
- [x] Box shadow angle/distance not applying to any block (general fix -- BoxShadowSection watcher was overwriting user-set angle via round-trip recalculation)
- [x] Dev Tools: copy JSON broken in popout mode (main window lost focus, clipboard API failed -- fix: use popup window's clipboard API)
Container-in-Group Support
- [x] Containers (conversation, slider, form, AR) can be placed inside groups via AddBlockTool and Cmd+G
- [x] Engine renders containers inside groups via
groupBlockprop (falls back to DataStore for top-level) - [x] Dual-presence serialization: containers kept at top-level AND nested in group for save
- [x]
blockVisible()guard prevents double-rendering of grouped containers - [x] Z-index properly applied to containers in groups via order prop
- [x] Form template switching preserves parent (stays in group)
- [x] Video controls positioned inside gp-inner when video is in a group
- [x] NestedBlockChildren recursive component for proper indentation at all nesting levels
- [x] Group nesting depth limit (MAX_GROUP_DEPTH = 3) with wouldExceedGroupDepth() guard
- [x] "Add outside" / "Add to Group" button order swapped for better UX
Todo
Must-have before merge
- [ ] Visually distinguish bottom-of-group sort areas (drop "under group" vs "inside group at bottom")
- [ ] Thorough backward-compat testing (see testing checklist below)
Nice-to-have (can ship without)
- [ ] Padding support for groups (AF writes
paddingStyle, CE needs to read fromblock.style)
Future (separate issues)
- [ ] Animation staggering for group children (depends on this + animation system CAV-38)
- [ ] Group-level animation (shared animation config for all children)
- [ ] More group properties: background color, overflow, opacity, border-radius, etc.
Testing Progress
Full test plan: architecture/block-grouping-test-plan.md
Completed (2026-04-29)
- [x] Test 1: Group creation & styling -- all 10 items passed, padding/border/shadow/size all work in preview
- [x] Test 1b: Special blocks in groups -- video controls render correctly inside groups
- [x] Test 2: Reordering -- all 13 items passed, DnD in/out/between groups, container-locked blocks, undo/redo
- [ ] Test 3: JSON validation -- in progress. Serialized JSON structure verified correct (nested children, integer orders match visual order). Remaining: save/reload cycles, data integrity checks
- [ ] Test 4: Backward compatibility -- not started
- [ ] Test 5: Live test (test-a-tag) -- not started
Key findings during testing
- JSON object key order in serialized output doesn't affect rendering -- CE sorts by
ordervalues - DevTools JSON viewer was showing raw Vuex state (flat, fractional orders) instead of serialized form -- fixed by adding
serializeOrdersForSave()toCreativeJSONViewerV2.vue - Reorder preview delay (~1-2s) is caused by CE rebuild time after Vuex state change, not a bug.
BUILD_DEBOUNCE = 400msinLocalBuildPreview.vue+ actual rebuild time - Children-as-array (instead of object properties) would be a cleaner model but is a breaking change requiring CE, backend, and composer updates -- deferred to v2