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

  1. Overview
  2. Architecture
  3. What Works
  4. Common Issues & Solutions
  5. Testing Guide
  6. AI Training Considerations

๐ŸŽฏ Overview

The Universe Canvas Builder now features a complete, working nested template system using a region-based architecture. This system allows:

Current Status (November 24, 2025)

๐ŸŽ‰ FULLY FUNCTIONAL - All core functionality working:


๐Ÿ›๏ธ 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)

2. TemplateRendererComponent (template-renderer.component.ts)

3. TreeNodeComponent (tree-node.component.ts)

4. CanvasBuilderComponent (canvas-builder.component.ts)


โœ… What Works

1. Template Rendering

2. Click Selection

Canvas Clicks

Tree Clicks

3. Configuration Panel

4. Position Operations (ALL FIXED)

5. Add Operations (ALL FIXED)

6. Delete Operation


๐Ÿ› 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):

  1. โœ… getSelectedSection() - Find in regions
  2. โœ… deleteSection() - Delete from regions
  3. โœ… updateSection() - Update in regions
  4. โœ… moveSectionUp() - Move up in regions
  5. โœ… moveSectionDown() - Move down in regions
  6. โœ… moveSectionOut() - Find parent in regions, remove from regions
  7. โœ… moveSectionInto() - Move into sibling in regions
  8. โœ… addSectionInside() - Add to regions
  9. โœ… addSectionAfter() - Add after in regions
  10. โœ… 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:


๐Ÿงช Testing Guide

Test 1: Template Rendering

  1. Drag "Split Sidebar Template" onto canvas
  2. Drag "Three-Column Equal" into MAIN region
  3. Expected: See 3 tall columns (min 200px each)

Test 2: Canvas Click Selection

  1. Click on template background (between columns)
  2. Expected:
    • Purple border around entire template
    • Right panel shows "Three-Column Equal"
    • Configuration tab active

Test 3: Tree Click Selection

  1. Click "Three-Column Equal" in tree view
  2. Expected:
    • Console log: [TreeNode] ๐ŸŒฒ TREE NODE CLICKED
    • Console log: [CanvasBuilder] โœ… SECTION SELECTION UPDATED
    • Canvas shows purple border
    • Config panel opens

Test 4: Position Operations

  1. Drag two templates into same region
  2. Select second template
  3. Click "Move Up" button
  4. Expected: Templates swap positions

Test 5: Delete Operation

  1. Select nested template
  2. Click "Delete" button in toolbar
  3. Expected:
    • Template removed from canvas
    • Template removed from tree
    • Selection cleared

Test 6: Add Operations

  1. Select template in region
  2. Click "Add After"
  3. 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:

  1. Semantic Understanding - AI knows "sidebar" is a region, not just position
  2. Natural Language Aligned - "Add to sidebar" is clearer than "Add at index 0"
  3. Self-Validating - Can check if region exists before adding
  4. Industry Patterns - Matches Vue slots, React children props, Web Components
  5. Scalable - Easy to add region-specific constraints

Trade-offs:

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 โœ…

Critical Files

Next Steps

  1. โœ… DONE: Core functionality working
  2. ๐Ÿ“ TODO: Add unit tests for region operations
  3. ๐Ÿ“ TODO: Add E2E tests for nested templates
  4. ๐Ÿ“ TODO: Create AI training examples
  5. ๐Ÿ“ 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