Embeddable Architecture - Critical Clarification

Resolving the confusion between .embeddable.ts files, widgets, and MongoDB storage

Embeddable Architecture - Critical Clarification

🚨 The Problem You Identified

You're absolutely right to question this! Looking at the codebase, we have THREE OVERLAPPING SYSTEMS:

  1. .embeddable.ts files (17 files in libs-ui-core/, libs-ui/, libs-ux/)

    • Define UiComponentConfig with inputs, outputs, and variants
    • Hardcoded in source code
    • Example: card.embeddable.ts has 4 variants hardcoded
  2. Widget Builder (MongoDB storage)

    • Creates widgets with variants dynamically
    • Stores in space_widgets collection
    • Variants are user-created
  3. Embeddable Registry (WidgetsEmbeddableRegistryService)

    • Single source of truth for UI atoms
    • Loads from BOTH built-in .embeddable.ts AND MongoDB widgets

The Confusion: Variants exist in TWO places, creating ambiguity about the source of truth.


πŸ“Š Current Architecture (What Actually Exists)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    EMBEDDABLE REGISTRY                          β”‚
β”‚            (Single Source of Truth - Runtime)                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  BUILT-IN      β”‚      β”‚  USER-CREATED   β”‚
β”‚  (Hardcoded)   β”‚      β”‚  (MongoDB)      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€      β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Source: NX libsβ”‚      β”‚ Source: DB      β”‚
β”‚ 17 .embeddable β”‚      β”‚ Dynamic         β”‚
β”‚ Has variants   β”‚      β”‚ Has variants    β”‚
β”‚ Static         β”‚      β”‚ User-defined    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example: Card Component

Built-in (card.embeddable.ts):

export const CARD_EMBEDDABLE: UiComponentConfig = {
  id: 'ux-card',
  name: 'UX Card',
  component: UxCardComponent,
  variants: [
    { id: 'default-card', name: 'Default Card', inputOverrides: {...} },
    { id: 'elevated-card', name: 'Elevated Card', inputOverrides: {...} },
    { id: 'outlined-card', name: 'Outlined Card', inputOverrides: {...} },
    { id: 'full-card', name: 'Full Featured Card', inputOverrides: {...} }
  ]
};

User-created (MongoDB):

{
  "id": "widget_123",
  "name": "My Custom Card",
  "type": "card",
  "variants": [
    { "id": "variant_456", "name": "Dark Card", "properties": {...} },
    { "id": "variant_789", "name": "Light Card", "properties": {...} }
  ]
}

Problem: Two separate variant systems with no clear relationship!


🎯 The Solution (95% Confidence)

Principle: Two-Tier System

TIER 1: Built-in Embeddables (Foundation)
  ↓ (provides)
TIER 2: User Widgets (Extensions)

Architecture Should Be:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    TIER 1: BUILT-IN EMBEDDABLES                 β”‚
β”‚              (Foundation - Ships with Platform)                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ Defined in .embeddable.ts files                               β”‚
β”‚ β€’ Provide DEFAULT variants as starting points                   β”‚
β”‚ β€’ Immutable, versioned with platform releases                   β”‚
β”‚ β€’ Examples: UX Card (4 defaults), Button (6 defaults)           β”‚
β”‚ β€’ Purpose: Professional, polished, ready-to-use components      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                    (user extends)
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    TIER 2: USER WIDGETS                         β”‚
β”‚              (Extensions - Created by Users)                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ Created via Widget Builder                                    β”‚
β”‚ β€’ Stored in MongoDB (space_widgets collection)                  β”‚
β”‚ β€’ Can REFERENCE built-in embeddables as base                    β”‚
β”‚ β€’ User-created variants extend/override defaults                β”‚
β”‚ β€’ Examples: "My Brand Button", "Custom Pricing Card"            β”‚
β”‚ β€’ Purpose: Custom, brand-specific, project-specific components  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ Implementation Strategy

1. Keep .embeddable.ts Files As-Is βœ…

Purpose: Professional component library (like Material UI, Ant Design)

Characteristics:

Example Use Case:

// User starts with built-in "Elevated Card" variant
// Then customizes it in Widget Builder
// Saves as "My Brand Card" in MongoDB

2. Clarify MongoDB Widgets βœ…

Purpose: User-created extensions

Characteristics:

Data Model (MongoDB):

interface UserWidget {
  id: string;
  name: string;
  type: string;
  
  // Reference to built-in embeddable (optional)
  baseEmbeddableId?: string; // e.g., 'ux-card'
  
  // User's customizations
  propertyConfig: {
    groups: PropertyGroup[];
    values: Record<string, unknown>;
  };
  
  // User's variants (extend/override built-in)
  variants: Array<{
    id: string;
    name: string;
    description: string;
    properties: Record<string, unknown>;
    baseVariantId?: string; // Reference to built-in variant (optional)
  }>;
  
