Next Steps - Actionable Implementation Guide
Concrete ordered tasks to complete platform generation with code patterns and file references
Next Steps - Actionable Implementation Guide
This document provides the ordered action items to complete platform generation. For architecture reference, see system-analysis.md.
Priority 1: Complete Deploy Step (Blocks Everything)
Problem
The deploy step UI exists but doesn't actually generate NX libraries.
Files to Modify
1. Deploy Step Component
libs/libs-structural/modularity-lib/src/lib/components/steps/deploy-step/deploy-step.component.ts
// Add actual export call in startExport()
async startExport(): Promise<void> {
this.isExporting.set(true);
// Get module context
const ctx = this.context();
const entities = this.entities();
const actions = this.actions();
const embeddables = this.embeddables();
// Call export service (THIS IS MISSING)
const result = await this.moduleExportService.exportModule({
moduleName: ctx.moduleName,
entities,
actions,
embeddables,
targetPath: `modules/${this.modulePath()}`
});
this.exportComplete.set(true);
this.moduleExported.emit();
}
2. Module Export Service
libs/libs-structural/modularity-lib/src/lib/services/module-export.service.ts
// Implement actual file generation
async exportModule(config: ExportConfig): Promise<ExportResult> {
// 1. Generate TypeScript files
const modelFiles = this.generateModels(config.entities);
const actionFiles = this.generateActions(config.actions);
const componentFiles = this.generateComponents(config.embeddables);
// 2. Generate project.json
const projectJson = this.generateProjectJson(config.moduleName);
// 3. Generate tsconfig.lib.json (NO rootDir!)
const tsconfig = this.generateTsConfig();
// 4. Generate index.ts barrel
const indexTs = this.generateBarrel(config);
// 5. Write files to disk via backend
await this.writeFiles(config.targetPath, {
...modelFiles,
...actionFiles,
...componentFiles,
'project.json': projectJson,
'tsconfig.lib.json': tsconfig,
'src/index.ts': indexTs
});
// 6. Update tsconfig.base.json path mapping
await this.updatePathMapping(config.moduleName, config.targetPath);
return { success: true, path: config.targetPath };
}
Verification
# After implementing, run:
nx build <module-name>
# Should succeed without errors
Priority 2: Wire Action Flows to Singularity
Problem
Action builder creates flow definitions but doesn't persist them to Singularity backend.
Files to Modify
Action Flow Storage Service
libs/libs-structural/modularity-lib/src/lib/services/action-flow-storage.service.ts
// Add Singularity persistence
async saveFlow(actionId: string, flow: ActionFlow): Promise<void> {
// 1. Save locally (existing)
this.localFlows.set(actionId, flow);
// 2. NEW: Persist to Singularity backend
await firstValueFrom(
this.http.post('/api/singularity/flows', {
name: flow.name,
description: flow.description,
blocks: flow.blocks,
connections: flow.connections,
category: 'module-action',
universeId: this.contextService.currentUniverseId()
})
);
}
Backend Endpoint (already exists)
POST /singularity/flows in apis/backend-api/src/app/singularity/singularity.controller.ts
Priority 3: Implement Deployment Service
Problem
deployment.service.ts returns stub data; no actual deployment happens.
File to Implement
apis/backend-api/src/app/deployment/deployment.service.ts
@Injectable()
export class DeploymentService {
constructor(
private readonly spaceDatabaseService: SpaceDatabaseService,
private readonly configService: ConfigService,
) {}
async deployUniverse(
tenantId: string,
universeId: string,
config: DeploymentConfig
): Promise<DeploymentResult> {
const deploymentId = `deploy_${Date.now()}`;
// 1. Fetch all universe data
const pages = await this.fetchPages(tenantId, universeId);
const modules = await this.fetchModules(tenantId, universeId);
const widgets = await this.fetchWidgets(tenantId);
const assets = await this.fetchAssets(tenantId, universeId);
// 2. Bundle into deployable artifact
const bundle = {
manifest: { universeId, version: Date.now(), pages: pages.map(p => p.id) },
pages: pages,
modules: modules,
widgets: widgets,
assets: assets
};
// 3. Generate static output or serverless bundle
const outputPath = await this.generateOutput(bundle, config);
// 4. Upload to hosting provider
const url = await this.uploadToHost(outputPath, config);
// 5. Store deployment record
await this.storeDeploymentRecord(tenantId, deploymentId, {
universeId,
url,
status: 'deployed',
config
});
return {
success: true,
deploymentId,
url,
subdomain: this.generateSubdomain(tenantId, universeId),
status: 'deployed'
};
}
private async uploadToHost(path: string, config: DeploymentConfig): Promise<string> {
// Integration with Railway/Vercel/custom host
// TODO: Implement based on hosting choice
}
}
Priority 4: Audit Property Migration
Task
Find widgets still using manual styles instead of usePropertyStyles.
Command
# Find manual style patterns
grep -rn "ngStyle\|this\.styles\s*=\|get customStyles" \
libs/libs-widgets/widgets/src/lib/components/ \
--include="*.ts"
Expected Output
Widgets should ONLY have:
customStyles = usePropertyStyles(this.widget, this.activeBreakpoint, ['font', 'appearance']);
Conversion Pattern
// BEFORE (70+ lines)
get customStyles(): Record<string, string> {
const props = this.widget()?.properties ?? {};
return {
fontSize: props.fontSize ? `${props.fontSize}px` : undefined,
fontWeight: props.fontWeight ?? undefined,
color: props.textColor ?? undefined,
backgroundColor: props.backgroundColor ?? undefined,
// ... 60 more lines
};
}
// AFTER (1 line)
customStyles = usePropertyStyles(this.widget, this.activeBreakpoint, ['font', 'appearance']);
Priority 5: Verify Embeddable Wrapping
Task
Ensure all 5 widget pairs properly wrap (not re-implement) their embeddables.
Checklist
| Widget | Import Check | Template Check |
|---|---|---|
ButtonWidgetComponent |
imports: [UiButtonComponent] |
<ui-button .../> |
InputWidgetComponent |
imports: [UiInputComponent] |
<ui-input .../> |
CardWidgetComponent |
imports: [UiCardComponent] |
<ui-card .../> |
CheckboxWidgetComponent |
imports: [UiCheckboxComponent] |
<ui-checkbox .../> |
SelectWidgetComponent |
imports: [UiSelectComponent] |
<ui-select .../> |
Verification Command
# For each widget, check imports
grep -A 10 "@Component" libs/libs-widgets/widgets/src/lib/components/button-widget/button-widget.component.ts | grep "imports"
Priority 6: Variant CRUD E2E Test
Task
Verify variant lifecycle works end-to-end.
Test File
apps/gograb-space-e2e/src/variant-crud.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Variant CRUD', () => {
const API = 'http://localhost:3000/api';
const SPACE_ID = 'test-space';
let widgetId: string;
test.beforeAll(async ({ request }) => {
// Create test widget
const res = await request.post(`${API}/spaces/${SPACE_ID}/widgets`, {
data: {
name: 'Test Button',
type: 'button',
category: 'ui',
variants: []
}
});
widgetId = (await res.json()).data.id;
});
test('create variant', async ({ request }) => {
const res = await request.post(`${API}/spaces/${SPACE_ID}/widgets/${widgetId}/variants`, {
data: {
name: 'Primary Blue',
properties: { backgroundColor: '#3b82f6', color: '#ffffff' }
}
});
expect(res.ok()).toBeTruthy();
const data = await res.json();
expect(data.data.variants).toContainEqual(
expect.objectContaining({ name: 'Primary Blue' })
);
});
test('list variants', async ({ request }) => {
const res = await request.get(`${API}/spaces/${SPACE_ID}/widgets/${widgetId}/variants`);
expect(res.ok()).toBeTruthy();
const data = await res.json();
expect(data.data.length).toBeGreaterThan(0);
});
test('update variant', async ({ request }) => {
// Get current variants
const list = await request.get(`${API}/spaces/${SPACE_ID}/widgets/${widgetId}/variants`);
const variants = (await list.json()).data;
const variantId = variants[0].id;
// Update
const res = await request.put(
`${API}/spaces/${SPACE_ID}/widgets/${widgetId}/variants/${variantId}`,
{ data: { name: 'Updated Blue' } }
);
expect(res.ok()).toBeTruthy();
});
});
Priority 7: Build Reference Space
Goal
Build a "Contact Form" space from scratch using all tools.
Steps
Module Workbench (
/modularity/workbench)- Models: Contact { email, message, createdAt }
- State: contacts collection
- Actions: CreateContact, ListContacts
- UI: ContactFormEmbeddable, ContactListEmbeddable
- Deploy to
modules/contact-form/
Backend Registration
curl -X POST http://localhost:3000/api/spaces/test/modules \ -H "Content-Type: application/json" \ -d '{"name":"Contact Form","type":"module","scope":"shared","isActive":true}'Widget Creation (
/widget-builder/workspace)- Create ContactFormWidget wrapping ContactFormEmbeddable
- Add appearance property group
- Create 2 variants: "Light" and "Dark"
Page Composition (
/editor/page/contact-us)- Drag ContactFormWidget
- Configure properties
- Bind submit to CreateContact action
Universe Deployment (
/modular-universe/universe/contact-space)- Place Contact Form module
- Configure deployment settings
- Deploy
Verify
- Visit deployed URL
- Submit form
- Check data persisted
Success Checklist
- Deploy step generates actual NX library files
- Action flows persist to Singularity backend
- Deployment service bundles and hosts universes
- All widgets use
usePropertyStyles(no manual styles) - All 5 widget pairs wrap their embeddables
- Variant CRUD E2E test passes
- Reference space built and deployed
- Deployed URL accessible and functional
Timeline Estimate
| Priority | Task | Estimate |
|---|---|---|
| P1 | Deploy step implementation | 4-6 hours |
| P2 | Singularity flow wiring | 2-3 hours |
| P3 | Deployment service | 6-8 hours |
| P4 | Property migration audit | 2-3 hours |
| P5 | Embeddable wrapping verification | 1-2 hours |
| P6 | E2E tests | 3-4 hours |
| P7 | Reference space build | 4-6 hours |
| Total | 22-32 hours |
Related Documentation:
- System Analysis - Complete architecture reference
- Index - Documentation navigation
Last Updated: December 9, 2025 - Session 10.2