System Analysis - Complete Architecture Reference
Unified view of modules, embeddables, widgets, flows, backend APIs, and delivery pipeline with real code samples
System Analysis - Complete Architecture Reference
This document is the single source of truth for understanding GoGrab Space's platform generation architecture. It includes real code samples from the codebase to illustrate each concept.
1. Architecture Overview
1.1 The Component Hierarchy
┌─────────────────────────────────────────────────────────────────┐
│ UNIVERSE (Space) │
│ Composition layer - places multiple modules on a canvas │
│ Route: /modular-universe │
├─────────────────────────────────────────────────────────────────┤
│ MODULES │
│ NX libraries implementing BrowserModuleContract │
│ Own: business logic, actions, configuration, embeddables │
│ Location: modules/ folder │
├─────────────────────────────────────────────────────────────────┤
│ EMBEDDABLES │
│ Standalone Angular components with contract (inputs/outputs) │
│ Registered in module manifests and/or embeddable registry │
│ Location: libs/libs-ui-core/, module embeddables/ │
├─────────────────────────────────────────────────────────────────┤
│ WIDGETS │
│ Embeddable + editor metadata (property groups, variants) │
│ For page composition in editor; NOT source of truth │
│ Location: libs/libs-widgets/widgets/ │
├─────────────────────────────────────────────────────────────────┤
│ COMPONENTS │
│ Raw Angular components with no platform semantics │
│ Building blocks for embeddables │
└─────────────────────────────────────────────────────────────────┘
1.2 Key Directories
| Directory | Purpose |
|---|---|
apps/gograb-space/ |
Main platform application (port 5200) |
apis/backend-api/ |
NestJS API with MongoDB (port 3000) |
modules/ |
Business modules (NX libraries) |
libs/libs-ui-core/ |
Embeddable UI atoms (source of truth) |
libs/libs-widgets/widgets/ |
Widget wrappers for editor |
libs/libs-widgets/widget-builder/ |
Widget builder tool |
libs/libs-structural/modularity-lib/ |
Module workbench (6-step builder) |
libs/libs-structural/editor-web-lib/ |
Page composition editor |
libs/libs-space/universe-canvas/ |
Universe canvas builder |
2. Embeddables (Source of Truth)
2.1 What is an Embeddable?
An embeddable is a standalone Angular component that:
- Has explicit inputs and outputs (the "contract")
- Is registered in the embeddable registry or module manifest
- Can be used by modules and wrapped by widgets
- Extends
BaseWidgetComponentfor lifecycle management
2.2 Real Example: UiButtonComponent
Location: libs/libs-ui-core/ui-core/src/lib/components/ui-button/ui-button.component.ts
import { Component, input, ChangeDetectionStrategy } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
import { BaseWidgetComponent } from '@libs-widgets/widget-system';
@Component({
selector: 'ui-button',
standalone: true,
imports: [NgClass, NgStyle],
templateUrl: './ui-button.component.html',
styleUrls: ['./ui-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiButtonComponent extends BaseWidgetComponent {
// CONTRACT: Explicit inputs using Angular signals
text = input<string>('Button');
type = input<'button' | 'submit' | 'reset'>('button');
variant = input<string>('primary');
size = input<string>('md');
shape = input<string>('rounded');
effect = input<string>('pulse');
color = input<string | undefined>(undefined);
leftIcon = input<string>('');
rightIcon = input<string>('');
disabled = input<boolean>(false);
override loading = input<boolean>(false);
readonly customStyles = input<Record<string, string> | undefined>(undefined);
// Lifecycle
onWidgetInit(): void {
this.markReady();
}
onWidgetDestroy(): void {
// Cleanup handled by base class
}
// Output via base class event system
onClick(event: MouseEvent): void {
if (this.disabled() || this.loading()) {
event.preventDefault();
return;
}
this.emitWidgetEvent('click', {
mouseX: event.clientX,
mouseY: event.clientY,
timestamp: Date.now()
});
}
}
2.3 Embeddable Contract
Every embeddable must define:
- Inputs - Signal-based inputs with types and defaults
- Outputs - Events emitted via
emitWidgetEvent()or@Output() - Lifecycle -
onWidgetInit()andonWidgetDestroy()methods - Selector - Unique component selector (e.g.,
ui-button)
3. Widgets (Editor Wrappers)
3.1 What is a Widget?
A widget wraps an embeddable and adds:
- Property groups for the editor
- Variant support (style presets)
- Responsive behavior configuration
- Flash persistence integration
- Flow/action binding
3.2 Real Example: ButtonWidgetComponent
Location: libs/libs-widgets/widgets/src/lib/components/button-widget/button-widget.component.ts
import { Component, computed, inject, input, Output, EventEmitter } from '@angular/core';
import { Widget, BreakpointKey } from '@libs-foundational/core-models';
import { UiButtonComponent } from '@libs-ui-core/ui-core';
import { BaseWidgetComponent } from '@libs-widgets/widget-system';
import { usePropertyStyles } from '../../services/property-applicators';
import { WIDGET_FLOW_EXECUTOR_SERVICE } from '@libs-foundational/core-angular';
@Component({
selector: 'lib-button-widget',
standalone: true,
imports: [FormsModule, UiButtonComponent], // <-- Imports the embeddable
templateUrl: './button-widget.component.html',
styleUrls: ['./button-widget.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonWidgetComponent extends BaseWidgetComponent {
// Widget inputs from editor
readonly widget = input.required<Widget>();
readonly isSelected = input<boolean>(false);
readonly isEditing = input<boolean>(false);
readonly activeBreakpoint = input<BreakpointKey>('base');
@Output() widgetChange = new EventEmitter<Widget>();
@Output() widgetAction = new EventEmitter<{ widget: Widget; action: string; data?: any }>();
// Optional flow executor for action binding
private readonly flowExecutor = inject(WIDGET_FLOW_EXECUTOR_SERVICE, { optional: true });
// Merge widget props with embeddable props
config = computed<ButtonWidgetConfig>(() => {
const defaultConfig = { text: 'Button', buttonType: 'primary', /* ... */ };
const widgetProps = this.widget()?.properties ?? {};
return { ...defaultConfig, ...widgetProps };
});
// SMART PROPERTY SYSTEM: Apply font + appearance via applicators
// Replaces 70+ lines of manual style checking with 1 line!
customButtonStyles = usePropertyStyles(this.widget, this.activeBreakpoint, ['font', 'appearance']);
// Handle button click - can trigger flows
async handleButtonClick(event: MouseEvent): Promise<void> {
const action = this.config().action;
if (action === 'flow' && this.config().flowId && this.flowExecutor) {
await this.flowExecutor.executeFlow(this.config().flowId, {
widgetId: this.widget().id,
event: 'click'
});
}
this.widgetAction.emit({ widget: this.widget(), action: 'click', data: event });
}
}
3.3 Widget Template (wrapping embeddable)
<!-- button-widget.component.html -->
<ui-button
[text]="buttonText()"
[type]="buttonType_attr()"
[variant]="buttonType()"
[size]="buttonSize()"
[color]="buttonColor()"
[shape]="buttonShape()"
[effect]="buttonEffect()"
[disabled]="buttonDisabled()"
[loading]="buttonLoading()"
[leftIcon]="leftIcon()"
[rightIcon]="rightIcon()"
[customStyles]="customButtonStyles()"
(click)="handleButtonClick($event)"
/>
3.4 Widget vs Embeddable Summary
| Aspect | Embeddable | Widget |
|---|---|---|
| Source of truth | YES | NO |
| Has property groups | NO | YES |
| Has variants | NO | YES |
| Used in modules | YES | Indirectly |
| Used in editor | Via widget | YES |
| Location | ui-core, ui-forms | libs-widgets |
4. Modules (Business Logic)
4.1 What is a Module?
A module is an NX library that:
- Implements
BrowserModuleContract - Owns business logic, actions, and state
- Exposes embeddables in its manifest
- Declares permissions and navigation
- Has lifecycle methods (configure, initialize, start, stop, destroy)
4.2 Real Example: UserManagementModule
Location: modules/new-user-management/src/lib/user-management.module.ts
import { Injectable, inject } from '@angular/core';
import { Observable, of } from 'rxjs';
import {
BrowserModuleContract,
ModuleManifest,
ModuleDecorator,
} from '@libs-core/module-base';
import { ActionParameterBuilder, ParameterBuilders } from '@libs-core/action-registry';
import { UserListWidgetComponent } from './embeddables';
import { CreateUserAction, UpdateUserAction, DeleteUserAction } from './actions';
@ModuleDecorator({
id: 'user-management',
version: '1.0.0',
name: 'User Management',
description: 'Comprehensive user management module',
permissions: [
{ id: 'users:read', name: 'users:read', resource: 'users', action: 'read' },
{ id: 'users:create', name: 'users:create', resource: 'users', action: 'create' },
{ id: 'users:update', name: 'users:update', resource: 'users', action: 'update' },
{ id: 'users:delete', name: 'users:delete', resource: 'users', action: 'delete' },
],
navigation: {
menuItems: [
{
id: 'users-main',
label: 'Users',
icon: 'users',
route: 'list',
permissions: ['users:read'],
children: [
{ id: 'users-list', label: 'All Users', route: 'list' },
{ id: 'users-create', label: 'Create User', route: 'create' },
],
},
],
},
})
@Injectable({ providedIn: 'root' })
export class UserManagementModule implements BrowserModuleContract {
readonly manifest: ModuleManifest = {
id: 'user-management',
version: '1.0.0',
name: 'User Management',
basePath: 'users',
// EMBEDDABLES: Components this module exposes
embeddableComponents: [
{
id: 'users:list-widget',
componentType: UserListWidgetComponent,
displayName: 'User List Widget',
description: 'Compact widget displaying a list of users',
category: 'widget',
permissions: ['users:read'],
inputs: [
ActionParameterBuilder.number('limit')
.description('Maximum number of users to display')
.defaultValue(5)
.min(1).max(50)
.build(),
ActionParameterBuilder.string('roleFilter')
.description('Filter users by role')
.build(),
ParameterBuilders.toggle('showAvatar', 'Show Avatar')
.defaultValue(true)
.build(),
],
},
],
// PUBLIC ACTIONS: Can be invoked by other modules
publicActions: [
CreateUserAction,
UpdateUserAction,
DeleteUserAction,
],
};
// Lifecycle methods
configure<T>(config: T): Observable<void> { return of(void 0); }
initialize(): Observable<void> { return of(void 0); }
start(): Observable<void> { return of(void 0); }
stop(): Observable<void> { return of(void 0); }
destroy(): Observable<void> { return of(void 0); }
}
4.3 Module Actions
Actions are the public API of a module. Other modules can invoke them via the Action Registry.
Location: modules/new-user-management/src/lib/actions/create-user.action.ts
import { Injectable, inject } from '@angular/core';
import { Observable, of, map, catchError } from 'rxjs';
import {
PublicAction,
ExecutableAction,
ActionContext,
ActionParameterBuilder,
ParameterBuilders,
} from '@libs-core/action-registry';
import { EventBusService } from '@libs-core/event-system';
import { UserService } from '../services/user.service';
export interface CreateUserInput {
email: string;
firstName: string;
lastName: string;
roleId: string;
tenantId: string;
}
export interface CreateUserOutput {
success: boolean;
userId?: string;
error?: string;
}
@PublicAction({
id: 'users:create-user',
name: 'Create User',
description: 'Create a new user with role assignment',
permissions: ['users:create'],
parameters: [
ParameterBuilders.email('email')
.label('Email Address')
.build(),
ActionParameterBuilder.string('firstName')
.required('First name is required')
.minLength(2).maxLength(50)
.build(),
ActionParameterBuilder.string('lastName')
.required('Last name is required')
.build(),
ActionParameterBuilder.string('roleId')
.required('Role is required')
.build(),
],
})
@Injectable({ providedIn: 'root' })
export class CreateUserAction implements ExecutableAction<CreateUserInput, CreateUserOutput> {
private readonly userService = inject(UserService);
private readonly eventBus = inject(EventBusService);
execute(params: CreateUserInput, context: ActionContext): Observable<CreateUserOutput> {
return this.userService.createUser({
email: params.email,
firstName: params.firstName,
lastName: params.lastName,
roleId: params.roleId,
}).pipe(
map(user => {
// Emit event for other modules to react
this.eventBus.emit('user:created', { userId: user.id, tenantId: params.tenantId });
return { success: true, userId: user.id };
}),
catchError(error => of({ success: false, error: error.message }))
);
}
}
4.4 Invoking Actions from Other Modules
// From another module or widget
const actionRegistry = inject(ActionRegistryService);
actionRegistry.execute('users:create-user', {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
roleId: 'role_123',
tenantId: context.tenantId
}).subscribe(result => {
if (result.success) {
console.log('User created:', result.userId);
}
});
5. Property System
5.1 How Properties Work
The property system provides:
- Property Definitions - Atomic properties (backgroundColor, fontSize, etc.)
- Property Templates - Groups of properties for widget types
- Property Applicators - Functions that apply properties to components
- Variants - Presets of property values
5.2 Using usePropertyStyles
The usePropertyStyles hook automatically applies property groups:
// Instead of 70+ lines of manual style checking:
customButtonStyles = usePropertyStyles(
this.widget, // Widget signal
this.activeBreakpoint, // Current breakpoint
['font', 'appearance'] // Property groups to apply
);
// Returns computed styles like:
// {
// 'font-size': '16px',
// 'font-weight': '600',
// 'background-color': '#3b82f6',
// 'border-radius': '8px'
// }
5.3 Backend Property Endpoints
Controller: apis/backend-api/src/app/properties/properties.controller.ts
| Endpoint | Method | Description |
|---|---|---|
/api/spaces/:spaceId/properties |
GET | List all properties |
/api/spaces/:spaceId/properties |
POST | Create property |
/api/spaces/:spaceId/properties/templates |
GET | List templates |
/api/spaces/:spaceId/properties/templates/ai-context |
GET | AI generation context |
/api/spaces/:spaceId/properties/core-sync |
GET | Flash persistence sync |
6. Backend API Structure
6.1 Widget Endpoints
Controller: apis/backend-api/src/app/widgets/widgets.controller.ts
// Widget DTO
interface CreateWidgetDto {
name: string;
type: string;
category: string;
description?: string;
variants?: Array<{
id: string;
name: string;
properties: Record<string, unknown>;
}>;
propertyConfig?: {
groups: Array<{
id: string;
title: string;
properties: Array<{ key: string; label: string; type: string; }>;
}>;
values: Record<string, unknown>;
};
}
| Endpoint | Method | Description |
|---|---|---|
/api/spaces/:spaceId/widgets |
GET | List all widgets |
/api/spaces/:spaceId/widgets |
POST | Create widget |
/api/spaces/:spaceId/widgets/:id |
GET | Get widget |
/api/spaces/:spaceId/widgets/:id |
PUT | Update widget |
/api/spaces/:spaceId/widgets/:id |
DELETE | Delete widget |
/api/spaces/:spaceId/widgets/:id/variants |
GET | Get variants |
/api/spaces/:spaceId/widgets/:id/variants |
POST | Create variant |
/api/spaces/:spaceId/widgets/:id/variants/:variantId |
PUT | Update variant |
/api/spaces/:spaceId/widgets/:id/embeddable |
GET | Get embeddable metadata |
6.2 Space Modules Endpoints
Controller: apis/backend-api/src/app/spaces/space-modules.controller.ts
| Endpoint | Method | Description |
|---|---|---|
/api/spaces/:spaceId/modules |
GET | List modules |
/api/spaces/:spaceId/modules |
POST | Create module |
/api/spaces/:spaceId/modules/batch |
POST | Batch upsert |
/api/spaces/:spaceId/modules/:id |
GET | Get module |
/api/spaces/:spaceId/modules/:id/activate |
PATCH | Activate |
/api/spaces/:spaceId/modules/:id/deactivate |
PATCH | Deactivate |
/api/spaces/:spaceId/modules/for-universe/:universeId |
GET | Get for universe |
6.3 Universe Deployment Endpoints
Controller: apis/backend-api/src/app/universes/universe-deployment.controller.ts
| Endpoint | Method | Description |
|---|---|---|
/api/universes/initialize |
POST | Initialize universe (create collections) |
/api/universes/:id/publish |
POST | Publish universe |
/api/universes/:id/unpublish |
POST | Unpublish universe |
/api/universes/:id/deployment-status |
GET | Get deployment status |
/api/universes/:id/verify-domain |
POST | Verify custom domain |
6.4 Singularity (Flows) Endpoints
Controller: apis/backend-api/src/app/singularity/singularity.controller.ts
| Endpoint | Method | Description |
|---|---|---|
/singularity/flows |
POST | Create flow |
/singularity/flows/:id |
GET | Get flow |
/singularity/flows/:id |
PUT | Update flow |
/singularity/flows/:id |
DELETE | Delete flow |
/singularity/flows/:id/execute |
POST | Execute flow |
/singularity/flows/:id/validate |
POST | Validate flow |
7. Routing Structure
7.1 Module Workbench Routes
Location: libs/libs-structural/modularity-lib/src/modularity.routes.ts
/modularity
├── / -> Dashboard (browse modules)
├── /workbench -> Module workbench
│ ├── /models -> Step 1: Define entities
│ ├── /state -> Step 2: Configure state
│ ├── /actions -> Step 3: Create actions
│ ├── /actions/:actionId -> Action flow builder
│ ├── /ui -> Step 4: Design embeddables
│ ├── /test -> Step 5: Test module
│ └── /deploy -> Step 6: Export/deploy
├── /workbench/:moduleId -> Edit existing module
├── /ai-builder -> AI module generation
└── /ai-scaffold -> Quick AI scaffold
7.2 Widget Builder Routes
Location: libs/libs-widgets/widget-builder/src/widget-builder.routes.ts
/widget-builder
├── / -> Builder workspace
├── /workspace -> AI/Templates/Components modes
├── /workspace/:mode -> Specific mode
├── /variant-editor/:widgetId -> Edit variants
├── /property-manager -> Manage properties
└── /property-manager/:id -> Edit property
7.3 Editor Routes
Location: libs/libs-structural/editor-web-lib/src/lib/editor.routes.ts
/editor
├── / -> Blank canvas
├── /new -> Create new page
├── /page/:pageSlug -> Edit page
├── /universe/:universeId/page/:pageSlug -> Edit in universe context
├── /module/:moduleId -> Build module content
├── /ai-assist -> AI page generation
└── /widget-builder -> Nested widget builder
7.4 Universe Canvas Routes
Location: libs/libs-structural/modular-universe/src/lib/modular-universe.routes.ts
/modular-universe
├── / -> Universe canvas builder
├── /universe/:universeId -> Edit specific universe
└── /galaxy/:galaxyId -> Edit galaxy
8. Current Gaps (Honest Assessment)
8.1 What Works
- Module contract (
BrowserModuleContract) and decorator - Module manifest with embeddables and actions
- Embeddable components with input/output contracts
- Widget wrappers with property applicators
- Backend CRUD for widgets, modules, properties
- Workbench 6-step flow (UI exists)
- Universe canvas for module placement
- Variant CRUD endpoints
8.2 What Needs Work
| Gap | Status | Description |
|---|---|---|
| End-to-end 0→100 build | Not demonstrated | No reference space built through entire pipeline |
| Deploy step implementation | Partial | UI exists, actual NX lib generation incomplete |
| Artifact bundling | Stub | deployment.service.ts is placeholder |
| Embeddable deduplication | Pending | ui-core vs widgets duplicates exist |
| Property migration | In progress | Some widgets still use manual styles |
| Variant persistence verification | Untested | CRUD exists but E2E not verified |
| Inter-module communication | Implicit | Event/action patterns not formalized |
8.3 Duplication Issues to Resolve
Components that exist in both ui-core AND widgets (need consolidation):
ui-button/button-widgetui-input/input-widgetui-card/card-widgetui-checkbox/checkbox-widgetui-select/select-widget
Rule: ui-core is source of truth; widgets WRAP, not re-implement.
9. How to Use This Architecture
9.1 Creating a New Module
Use
/modularity/workbenchto define:- Models (entities with fields)
- State (collections, storage)
- Actions (CRUD operations)
- UI (embeddables)
Export to
modules/folderRegister embeddables in manifest:
embeddableComponents: [
{
id: 'my-module:my-widget',
componentType: MyWidgetComponent,
inputs: [/* ... */]
}
]
- Wire to backend via
/api/spaces/:spaceId/modules
9.2 Creating a Widget from Embeddable
- Create wrapper component in
libs/libs-widgets/widgets/ - Import the embeddable from ui-core
- Add property groups using
usePropertyStyles - Register via backend widget endpoint
- Define variants
9.3 Composing Pages
- Open
/editor/page/:pageSlug - Drag widgets from library
- Configure properties in panel
- Bind events to module actions (via flow executor or direct)
- Save page
9.4 Deploying a Universe
- Open
/modular-universe/universe/:universeId - Place modules on canvas
- Configure behavior triggers
- Click Deploy → calls
/api/universes/:id/publish - Monitor status via
/api/universes/:id/deployment-status
10. Success Criteria
To declare the platform "ready for end-to-end generation":
- One reference space built 0→100 (module + embeddables + widgets + page + actions)
- All backend endpoints exercised and verified
- Property system fully applied (no manual styles)
- Variants persist and are selectable in editor
- Embeddable duplication resolved
- Deploy step generates actual NX library
- Universe deployment produces accessible URL
- E2E tests cover full path
Last Updated: December 9, 2025
Session ID: 10.2