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:
- WidgetFactoryService - Uses factory defaults ✅
- ModuleWidgetBridgeService - Converts embeddables, may not have all properties ⚠️
- Loaded from storage - Old saves may have incomplete config ⚠️
- AI generation - May not generate complete config ⚠️
The component MUST provide defaults regardless of how the widget was created.
Files Fixed
gallery-widget.component.ts- Added defaults with deep merge for columnshero-widget.component.ts- Added defaults for all propertiespricing-widget.component.ts- Added defaults with deep merge for columnstestimonial-widget.component.ts- Added defaults with deep merge for author
Testing Recommendations
Test all complex widgets with:
- Empty properties:
{ properties: {} } - Partial properties:
{ properties: { title: 'Test' } } - Missing nested objects:
{ properties: { columns: undefined } } - Loaded from embeddables: Created via
ModuleWidgetBridgeService
All should render without errors.
Future Prevention
Code Review Checklist
When creating new widgets that access nested config:
- Does the widget access
config().nested.property? - Does the
configcomputed provide defaults? - Are nested objects deep-merged?
- Is the widget tested with empty/partial properties?
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:
- Runtime crashes when accessing undefined nested properties
- Visual breakage in widget showcase
- Errors when adding widgets from different sources (embeddables, modules, AI)
- Data loss when loading old widget configurations
The widgets are now robust and handle all edge cases gracefully.