SIMPLIFIED WIDGET ARCHITECTURE
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Simplified Widget Architecture
Created: December 3, 2025 Status: 🎯 Proposed - Needs Review Goal: Simplify widget creation by making embeddables the atomic foundation
🎯 The Problem
The current widget system is confusing and complex:
- Multiple entry points: Widget Builder (Basic/Custom), Widget Manager, Variant Creator
- Unclear hierarchy: What's the difference between widget, variant, custom widget, embeddable?
- Broken variants: Core widgets don't show correct variants
- Disconnected from modules: Widgets and module embeddables feel like separate systems
💡 The Insight
"Start with embeddables as the atomic unit, then build widgets on top of them."
Embeddables are already defined in ModuleEmbeddableComponent - they ARE the smallest UI piece. Everything else should build upon them.
🏗️ Simplified Hierarchy
┌─────────────────────────────────────────────────────────────────┐
│ ATOMIC LAYER (Foundation) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ EMBEDDABLE COMPONENT │
│ ├── Angular Standalone Component (.ts, .html, .scss) │
│ ├── Input definitions (typed properties) │
│ ├── Output definitions (events) │
│ ├── Category (widget, form, list, card, etc.) │
│ └── Base styling │
│ │
│ Example: ButtonEmbeddable, InputEmbeddable, CardEmbeddable │
│ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ CONFIGURATION LAYER (Widgets) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WIDGET = Embeddable + Configuration │
│ ├── References ONE embeddable │
│ ├── Property Groups (organized input controls) │
│ ├── Variants (saved configurations of the same embeddable) │
│ ├── Default values │
│ └── Persistence settings │
│ │
│ Example: "Primary Button" = ButtonEmbeddable + blue styling │
│ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ COMPOSITION LAYER (Custom Widgets) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CUSTOM WIDGET = Multiple Embeddables + Layout + Logic │
│ ├── Combines multiple embeddables │
│ ├── Defines layout/arrangement │
│ ├── Wires outputs to inputs (event connections) │
│ └── Adds custom logic │
│ │
│ Example: "Login Form" = InputEmbeddable + InputEmbeddable │
│ + ButtonEmbeddable + validation logic │
│ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ MODULE LAYER (Integration) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ MODULE = Embeddables + Actions + State + Routes │
│ ├── Declares embeddableComponents[] in manifest │
│ ├── Defines public actions │
│ ├── Manages state/collections │
│ └── Provides routes │
│ │
│ Example: UserManagementModule with UserCardEmbeddable, │
│ UserListEmbeddable, UserFormEmbeddable │
│ │
└─────────────────────────────────────────────────────────────────┘
📐 Data Structures
1. Embeddable (Atomic Unit)
Already exists in ModuleEmbeddableComponent:
interface ModuleEmbeddableComponent {
id: string; // 'button', 'input', 'card'
componentType: Type<unknown>; // Angular component class
displayName: string; // 'Button', 'Text Input'
description?: string;
category: 'widget' | 'form' | 'list' | 'card' | 'dashboard' | 'custom';
inputs?: ModuleParameterMetadata[]; // Typed inputs
outputs?: ModuleOutputMetadata[]; // Events
thumbnail?: string;
}
2. Widget (Configuration Layer)
interface WidgetDefinition {
id: string; // 'primary-button-widget'
name: string; // 'Primary Button'
description?: string;
// REFERENCE to embeddable (not duplication!)
embeddableId: string; // 'button'
// Configuration on top of embeddable
propertyGroups: PropertyGroup[]; // Organized input controls
defaultValues: Record<string, unknown>;
// Variants are SAVED CONFIGURATIONS (not separate components)
variants: WidgetVariant[];
// Metadata
icon?: string;
thumbnail?: string;
tags?: string[];
}
interface WidgetVariant {
id: string; // 'variant-primary-blue'
name: string; // 'Primary Blue'
description?: string;
thumbnail?: string;
// Just the property values that differ from default
propertyOverrides: Record<string, unknown>;
}
interface PropertyGroup {
id: string;
title: string; // 'Appearance', 'Behavior'
icon?: string;
collapsed?: boolean;
// Which inputs from the embeddable belong here
properties: string[]; // ['color', 'size', 'variant']
}
3. Custom Widget (Composition)
interface CustomWidgetDefinition {
id: string;
name: string;
description?: string;
// Composition of embeddables
children: EmbeddableInstance[];
// Layout configuration
layout: {
type: 'stack' | 'row' | 'grid' | 'absolute';
gap?: number;
padding?: number;
};
// Event wiring
connections: EventConnection[];
// Exposed inputs/outputs (what the custom widget exposes externally)
inputs: ModuleParameterMetadata[];
outputs: ModuleOutputMetadata[];
}
interface EmbeddableInstance {
instanceId: string; // Unique in this composition
embeddableId: string; // Reference to embeddable
position?: { x: number; y: number };
size?: { width: number; height: number };
propertyValues: Record<string, unknown>;
}
interface EventConnection {
from: { instanceId: string; output: string };
to: { instanceId: string; input: string } | { action: string };
}
🛠️ Simplified Workflow
Creating an Embeddable (Foundation)
Who: Developers/Advanced Users Tool: Code or Visual Embeddable Builder Output: Angular component + metadata
// 1. Create the Angular component
@Component({
selector: 'app-rating-stars',
standalone: true,
template: `<div class="stars">⭐</div>`,
})
export class RatingStarsComponent {
@Input() value = 0;
@Input() max = 5;
@Input() readonly = false;
@Output() valueChange = new EventEmitter<number>();
}
// 2. Define the embeddable metadata
const ratingEmbeddable: ModuleEmbeddableComponent = {
id: 'rating-stars',
componentType: RatingStarsComponent,
displayName: 'Rating Stars',
category: 'widget',
inputs: [
{ name: 'value', type: 'number', defaultValue: 0 },
{ name: 'max', type: 'number', defaultValue: 5 },
{ name: 'readonly', type: 'boolean', defaultValue: false }
],
outputs: [
{ name: 'valueChange', type: 'number' }
]
};
Creating a Widget (Configuration)
Who: Space builders Tool: Widget Builder (simplified) Output: Configured embeddable with variants
┌─────────────────────────────────────────────────────┐
│ CREATE WIDGET │
├─────────────────────────────────────────────────────┤
│ │
│ 1. Select Base Embeddable: │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ ★ │ │ 🔘 │ │ 📝 │ │ 📦 │ │
│ │Stars│ │Button│ │Input│ │ Card │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ 2. Configure Properties: │
│ Max Stars: [5] │
│ Readonly: [ ] No │
│ Size: [Medium ▾] │
│ │
│ 3. Save as Variant: │
│ [💾 Save "5-Star Rating"] │
│ │
│ 4. Create More Variants: │
│ • 5-Star Rating (default) │
│ • 10-Star Rating (max=10) │
│ • Read-Only Display (readonly=true) │
│ │
└─────────────────────────────────────────────────────┘
Creating a Custom Widget (Composition)
Who: Advanced space builders Tool: Custom Widget Composer Output: Composed widget from multiple embeddables
┌─────────────────────────────────────────────────────┐
│ COMPOSE CUSTOM WIDGET │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Embeddables │ Canvas │ │
│ │ ───────────── │ ─────── │ │
│ │ ★ Stars │ ┌───────────────┐ │ │
│ │ 📝 Input │ │ Product Name │ │ │
│ │ 🖼️ Image │ │ [Text Input] │ │ │
│ │ 📦 Card │ ├───────────────┤ │ │
│ │ │ │ [Image] │ │ │
│ │ Drag to canvas → │ │ 🖼️ Product │ │ │
│ │ │ ├───────────────┤ │ │
│ │ │ │ Rating: ★★★★☆ │ │ │
│ │ │ │ [Stars] │ │ │
│ │ │ └───────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ [💾 Save as "Product Card Widget"] │
│ │
└─────────────────────────────────────────────────────┘
🔄 Module Integration
All widgets/embeddables integrate with modules via the manifest:
const productModule: ModuleManifest = {
id: 'product-catalog',
name: 'Product Catalog',
version: '1.0.0',
// Embeddables this module provides
embeddableComponents: [
productCardEmbeddable, // Basic product card
productListEmbeddable, // List of products
productFormEmbeddable, // Edit product form
ratingStarsEmbeddable, // Rating widget
],
publicActions: [...],
routes: [...],
};
🎯 Benefits of This Architecture
- Clear Hierarchy: Embeddable → Widget → Custom Widget → Module
- No Duplication: Widgets REFERENCE embeddables, don't duplicate code
- Variants are Simple: Just saved property configurations
- Module-First: Everything aligns with
ModuleEmbeddableComponent - Composable: Custom widgets compose embeddables
- Type-Safe: Inputs/outputs are strongly typed
📋 Implementation Tasks
Phase 1: Core Embeddable Registry
- Enhance
EmbeddableRegistryServiceas the single source of truth - All core widgets register as embeddables
- Deprecate separate widget registration paths
Phase 2: Simplified Widget Builder
- Step 1: Select embeddable (from registry)
- Step 2: Configure properties (from embeddable inputs)
- Step 3: Save variant (property overrides only)
- Remove duplicate "Basic" vs "Custom" modes
Phase 3: Custom Widget Composer
- Visual composer for arranging embeddables
- Event wiring UI (output → input connections)
- Export as new embeddable
Phase 4: Variant System Fix
- Variants stored as property overrides only
- Core widgets load variants from storage
- Variant picker shows thumbnails correctly
🚫 What We Remove
- Separate "widget types": Everything is an embeddable
- Duplicate registries: One registry for embeddables
- Complex "Basic vs Custom" modes: Just "Configure" vs "Compose"
- Code generation in Widget Builder: That's for modules
✅ What We Keep
- ModuleEmbeddableComponent: The atomic unit
- Property groups: For organizing inputs
- Variants: As saved configurations
- Module integration: Embeddables in manifests
- Visual composer: For custom widgets
Next Step: Review and approve this architecture, then begin Phase 1 implementation.