  // Metadata
  spaceId: string;
  scope: 'private' | 'public';
  createdBy: string;
  createdAt: Date;
}

3. Registry Merging Strategy βœ…

On App Load:

// 1. Load built-in embeddables (static)
await uiEmbeddableRegistrar.initialize();
// Result: 17 built-in embeddables with default variants

// 2. Load user widgets from MongoDB (dynamic)
await widgetFlashSync.initialize(spaceId);
// Result: User's custom widgets

// 3. Merge in Registry
const allEmbeddables = [
  ...builtInEmbeddables,  // Source: 'builtin'
  ...userWidgets          // Source: 'user-created'
];

In Widget Builder:

// Show both tiers
const templates = [
  { group: 'Built-in', items: builtInEmbeddables },
  { group: 'My Widgets', items: userWidgets }
];

🎨 Variant Inheritance Model

Scenario: User Customizes Built-in Card

Step 1: User selects "Elevated Card" (built-in variant)

// Built-in definition
{
  id: 'elevated-card',
  name: 'Elevated Card',
  inputOverrides: {
    variant: 'elevated',
    size: 'md'
  }
}

Step 2: User customizes in Widget Builder

Step 3: User saves as "My Brand Card"

{
  "id": "widget_user_123",
  "name": "My Brand Card",
  "type": "card",
  "baseEmbeddableId": "ux-card",
  "variants": [
    {
      "id": "variant_456",
      "name": "Brand Elevated",
      "baseVariantId": "elevated-card",
      "properties": {
        "variant": "elevated",
        "size": "md",
        "boxShadow": "0 8px 24px rgba(59, 130, 246, 0.3)",
        "borderRadius": 16,
        "backgroundColor": "#3b82f6"
      }
    }
  ]
}

Result: User widget extends built-in, creating brand-specific version.


πŸ“‹ Recommended Changes (Immediate)

Change 1: Add baseEmbeddableId to MongoDB Schema βœ…

File: apis/backend-api/src/app/widgets/dto/create-widget.dto.ts

export class CreateWidgetDto {
  name: string;
  type: string;
  category: string;
  
  // NEW: Reference to built-in embeddable
  baseEmbeddableId?: string;
  
  propertyConfig: {...};
  variants: WidgetVariantDto[];
}

Change 2: Add baseVariantId to Variant Schema βœ…

File: apis/backend-api/src/app/widgets/dto/create-variant.dto.ts

export class CreateVariantDto {
  name: string;
  description?: string;
  properties: Record<string, unknown>;
  
  // NEW: Reference to built-in variant
  baseVariantId?: string;
  
  preview?: string;
}

Change 3: Update Widget Builder UI βœ…

File: libs/libs-widgets/widget-builder/src/lib/components/widget-builder-workspace/widget-builder-workspace.component.ts

Add "Start from Template" tab:

<div class="template-selector">
  <h3>Start from Built-in</h3>
  @for (embeddable of builtInEmbeddables(); track embeddable.id) {
    <div class="template-card" (click)="selectBuiltIn(embeddable)">
      <div class="icon">{{ embeddable.icon }}</div>
      <div class="name">{{ embeddable.name }}</div>
      <div class="variants">{{ embeddable.variants?.length }} variants</div>
    </div>
  }
</div>

Change 4: Clarify Documentation βœ…

Update: docs/english/GoGrab-Space/modules/embeddable-components-guide.md

Add section:

## Two Types of Embeddables

### 1. Built-in Embeddables (Platform Foundation)
- Defined in `.embeddable.ts` files
- Professional, polished components
- Default variants as starting points
- Immutable (change via platform release)
- Examples: UX Card, Panel, Menu, Button

### 2. User Widgets (Custom Extensions)
- Created via Widget Builder
- Stored in MongoDB
- Can reference built-in as base
- User-defined variants
- Examples: "My Brand Button", "Company Card"

πŸ† Final Architecture (AI-Ready)

For AI Agents to Understand:

Q: What's an embeddable?

Q: What's a widget?

Q: What are .embeddable.ts files?

Q: Where do variants live?

Q: Can I create a widget without a built-in base?

Q: How do modules fit in?


🎯 Naming Convention (Final)

Term Definition Storage Example
Component Pure Angular component NX library UxCardComponent
Embeddable Component + registration config .embeddable.ts CARD_EMBEDDABLE
Built-in Embeddable Platform-shipped embeddable NX library All 17 in libs-ui
User Widget User-created via builder MongoDB "My Brand Card"
Module Embeddable Component exposed by module Module manifest UserListWidget
Widget Wrapper Embeddable + editor features Runtime CardWidgetComponent

βœ… Action Items (Immediate)

1. Update MongoDB Schema βœ…

2. Update Widget Builder UI βœ…

