WIDGET RENDERING FIXES

Comprehensive guidelines for AI-assisted development within the Editor platform

Widget Rendering Fixes - Critical Bug Fixes

Date: December 9, 2025
Session: Widget System Architecture Fix - Part 2
Status: ✅ COMPLETE


Overview

Fixed critical rendering bugs in complex widgets that were causing "Cannot read properties of undefined" errors. The root cause was widgets accessing nested config properties without defensive defaults.


Bugs Fixed

1. Gallery Widget - columns.desktop Undefined Error

Error: Cannot read properties of undefined (reading 'desktop')

Location: gallery-widget.component.ts:491

Root Cause:

// BROKEN - No defaults merged
config = computed(() => {
  return this.widget().properties as GalleryWidgetConfig;
});

// Later in gridStyles computed:
columns = config.columns.desktop; // ❌ config.columns is undefined!

Fix:

// FIXED - Merges with defaults from gallery-widget.types.ts
config = computed(() => {
  const props = this.widget().properties || {};
  return {
    ...defaultGalleryWidgetConfig,
    ...props,
    columns: {
      ...defaultGalleryWidgetConfig.columns!,
      ...(props as any).columns
    }
  } as GalleryWidgetConfig;
});

Default Columns:

{
  desktop: 3,
  tablet: 2,
  mobile: 1
}

2. Hero Widget - Missing Property Defaults

Location: hero-widget.component.ts

Root Cause: Widget accessed config().titulo, config().overlayOpacity, etc. without defaults

Fix:

config = computed(() => {
  const props = this.widget.properties || {};
  return {
    titulo: 'Hero Title',
    subtitulo: 'Hero Subtitle',
    overlayOpacity: 0.4,
    mostrarBoton: true,
    textoBoton: 'Get Started',
    targetBoton: '_self' as const,
    alturaMinima: 400,
    alineacionTexto: 'center' as const,
    posicionVertical: 'center' as const,
    ...props,
  } as HeroWidgetConfig;
});

3. Pricing Widget - columns Undefined Error

Location: pricing-widget.component.ts

Root Cause: Widget likely accessed config().columns.base without defaults

Fix:

config = computed(() => {
  const props = this.widget().properties || {};
  return {
    title: 'Choose Your Plan',
    description: '',
    plans: [],
    layout: 'grid' as const,
    columns: {
      base: 1,
      md: 2,
      lg: 3,
    },
    currency: 'USD',
    currencySymbol: '$',
    currencyPosition: 'before' as const,
    billingToggle: true,
    showPeriod: true,
    cardStyle: 'default' as const,
    showPopular: true,
    featuredScale: 1.05,
    spacing: 'normal' as const,
    primaryColor: '#3b82f6',
    secondaryColor: '#6b7280',
    accentColor: '#f59e0b',
    textColor: '#1f2937',
    backgroundColor: '#ffffff',
    borderColor: '#e5e7eb',
    animateOnView: true,
    hoverEffect: 'lift' as const,
    clickable: false,
    ariaLabel: 'Pricing plans',
    screenReaderFriendly: true,
    showComparison: false,
    highlightDifferences: false,
    compactMode: false,
    enabled: true,
    ...props,
    columns: {
      base: 1,
      md: 2,
      lg: 3,
      ...(props as any).columns
    }
  } as PricingWidgetConfig;
});

4. Testimonial Widget - author Object Undefined

Location: testimonial-widget.component.ts

Root Cause: Widget accessed config().author.name, config().author.avatar, etc. without ensuring author exists

Fix:

config = computed(() => {
  const props = this.widget.properties || {};
  return {
    quote: 'Customer testimonial',
    author: {
      name: 'Customer',
      title: '',
      company: '',
      avatar: '',
      rating: 5,
      ...(props as any).author
    },
    layout: 'default' as const,
    alignment: 'left' as const,
    showQuotes: true,
    showRating: true,
    showAvatar: true,
    quoteSize: 'medium' as const,
    authorSize: 'medium' as const,
    // ... all other defaults ...
    ...props,
    // Re-merge author for deep merge
    author: {
      name: 'Customer',
      title: '',
      company: '',
      avatar: '',
      rating: 5,
      ...(props as any).author
    }
  } as TestimonialWidgetConfig;
});

Pattern: Good vs Bad Config Handling

❌ BAD Pattern (Causes Undefined Errors)

config = computed(() => {
  return this.widget().properties as MyWidgetConfig;
});

// Later:
const value = config().nestedObject.property; // ❌ CRASH if nestedObject is undefined!

✅ GOOD Pattern (Card Widget Example)

config = computed(() => {
  const properties = this.widget().properties as CardWidgetConfig;
  const presetKey = (properties.variantPreset as CardVariantPreset) || 'default';
  const preset = this.VARIANT_PRESETS[presetKey] || {};
  return { ...CARD_WIDGET_DEFAULTS, ...preset, ...properties }; // ✅ Always has defaults
});

✅ BEST Pattern (New Standard)

config = computed(() => {
  const props = this.widget().properties || {};
  return {
    ...DEFAULT_CONFIG,
    ...props,
    // Deep merge nested objects
    nestedObject: {
      ...DEFAULT_CONFIG.nestedObject,
      ...(props as any).nestedObject
    }
  } as MyWidgetConfig;
});

Why This Happens

Widgets can be created through multiple paths:

  1. WidgetFactoryService - Uses factory defaults ✅
  2. ModuleWidgetBridgeService - Converts embeddables, may not have all properties ⚠️
  3. Loaded from storage - Old saves may have incomplete config ⚠️
  4. AI generation - May not generate complete config ⚠️

The component MUST provide defaults regardless of how the widget was created.


Files Fixed

  1. gallery-widget.component.ts - Added defaults with deep merge for columns
  2. hero-widget.component.ts - Added defaults for all properties
  3. pricing-widget.component.ts - Added defaults with deep merge for columns
  4. testimonial-widget.component.ts - Added defaults with deep merge for author

Testing Recommendations

Test all complex widgets with:

  1. Empty properties: { properties: {} }
  2. Partial properties: { properties: { title: 'Test' } }
  3. Missing nested objects: { properties: { columns: undefined } }
  4. Loaded from embeddables: Created via ModuleWidgetBridgeService

All should render without errors.


Future Prevention

Code Review Checklist

When creating new widgets that access nested config:

Standard Pattern

Always use this pattern for complex widgets:

config = computed(() => {
  const props = this.widget().properties || {};
  return {
    ...DEFAULT_CONFIG,  // Import from factory or types file
    ...props,
    // Deep merge ALL nested objects
    nestedObj: {
      ...DEFAULT_CONFIG.nestedObj,
      ...(props as any).nestedObj
    }
  } as MyWidgetConfig;
});

Impact

These fixes prevent:

The widgets are now robust and handle all edge cases gracefully.