WIDGET BUILDER CONSOLIDATION PLAN

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Widget Builder Consolidation Plan

Date: December 9, 2025
Session: 9
Priority: 3
Status: 🔄 In Progress


🎯 Goal

Consolidate two widget builder components into one unified, feature-complete interface.


📊 Current State Analysis

Component 1: WidgetBuilderWorkspaceComponent ⭐ PRIMARY

Location: libs/libs-widgets/widget-builder/src/lib/components/widget-builder-workspace/
Route: /widgets/builder/workspace

Strengths:

Weaknesses:

Decision: Use this as the base - better UX and architecture


Component 2: WidgetBuilderComponent (Legacy)

Location: libs/libs-widgets/widget-builder/src/lib/components/widget-builder/
Route: /widgets/builder/ (currently)

Strengths:

Weaknesses:

Decision: Extract useful features then deprecate


🔄 Consolidation Strategy

Phase 1: Enhance WidgetBuilderWorkspaceComponent

Add missing features from old component:

  1. Add "From Component" Tab to Step 1 (Describe)
  2. Add "Code Editor" Tab to Step 2 (Customize)
  3. Integrate embeddable registry for component selection

Phase 2: Update Routes

Make workspace the primary entry point:

  1. Change /widgets/builder/ → redirect to /widgets/builder/workspace
  2. Keep /widgets/builder/workspace as main route
  3. Add /widgets/builder/templates for template gallery (separate page)

Phase 3: Deprecate Old Component

  1. Add deprecation notice to old component
  2. Remove from barrel exports (gradual deprecation)
  3. Eventually delete old component

📋 Implementation Tasks

Task 1: Add Component Selection Mode (2 hours)

Goal: Add "From Component" option in Step 1

Files to Modify:

Implementation:

// Add to component
readonly creationMethod = signal<'ai' | 'template' | 'component'>('ai');
private readonly embeddableRegistry = inject(WidgetsEmbeddableRegistryService);

readonly availableEmbeddables = computed(() => 
  this.embeddableRegistry.embeddables()
);

readonly embeddableCategories = computed(() => 
  this.embeddableRegistry.categories()
);

readonly selectedEmbeddable = signal<EmbeddableDefinition | null>(null);

// Method to select embeddable
selectEmbeddable(embeddable: EmbeddableDefinition): void {
  this.selectedEmbeddable.set(embeddable);
  
  // Load property groups from embeddable
  const propertyGroups = this.embeddableRegistry.generatePropertyGroups(embeddable.id);
  this.propertyGroups.set(propertyGroups);
  
  // Pre-fill widget metadata
  this.widgetName.set(embeddable.name);
  this.widgetDescription.set(embeddable.description);
  this.widgetCategory.set(embeddable.category);
  
  // Move to customize step
  this.goToStep('customize');
}

UI Changes:

<!-- In Step 1: Describe -->
<div class="creation-methods">
  <button 
    [class.active]="creationMethod() === 'ai'"
    (click)="creationMethod.set('ai')">
    <span class="material-icons">auto_awesome</span>
    AI Generate
  </button>
  
  <button 
    [class.active]="creationMethod() === 'template'"
    (click)="creationMethod.set('template')">
    <span class="material-icons">dashboard</span>
    Templates
  </button>
  
  <button 
    [class.active]="creationMethod() === 'component'"
    (click)="creationMethod.set('component')">
    <span class="material-icons">widgets</span>
    From Component
  </button>
</div>

<!-- Component Selection UI (when creationMethod === 'component') -->
@if (creationMethod() === 'component') {
  <div class="component-palette">
    @for (category of embeddableCategories(); track category) {
      <div class="component-category">
        <h3>{{ category }}</h3>
        <div class="component-grid">
          @for (embeddable of availableEmbeddables() | filterByCategory: category; track embeddable.id) {
            <button 
              class="component-card"
              (click)="selectEmbeddable(embeddable)">
              <span class="material-icons">{{ embeddable.icon }}</span>
              <span class="component-name">{{ embeddable.name }}</span>
              <span class="component-desc">{{ embeddable.description }}</span>
            </button>
          }
        </div>
      </div>
    }
  </div>
}

Task 2: Add Code Editor Mode (2 hours)

Goal: Add advanced "Code" tab in Step 2 (Customize)

Files to Modify:

Implementation:

// Add to component
readonly customizeMode = signal<'visual' | 'code'>('visual');

readonly templateCode = signal<string>('');
readonly stylesCode = signal<string>('');
readonly logicCode = signal<string>('');

toggleCodeEditor(): void {
  const current = this.customizeMode();
  this.customizeMode.set(current === 'visual' ? 'code' : 'visual');
}

UI Changes:

<!-- In Step 2: Customize -->
<div class="customize-header">
  <h2>Customize Widget</h2>
  <div class="mode-toggle">
    <button 
      [class.active]="customizeMode() === 'visual'"
      (click)="customizeMode.set('visual')">
      <span class="material-icons">tune</span>
      Visual
    </button>
    <button 
      [class.active]="customizeMode() === 'code'"
      (click)="customizeMode.set('code')">
      <span class="material-icons">code</span>
      Code
    </button>
  </div>
</div>