3. Update Documentation βœ…

4. Clean Up Terminology βœ…


πŸ€– AI Training Guidance

Generate New Widget from Built-in

User Prompt: "Create a dark mode version of the Elevated Card"

AI Flow:

  1. Load CARD_EMBEDDABLE definition
  2. Find elevated-card variant
  3. Start with those inputOverrides
  4. Apply dark mode properties
  5. Save as new user widget with baseEmbeddableId: 'ux-card'

Result:

{
  "name": "Dark Elevated Card",
  "baseEmbeddableId": "ux-card",
  "variants": [{
    "name": "Dark Mode",
    "baseVariantId": "elevated-card",
    "properties": {
      "variant": "elevated",
      "size": "md",
      "backgroundColor": "#1f2937",
      "textColor": "#f9fafb",
      "boxShadow": "0 10px 25px rgba(139, 92, 246, 0.3)"
    }
  }]
}

Generate New Widget from Scratch

User Prompt: "Create a testimonial card with author photo and quote"

AI Flow:

  1. No baseEmbeddableId (from scratch)
  2. Generate component structure
  3. Create property groups
  4. Generate default variant
  5. Save as new user widget

Result:

{
  "name": "Testimonial Card",
  "baseEmbeddableId": null,
  "propertyConfig": {
    "groups": [/* custom property groups */]
  },
  "variants": [/* generated variants */]
}

🎨 Variant Creation Workflow (Final)

For Built-in Embeddables

When: User wants professional, vetted components

Flow:

  1. Browse built-in embeddables in Widget Builder
  2. Select embeddable (e.g., "UX Card")
  3. See 4 built-in variants
  4. Click "Use as Template"
  5. Customize in editor
  6. Save as new user widget (with baseEmbeddableId)

For Custom Widgets

When: User needs unique, brand-specific components

Flow:

  1. AI Generate OR Manual Compose
  2. Define property groups
  3. Create variants
  4. Save to MongoDB
  5. Use across pages

πŸš€ Benefits of This Architecture

For Users

βœ… Professional components out-of-the-box (built-ins) βœ… Unlimited customization (user widgets) βœ… Clear inheritance (baseEmbeddableId + baseVariantId) βœ… Reusability (save once, use everywhere)

For AI

βœ… Clear distinction between tiers βœ… Explicit inheritance for learning βœ… Default presets as training data βœ… Variant generation guidance

For Platform

βœ… Version control for built-ins (via git) βœ… Multi-tenant isolation for user widgets (via MongoDB) βœ… Gradual rollout (add more built-ins over time) βœ… Extensibility (users can always go custom)


πŸ“ Data Models (Final)

Built-in Embeddable (.embeddable.ts)

interface UiComponentConfig {
  id: string;                      // 'ux-card'
  name: string;                    // 'UX Card'
  component: Type<unknown>;        // UxCardComponent
  inputs: UiInputDefinition[];     // Contract
  outputs: UiOutputDefinition[];   // Contract
  variants: UiVariant[];           // DEFAULT presets
  category: string;
  icon: string;
  tags: string[];
}

User Widget (MongoDB)

interface UserWidget {
  id: string;                      // 'widget_123'
  name: string;                    // 'My Brand Card'
  type: string;                    // 'card'
  
  // Inheritance
  baseEmbeddableId?: string;       // 'ux-card' (if extends built-in)
  
  // Configuration
  propertyConfig: {
    groups: PropertyGroup[];       // Custom property organization
    values: Record<string, unknown>; // Current values
  };
  
  // Variants
  variants: Array<{
    id: string;                    // 'variant_456'
    name: string;                  // 'Dark Mode'
    baseVariantId?: string;        // 'elevated-card' (if extends built-in variant)
    properties: Record<string, unknown>;
  }>;
  
  // Metadata
  spaceId: string;
  scope: 'private' | 'public';
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}

πŸ”„ Migration Path (If Needed)

Current State: Mixed responsibilities

Target State: Clear two-tier system

Steps:

  1. βœ… Add baseEmbeddableId and baseVariantId to schema
  2. βœ… Update Widget Builder to show built-in templates
  3. βœ… Update docs to clarify two-tier system
  4. ⏳ (Optional) Migrate existing user widgets to reference built-ins

Timeline: Can be done incrementally, no breaking changes


πŸ’‘ Key Insights

What You Were Right About

βœ… "Things we're saving as .embeddable should be MongoDB-ready"

βœ… "The issue would be actions, inputs, outputs, etc"

βœ… "Everything must be ready to create variants easily"

What's Actually Happening

Built-in embeddables (.embeddable.ts):

User widgets (MongoDB):

Together:


🎯 Confidence Rating: 95%

Why 95% and not 100%?

What makes it 95%?


Last Updated: December 9, 2025 - Session 10.2 Status: Ready for implementation