Appearance
Block Nesting Rules
Rules for what can be nested inside what in the block system.
Block categories
- Groups (
blockGroupProperties): Visual container with background, border, padding, shadow, animations - Containers (
formProperties,sliderProperties,conversationProperties): Functional containers with their own sub-block systems (form inputs, slides, conversation messages) - Regular blocks (
textProperties,graphicProperties,buttonProperties,htmlProperties,videoProperties): Visual elements - Structural blocks (
baseProperties,closeProperties,expandableInitialProperties,expandableExpandedProperties,taglineProperties,iconProperties,removeProperties,labelProperties): Locked/structural, not user-movable
Nesting rules
| Parent \ Child | Regular block | Group | Container | Structural |
|---|---|---|---|---|
| Top-level | yes | yes | yes | yes |
| Group | yes | yes | yes | no |
| Container | no | no | no | no |
In plain language
- Groups in groups: Allowed. Enables wrapper patterns like a shared background group containing sub-groups.
- Containers in groups: Allowed. A form or slider can live inside a group for shared styling. Containers use a dual-presence serialization approach: they are kept at top-level (so DataStore and other readers can find them) and also nested inside the group (for rendering). The engine uses the
groupBlockprop to source block data from the group parent instead of DataStore when rendering containers inside groups. - Regular blocks in groups: Allowed. The primary use case.
- Groups in containers: Not allowed. Containers have their own sub-block systems (form inputs, slides) and adding groups inside them would create conflicting parent hierarchies.
- Containers in containers: Not allowed. Same reason as above.
- Anything in containers via DnD: Not allowed. Container children (form inputs, slides, conversation messages) are managed by their own systems, not the general block list.
- Structural blocks in groups: Not allowed. These are locked to their position (base block, close button, expandable sections, etc.).
Nesting depth
Maximum group nesting depth is 3 levels (configurable via MAX_GROUP_DEPTH in utils.ts). The Creative Engine renders recursively via CreativeGroupBlock, which uses dynamic <component :is> to render children including other groups.
Depth is enforced at multiple points:
wouldExceedGroupDepth(): Called during DnD drops and Cmd+G grouping operations. Returns true if the operation would exceed MAX_GROUP_DEPTH.getGroupDepth(block): Counts group ancestors upward from a given block.getGroupSubtreeDepth(block): Counts nested group descendants downward from a given block.
Rendering containers inside groups
Containers (form, slider, conversation, AR) require special handling when placed inside groups because they rely on DataStore for their block data. The engine uses a two-layer approach:
groupBlockprop: When a container is rendered inside aCreativeGroupBlock, it receives the block data directly via thegroupBlockprop. The container component checks for this prop first and falls back to DataStore only when rendering at top-level.blockVisible()guard: Returns false for containers whoseparentfield points to a group. This prevents the top-level rendering loop from also rendering the container, which would cause double-rendering.Dual-presence serialization: When saving, containers inside groups are kept at both top-level (so DataStore and backend readers can find them) and nested inside the group's children. On load,
promoteGroupChildren()flattens the nested copies back to the flat model the frontend uses internally.Z-index: Containers in groups receive proper z-index values via the
orderprop, just like regular blocks in groups.Template switching: Operations that replace block data (e.g. switching form templates) must preserve the
parentfield so the container stays associated with its group.
Implementation notes
Guards are enforced at three layers:
- UI/DnD (
BlocksList.vue):canDropHere()andcanDropInGroup()check block types before allowing drops - Selection (
BlocksList.vue): Multi-select and keyboard shortcuts (Cmd+G) check container context - Vuex (
blocks.ts):moveBlockToGroupmutation validates block types as a safety net - Groupability (
utils.ts):isGroupable()andNON_GROUPABLE_BLOCKSdefine which blocks can participate in grouping