Skip to content

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.vue component (renders wrapper div with children)
  • [x] Group constant GROUP: 'blockGroupProperties'
  • [x] getBlockPrefix handles blockGroup* 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 updatingFromInput flag)
  • [x] Group padding defaults wrong (override keys were top/right/bottom/left instead of paddingTop/paddingRight/paddingBottom/paddingLeft)
  • [x] Delete group crash (state.blocks.creativeBlocks should be state.creativeBlocks in Vuex module action)
  • [x] Delete group dialog missing warning (added i18n key confirmDeleteGroup to 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's isolation: isolate prevents 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 groupBlock prop (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 from block.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 order values
  • DevTools JSON viewer was showing raw Vuex state (flat, fractional orders) instead of serialized form -- fixed by adding serializeOrdersForSave() to CreativeJSONViewerV2.vue
  • Reorder preview delay (~1-2s) is caused by CE rebuild time after Vuex state change, not a bug. BUILD_DEBOUNCE = 400ms in LocalBuildPreview.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

Internal documentation