@if (customizeMode() === 'visual') {
  <!-- Existing property groups UI -->
  <div class="property-groups">
    <!-- ... property editor ... -->
  </div>
} @else {
  <!-- Code editor UI -->
  <div class="code-editor">
    <div class="code-tabs">
      <button 
        [class.active]="activeCodeTab() === 'template'"
        (click)="activeCodeTab.set('template')">
        Template (HTML)
      </button>
      <button 
        [class.active]="activeCodeTab() === 'styles'"
        (click)="activeCodeTab.set('styles')">
        Styles (SCSS)
      </button>
      <button 
        [class.active]="activeCodeTab() === 'logic'"
        (click)="activeCodeTab.set('logic')">
        Logic (TS)
      </button>
    </div>
    
    <div class="code-content">
      @switch (activeCodeTab()) {
        @case ('template') {
          <textarea 
            [(ngModel)]="templateCode"
            class="code-textarea"
            placeholder="<div class='my-widget'>...</div>">
          </textarea>
        }
        @case ('styles') {
          <textarea 
            [(ngModel)]="stylesCode"
            class="code-textarea"
            placeholder=".my-widget { ... }">
          </textarea>
        }
        @case ('logic') {
          <textarea 
            [(ngModel)]="logicCode"
            class="code-textarea"
            placeholder="// Widget logic here">
          </textarea>
        }
      }
    </div>
  </div>
}

Task 3: Update Route Configuration (30 minutes)

Goal: Make workspace the primary entry point

File to Modify: libs/libs-widgets/widget-builder/src/widget-builder.routes.ts

Changes:

export const WidgetBuilderRoutes: Routes = [
  // Default route → workspace
  {
    path: '',
    redirectTo: 'workspace',
    pathMatch: 'full'
  },
  
  // Template gallery (separate page)
  {
    path: 'templates',
    loadComponent: () => import('./lib/components/template-gallery/template-gallery.component')
      .then(m => m.TemplateGalleryComponent),
    title: '📚 Widget Templates',
    data: {
      animation: 'fade',
      tool: 'widget-builder',
      icon: 'fas fa-layer-group',
      description: 'Browse and use professional widget templates'
    }
  },
  
  // Workspace (main builder) - PRIMARY
  {
    path: 'workspace',
    loadComponent: () => import('./lib/components/widget-builder-workspace/widget-builder-workspace.component')
      .then(m => m.WidgetBuilderWorkspaceComponent),
    title: 'Widget Builder - Workspace',
    data: {
      mode: 'create',
      animation: 'slideLeft'
    }
  },
  
  // Edit existing widget
  {
    path: 'workspace/edit/:widgetId',
    loadComponent: () => import('./lib/components/widget-builder-workspace/widget-builder-workspace.component')
      .then(m => m.WidgetBuilderWorkspaceComponent),
    title: 'Edit Widget',
    data: {
      mode: 'edit',
      animation: 'slideLeft'
    }
  },
  
  // Property Manager
  {
    path: 'properties',
    loadComponent: () => import('./lib/components/property-manager/property-manager.component')
      .then(m => m.PropertyManagerComponent),
    title: 'Property Manager - Atomic Building Blocks',
    data: {
      animation: 'slideLeft',
      tool: 'property-manager'
    }
  },
  
  // OLD COMPONENT - DEPRECATED (add deprecation notice)
  {
    path: 'legacy',
    loadComponent: () => import('./lib/components/widget-builder/widget-builder.component')
      .then(m => m.WidgetBuilderComponent),
    title: 'Widget Builder (Legacy)',
    data: {
      deprecated: true,
      redirectTo: '/widgets/builder/workspace'
    }
  }
];

Task 4: Add Deprecation Notice (30 minutes)

Goal: Warn users that old component is deprecated

File to Modify: widget-builder.component.ts

Add to template:

<!-- Add at top of old component -->
<div class="deprecation-notice">
  <span class="material-icons">warning</span>
  <div class="notice-content">
    <strong>This version is deprecated</strong>
    <p>Please use the new <a [routerLink]="['/widgets/builder/workspace']">Widget Builder Workspace</a> for the best experience.</p>
  </div>
  <button (click)="router.navigate(['/widgets/builder/workspace'])">
    Switch Now
  </button>
</div>

📊 Feature Comparison Matrix

Feature Old Builder Workspace (After Enhancement)
AI Generation ✅ Basic ✅ Advanced (IntelligentGenerator)
Template Gallery ❌ ✅
Component Selection ✅ ✅ (After Task 1)
Code Editor ✅ ✅ (After Task 2)
Property Groups ✅ ✅
Wizard UX ❌ ✅
Live Preview ⚠️ Basic ✅ Advanced
Progress Tracking ❌ ✅
Modern State Management ⚠️ ✅ Signals

🎯 Success Criteria

Functional Requirements

UX Requirements

Technical Requirements


📁 Files to Modify

High Priority (Tasks 1-2)

  1. widget-builder-workspace.component.ts - Add component selection + code editor
  2. widget-builder-workspace.component.html - UI for new features
  3. widget-builder-workspace.component.scss - Styles for new UI

Medium Priority (Task 3)

  1. widget-builder.routes.ts - Update route configuration

Low Priority (Task 4)

  1. widget-builder.component.ts - Add deprecation notice
  2. widget-builder.component.html - Deprecation banner

📊 Estimated Time

Total: ~5 hours (buffer included for testing)


🚀 Implementation Order

  1. First: Task 1 (Component Selection) - Highest value, enables embeddable integration
  2. Second: Task 3 (Routes) - Makes workspace the primary entry
  3. Third: Task 2 (Code Editor) - For advanced users
  4. Fourth: Task 4 (Deprecation) - Graceful transition

📝 Testing Checklist

Component Selection Mode

Code Editor Mode

Route Redirection


🎉 Expected Outcome

After consolidation:


Document Version: 1.0
Session: 9
Status: 🔄 Ready for Implementation
Next Step: Implement Task 1 (Component Selection Mode)