Universe Canvas - Nested Template System
Complete guide to the working nested template architecture, common issues, and solutions
๐๏ธ Universe Canvas - Nested Template System
๐ Table of Contents
๐ฏ Overview
The Universe Canvas Builder now features a complete, working nested template system using a region-based architecture. This system allows:
- โ Infinite nesting of templates within templates
- โ Visual grid layouts using CSS Grid
- โ Full interactivity (click, select, configure, delete)
- โ Tree view integration showing complete hierarchy
- โ AI-friendly structure with semantic regions
Current Status (November 24, 2025)
๐ FULLY FUNCTIONAL - All core functionality working:
- Template rendering (CSS Grid with regions)
- Click selection (canvas and tree)
- Configuration panel
- Position operations (move up/down, move out/into)
- Add operations (inside, after, before)
- Delete operations
- Tree view with region display
- Infinite nesting
๐๏ธ Architecture
Data Structure
Template Section (Region-Based)
{
"id": "template-split-sidebar-123",
"label": "Split Sidebar Template",
"type": "region",
"layout": "template",
"template": "split-sidebar",
"templateConfig": {
"sidebarWidth": "300px",
"gap": "0px"
},
"regions": [
{
"name": "sidebar",
"displayName": "SIDEBAR",
"description": "Fixed sidebar (300px)",
"type": "region",
"width": "300px",
"children": [
// Nested sections here
],
"styles": {}
},
{
"name": "main",
"displayName": "MAIN",
"description": "Flexible main content area",
"type": "region",
"flex": true,
"children": [
{
"id": "template-three-column-equal-456",
"layout": "template",
"template": "three-column-equal",
"regions": [
{ "name": "col1", "children": [] },
{ "name": "col2", "children": [] },
{ "name": "col3", "children": [] }
]
}
],
"styles": {}
}
],
"children": [], // Empty for templates (children are in regions)
"styles": {
"width": "100%",
"minHeight": "100vh"
}
}
Key Components
1. CanvasSection (canvas-section.component.ts)
- Root component for rendering sections
- Conditionally renders
TemplateRendererComponentfor template layouts - Handles click events and passes them up
- Has click handler:
(click)="onSectionClick($event)"
2. TemplateRendererComponent (template-renderer.component.ts)
- Renders CSS Grid layout based on template type
- Creates drop zones for each region
- CRITICAL: Has its own click handler
(click)="onTemplateClick($event)"to handle clicks on template background - Emits
sectionClickevents for parent and children
3. TreeNodeComponent (tree-node.component.ts)
- Renders hierarchical tree view
- CRITICAL: Searches BOTH
section.childrenANDsection.regions[].children - Shows region labels with purple styling
- Has click handler:
(click)="onNodeClick($event)"
4. CanvasBuilderComponent (canvas-builder.component.ts)
- Master component managing entire canvas
- Contains 7 CRITICAL methods that must handle regions:
getSelectedSection()- Find section by IDdeleteSection()- Remove sectionupdateSection()- Update section propertiesmoveSectionUp()- Move within siblingsmoveSectionDown()- Move within siblingsmoveSectionOut()- Move out of parentmoveSectionInto()- Move into previous siblingaddSectionInside()- Add child sectionaddSectionAfter()- Add sibling afteraddSectionBefore()- Add sibling before
โ What Works
1. Template Rendering
- CSS Grid layouts for all 6 template types
- Visual regions with drop zones
- Nested templates render recursively
- Auto-responsive layouts
2. Click Selection
Canvas Clicks
- โ Template background โ Selects template
- โ Template label โ Selects template
- โ Region label โ Selects template
- โ Empty region โ Selects template
- โ Nested child โ Selects child (not parent)
Tree Clicks
- โ Any tree node โ Selects in canvas
- โ Region labels shown with visual distinction
- โ Child count includes regions
3. Configuration Panel
- โ Dynamic tabs based on section type
- โ Template info banner for template sections
- โ Configuration schema from module manifest
- โ Style tab for all sections
- โ Position tab for all sections
4. Position Operations (ALL FIXED)
- โ Move Up - Reorders within siblings
- โ Move Down - Reorders within siblings
- โ Move Out - Ungroups from parent
- โ Move Into - Nests into previous sibling
5. Add Operations (ALL FIXED)
- โ Add Inside - Adds as child
- โ Add After - Adds as next sibling
- โ Add Before - Adds as previous sibling
6. Delete Operation
- โ Delete button in toolbar
- โ Delete from tree selection
- โ Recursive deletion from regions
๐ Common Issues & Solutions
Issue 1: "Can't select nested template in tree"
Symptom: Clicking "Three-Column Equal" in tree โ Config panel doesn't open
Root Cause: getSelectedSection() only searched section.children, not section.regions[].children
Solution:
// โ BEFORE (Broken)
for (const child of section.children) {
const found = findSection(child);
if (found) return found;
}
// โ
AFTER (Fixed)
// Search in regular children
for (const child of section.children) {
const found = findSection(child);
if (found) return found;
}
// Also search in template regions
if (section.regions) {
for (const region of section.regions) {
for (const child of region.children) {
const found = findSection(child);
if (found) return found;
}
}
}
Impact: All 10 methods needed this fix!
Issue 2: "Can't click template background to select it"
Symptom: Clicking the grid area of a template โ Nothing happens
Root Cause: Template-renderer had no click handler. Clicks were blocked by the wrapper div.
HTML Structure:
<div class="canvas-section" (click)="..."> <!-- Has handler but blocked! -->
<lib-template-renderer>
<div class="template-renderer"> <!-- YOU CLICK HERE (no handler!) -->
<div class="template-region">
<!-- Content -->
</div>
</div>
</lib-template-renderer>
</div>
Solution:
// Add click handler to template-renderer wrapper
<div class="template-renderer" (click)="onTemplateClick($event)">
// In component:
onTemplateClick(event: MouseEvent): void {
const target = event.target as HTMLElement;
const isDirectClick = target.classList.contains('template-renderer') ||
target.classList.contains('template-region') ||
target.classList.contains('region-empty') ||
target.classList.contains('region-label');
if (isDirectClick) {
event.stopPropagation(); // Prevent double-selection
this.sectionClick.emit(this.section()); // Emit template itself
}
// If clicking child, let child handle it
}
Issue 3: "Position operations don't work on nested sections"
Symptom: Move Up/Down/Out/Into buttons don't work for sections in template regions
Root Cause: All position methods only operated on section.children, not section.regions[].children
Example - Move Up:
// โ BEFORE (Broken)
const moveRecursive = (section: CanvasSection): CanvasSection => {
const index = section.children.findIndex(c => c.id === sectionId);
if (index > 0) {
// Swap with previous sibling
const newChildren = [...section.children];
[newChildren[index - 1], newChildren[index]] =
[newChildren[index], newChildren[index - 1]];
return { ...section, children: newChildren };
}
return {
...section,
children: section.children.map(moveRecursive)
};
};
// โ
AFTER (Fixed)
const moveRecursive = (section: CanvasSection): CanvasSection => {
// ... same children logic ...
const updated = { ...section, children: newChildren };
// ALSO HANDLE REGIONS
if (updated.regions) {
updated.regions = updated.regions.map(region => {
const regionIndex = region.children?.findIndex(c => c.id === sectionId) ?? -1;
if (regionIndex > 0 && region.children) {
const newChildren = [...region.children];
[newChildren[regionIndex - 1], newChildren[regionIndex]] =
[newChildren[regionIndex], newChildren[regionIndex - 1]];
return { ...region, children: newChildren };
}
return {
...region,
children: region.children ? region.children.map(moveRecursive) : []
};
});
}
return updated;
};
Fixed Methods (All 10):
- โ
getSelectedSection()- Find in regions - โ
deleteSection()- Delete from regions - โ
updateSection()- Update in regions - โ
moveSectionUp()- Move up in regions - โ
moveSectionDown()- Move down in regions - โ
moveSectionOut()- Find parent in regions, remove from regions - โ
moveSectionInto()- Move into sibling in regions - โ
addSectionInside()- Add to regions - โ
addSectionAfter()- Add after in regions - โ
addSectionBefore()- Add before in regions
Issue 4: "Tree view doesn't show nested templates"
Symptom: Tree view shows "Split Sidebar Template (0)" even though it has children in regions
Root Cause: Tree component only rendered section.children, not section.regions[].children
Solution:
// In tree-node.component.ts template:
<!-- Regular children (non-template sections) -->
@if (section().children.length > 0) {
<div class="tree-children">
@for (child of section().children; track child.id) {
<lib-tree-node [section]="child" ...>
</lib-tree-node>
}
</div>
}
<!-- NEW: Region children (template sections) -->
@if (section().regions && section().regions.length > 0) {
<div class="tree-children">
@for (region of section().regions; track region.name) {
@if (region.children && region.children.length > 0) {
<div class="tree-region">
<div class="region-label">
<i class="fas fa-layer-group"></i>
<span>{{ region.displayName || region.name }}</span>
<small>({{ region.children.length }})</small>
</div>
@for (child of region.children; track child.id) {
<lib-tree-node [section]="child" ...>
</lib-tree-node>
}
</div>
}
}
</div>
}
Also Fixed:
// Child count now includes regions
getTotalChildrenCount(): number {
const section = this.section();
let count = section.children.length;
if (section.regions) {
for (const region of section.regions) {
if (region.children) {
count += region.children.length;
}
}
}
return count;
}
Issue 5: "Nested templates not filling space"
Symptom: Three-column template appears collapsed at bottom with tiny icons
Root Cause: canvas-section applied flex: 1 1 0% to ALL child sections, ignoring minHeight for templates
Solution:
// In canvas-section.component.ts
readonly containerStyles = computed(() => {
const section = this.section();
const styles: any = { ...section.styles };
// ... other styling ...
if (this.depth() > 0) {
if (section.layout === 'template') {
// Templates need to respect their minHeight
styles.flex = '1 1 auto'; // โ
Auto basis respects minHeight!
styles.minWidth = '0';
} else {
// Regular sections use 0% basis
styles.flex = '1 1 0%';
styles.minWidth = '0';
}
}
return styles;
});
Also:
- Increased
template-renderermin-height from 400px to 500px - Increased
template-regionmin-height from 100px to 200px - Added
height: 100%to fill parent space
๐งช Testing Guide
Test 1: Template Rendering
- Drag "Split Sidebar Template" onto canvas
- Drag "Three-Column Equal" into MAIN region
- Expected: See 3 tall columns (min 200px each)
Test 2: Canvas Click Selection
- Click on template background (between columns)
- Expected:
- Purple border around entire template
- Right panel shows "Three-Column Equal"
- Configuration tab active
Test 3: Tree Click Selection
- Click "Three-Column Equal" in tree view
- Expected:
- Console log:
[TreeNode] ๐ฒ TREE NODE CLICKED - Console log:
[CanvasBuilder] โ SECTION SELECTION UPDATED - Canvas shows purple border
- Config panel opens
- Console log:
Test 4: Position Operations
- Drag two templates into same region
- Select second template
- Click "Move Up" button
- Expected: Templates swap positions
Test 5: Delete Operation
- Select nested template
- Click "Delete" button in toolbar
- Expected:
- Template removed from canvas
- Template removed from tree
- Selection cleared
Test 6: Add Operations
- Select template in region
- Click "Add After"
- Expected: New section appears after template in same region
๐ค AI Training Considerations
Why Regions Are Better for AI
Decision Made: Keep region-based architecture (not flat structure)
Advantages:
- Semantic Understanding - AI knows "sidebar" is a region, not just position
- Natural Language Aligned - "Add to sidebar" is clearer than "Add at index 0"
- Self-Validating - Can check if region exists before adding
- Industry Patterns - Matches Vue slots, React children props, Web Components
- Scalable - Easy to add region-specific constraints
Trade-offs:
- โ ๏ธ Slightly more complex (2 patterns:
children[]vsregions[].children) - โ But: AI is already trained on hierarchical structures (HTML, React, Vue)
- โ Solution: Clear documentation and helper methods
Training Data Structure
// For AI training - Simple rule:
if (section.layout === 'template') {
// Use regions
const region = section.regions.find(r => r.name === 'sidebar');
region.children.push(newSection);
} else {
// Use children
section.children.push(newSection);
}
Helper Method for AI
function getAllChildren(section: CanvasSection): CanvasSection[] {
const children = [...section.children];
if (section.regions) {
section.regions.forEach(region => {
children.push(...region.children);
});
}
return children;
}
๐ Summary
Working Features โ
- Template rendering (CSS Grid)
- Click selection (canvas & tree)
- Configuration panel
- Position operations (move up/down/out/into)
- Add operations (inside/after/before)
- Delete operation
- Tree view with regions
- Infinite nesting
- Visual feedback (purple borders)
Critical Files
libs/libs-structural/modular-universe/src/lib/universe-canvas-v3/components/canvas-builder/canvas-builder.component.ts- 10 methods fixedlibs/libs-structural/modular-universe/src/lib/universe-canvas-v3/components/template-renderer/template-renderer.component.ts- Click handler addedlibs/libs-structural/modular-universe/src/lib/universe-canvas-v3/components/tree-node/tree-node.component.ts- Region rendering addedlibs/libs-structural/modular-universe/src/lib/universe-canvas-v3/components/canvas-section/canvas-section.component.ts- Flex basis fixed
Next Steps
- โ DONE: Core functionality working
- ๐ TODO: Add unit tests for region operations
- ๐ TODO: Add E2E tests for nested templates
- ๐ TODO: Create AI training examples
- ๐ TODO: Performance optimization for deep nesting
Last Updated: November 24, 2025 - 11:30 PM EST
Session ID: 11.25.2.4
Status: โ
Complete & Production Ready