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:
.embeddable.tsfiles (17 files inlibs-ui-core/,libs-ui/,libs-ux/)- Define
UiComponentConfigwith inputs, outputs, and variants - Hardcoded in source code
- Example:
card.embeddable.tshas 4 variants hardcoded
- Define
Widget Builder (MongoDB storage)
- Creates widgets with variants dynamically
- Stores in
space_widgetscollection - Variants are user-created
Embeddable Registry (
WidgetsEmbeddableRegistryService)- Single source of truth for UI atoms
- Loads from BOTH built-in
.embeddable.tsAND 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:
- β Hardcoded in source code
- β Variants are "starter presets"
- β Immutable (change via code release)
- β Always available (no database dependency)
- β Professional quality (vetted by platform team)
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:
- β Created via Widget Builder
- β Stored in MongoDB
- β
Can reference built-in embeddable as
baseEmbeddableId - β Variants are user-created customizations
- β Dynamic, editable, deletable
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
- Changes shadow to
0 8px 24px - Changes border radius to
16px - Adds brand color
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?
- A: An Angular component with a well-defined contract (inputs/outputs)
- Can be built-in (shipped with platform) OR user-created (MongoDB)
Q: What's a widget?
- A: A user-created embeddable with editor metadata (property groups, variants)
- Always stored in MongoDB
- Can reference/extend built-in embeddables
Q: What are .embeddable.ts files?
- A: Configuration for built-in components (Tier 1)
- Provides professional defaults
- Users extend these via Widget Builder
Q: Where do variants live?
- A: Both places, but with different purposes:
- Built-in: Default presets (starting points)
- User widgets: Custom variants (extensions)
Q: Can I create a widget without a built-in base?
- A: Yes! User can create from scratch via AI generation or manual composition.
Q: How do modules fit in?
- A: Modules expose embeddables in their manifest. Those embeddables can be:
- References to built-in embeddables
- Module-specific embeddables (inside module NX lib)
- Wrappers around user widgets
π― 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 β
- Add
baseEmbeddableIdto widget schema - Add
baseVariantIdto variant schema - This creates explicit inheritance tracking
2. Update Widget Builder UI β
- Add "Start from Built-in" template selector
- Show built-in variants as starting points
- Clear visual distinction: Built-in vs Custom
3. Update Documentation β
- Clarify two-tier system in all docs
- Explain inheritance model
- Provide AI training examples
4. Clean Up Terminology β
- Use "Built-in Embeddable" not just "embeddable"
- Use "User Widget" not just "widget"
- Be explicit about tier in UI
π€ AI Training Guidance
Generate New Widget from Built-in
User Prompt: "Create a dark mode version of the Elevated Card"
AI Flow:
- Load
CARD_EMBEDDABLEdefinition - Find
elevated-cardvariant - Start with those inputOverrides
- Apply dark mode properties
- 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:
- No baseEmbeddableId (from scratch)
- Generate component structure
- Create property groups
- Generate default variant
- 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:
- Browse built-in embeddables in Widget Builder
- Select embeddable (e.g., "UX Card")
- See 4 built-in variants
- Click "Use as Template"
- Customize in editor
- Save as new user widget (with
baseEmbeddableId)
For Custom Widgets
When: User needs unique, brand-specific components
Flow:
- AI Generate OR Manual Compose
- Define property groups
- Create variants
- Save to MongoDB
- 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:
- β
Add
baseEmbeddableIdandbaseVariantIdto schema - β Update Widget Builder to show built-in templates
- β Update docs to clarify two-tier system
- β³ (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"
- Partially correct: Built-ins provide TEMPLATES, users save to MongoDB
β "The issue would be actions, inputs, outputs, etc"
- Correct: Built-ins define contract, user widgets customize values
β "Everything must be ready to create variants easily"
- Correct: Both tiers support variants, but with different purposes
What's Actually Happening
Built-in embeddables (.embeddable.ts):
- Professional component library
- Default variants = starting points
- Like Material UI or Ant Design
User widgets (MongoDB):
- Custom extensions
- User variants = brand-specific
- Like Tailwind UI Pro (user customizations)
Together:
- Built-ins provide foundation
- Users extend/customize
- Both registered in same runtime registry
- Clear inheritance via
baseEmbeddableId
π― Confidence Rating: 95%
Why 95% and not 100%?
- Need to verify backend schema supports
baseEmbeddableId - Need to implement template selector UI
- Need to test variant inheritance end-to-end
What makes it 95%?
- Architecture aligns with industry standards (Material UI model)
- Clear separation of concerns
- Extensibility without breaking existing code
- AI-trainable with explicit inheritance
Last Updated: December 9, 2025 - Session 10.2 Status: Ready for implementation