Editor Save Architecture - Critical Fix Required
Editor is saving to wrong location - needs to save pages as modules in universe
🚨 Editor Save Architecture - Critical Issue
Date: November 24, 2025
Session: 11.25.1.1
Status: 🚨 CRITICAL - Requires Immediate Fix
🎯 The Problem
Current Behavior: Pages are saved to PagesService (IndexedDB only), but they never reach MongoDB
Expected Behavior (per BREAKTHROUGH architecture): Pages should be stored as modules inside the universe's modules array in MongoDB
📊 Current vs Expected Architecture
Current (WRONG ❌)
MongoDB Structure:
// universes collection
{
"_id": "universe_123",
"name": "My Store",
"galaxyIds": ["galaxy_1", "galaxy_2"], // ❌ Deprecated concept
"modules": [] // ❌ Empty or doesn't exist
}
Pages Storage:
// Saved to IndexedDB via PagesService
storage.put('pages', [
{ id: 'page-1', title: 'Home', widgets: [...] } // ❌ NOT in MongoDB!
]);
Expected (CORRECT ✅)
MongoDB Structure (per BREAKTHROUGH doc):
// universes collection
{
"_id": "universe_123",
"name": "My Store",
"modules": [ // ✅ Everything is a module!
{
"id": "mod-nav-1",
"type": "navigation",
"name": "Top Nav",
"position": "header"
},
{
"id": "mod-galaxy-1",
"type": "galaxy", // ✅ Galaxy is a module type
"name": "Products",
"route": "/products",
"children": [
{
"id": "mod-world-1",
"type": "world", // ✅ World is a module type
"name": "Electronics",
"route": "/products/electronics",
"children": [
{
"id": "mod-page-1",
"type": "page", // ✅ Page is a module!
"name": "Laptops",
"route": "/products/electronics/laptops",
"widgets": [...] // ✅ Widgets stored here
}
]
}
]
}
]
}
🔍 Root Cause Analysis
1. Outdated Type Definitions
Current Universe Type (in multiverse.types.ts):
export interface Universe {
id: string;
spaceId: string;
name: string;
slug: string;
galaxyIds: string[]; // ❌ Deprecated - should be modules array
// ...
}
Required Universe Type (per BREAKTHROUGH):
export interface Universe {
id: string;
name: string;
slug: string;
modules: UniversalModule[]; // ✅ Everything is modules!
settings: UniverseSettings;
}
2. Editor Save Action Using Wrong Service
Current (save-current-page.action.ts):
// ❌ Saves to PagesService (IndexedDB only, never reaches MongoDB)
await this.pagesService.createPage(pageToSave);
Required:
// ✅ Should save to SpaceService.updateUniverse()
// 1. Get current universe
const universe = await this.spaceService.getUniverse(universeId);
// 2. Add/update page as a module in universe.modules
const pageModule: UniversalModule = {
id: page.id,
type: 'page',
name: page.title,
widgets: page.widgets,
route: `/${page.slug}`,
// ...
};
if (!universe.modules) universe.modules = [];
// Upsert: update if exists, add if not
const index = universe.modules.findIndex(m => m.id === page.id);
if (index >= 0) {
universe.modules[index] = pageModule;
} else {
universe.modules.push(pageModule);
}
// 3. Save entire universe (pushes to MongoDB)
await this.spaceService.updateUniverse(universe.id, universe);
📝 Required Changes
Phase 1: Update Type Definitions
1.1 Update Universe Interface
File: libs/libs-foundational/core-models/src/lib/types/multiverse.types.ts
export interface Universe {
id: string;
spaceId: string;
name: string;
slug: string;
description?: string;
// ✅ NEW: Modules array (replaces galaxyIds)
modules?: UniversalModule[]; // All content is modules
// ❌ DEPRECATED: Remove galaxyIds
// galaxyIds: string[];
// Deployment info
deploymentStatus: UniverseDeploymentStatus;
deployedUrl?: string;
customDomain?: string;
lastDeployedAt?: Date | string;
// Settings
settings?: UniverseSettings;
// Metadata
icon?: string;
createdAt?: Date | string;
updatedAt?: Date | string;
metadata?: Record<string, any>;
}
1.2 Create UniverseModule Helper
File: libs/libs-foundational/core-models/src/lib/helpers/module-helpers.ts
import { UniversalModule } from '../types/universal-module.types';
import { World } from '../types/page.types';
/**
* Convert a Page (World) to a UniversalModule
*/
export function pageToModule(page: World): UniversalModule {
return {
id: page.id,
type: 'page',
name: page.title,
slug: page.slug,
description: page.description,
route: `/${page.slug}`,
widgets: page.widgets,
parentId: page.parentId,
isActive: true,
config: {
layout: page.layout,
meta: page.meta,
permissions: page.permissions,
status: page.status
},
metadata: {
version: page.version,
createdAt: page.createdAt,
updatedAt: page.updatedAt,
publishedAt: page.publishedAt
}
};
}
/**
* Convert a UniversalModule back to a Page (World)
*/
export function moduleToPage(module: UniversalModule): World {
return {
id: module.id,
title: module.name,
slug: module.slug,
description: module.description || '',
widgets: module.widgets || [],
layout: module.config?.layout || {},
meta: module.config?.meta || {},
permissions: module.config?.permissions || {},
status: module.config?.status || 'draft',
parentId: module.parentId,
version: module.metadata?.version || 1,
createdAt: module.metadata?.createdAt,
updatedAt: module.metadata?.updatedAt,
publishedAt: module.metadata?.publishedAt
};
}
/**
* Upsert a module in a universe's modules array
*/
export function upsertModuleInUniverse(
universe: Universe,
module: UniversalModule
): Universe {
if (!universe.modules) {
universe.modules = [];
}
const index = universe.modules.findIndex(m => m.id === module.id);
if (index >= 0) {
// Update existing module
universe.modules[index] = module;
} else {
// Add new module
universe.modules.push(module);
}
return {
...universe,
updatedAt: new Date().toISOString()
};
}
/**
* Remove a module from a universe's modules array
*/
export function removeModuleFromUniverse(
universe: Universe,
moduleId: string
): Universe {
if (!universe.modules) {
return universe;
}
return {
...universe,
modules: universe.modules.filter(m => m.id !== moduleId),
updatedAt: new Date().toISOString()
};
}
Phase 2: Update Editor Save Action
File: libs/libs-structural/editor-web-lib/src/lib/actions/save-current-page.action.ts
import { SpaceService } from '@libs-foundational/core-angular';
import { pageToModule, upsertModuleInUniverse } from '@libs-foundational/core-models';
import { UserContextService } from '@storage-system';
@PublicAction({
id: 'editor:save-current-page',
// ...
})
export class SaveCurrentPageAction implements BaseAction {
private readonly editorService = inject(EditorService);
private readonly spaceService = inject(SpaceService); // ✅ Use SpaceService
private readonly userContext = inject(UserContextService); // ✅ Get universe context
private readonly logger = inject(LoggerService);
execute(params: SaveCurrentPageInput, context: ActionExecutionContext): Observable<SaveCurrentPageOutput> {
// Get current page and widgets
const currentPage = this.editorService.page();
const currentWidgets = this.editorService.widgets();
if (!currentPage) {
return of({ success: false, error: 'No page loaded in editor' });
}
// Get universe context
const universeId = this.userContext.currentUniverseId();
if (!universeId) {
return of({ success: false, error: 'No universe context' });
}
// Update page with current widgets
const pageToSave = {
...currentPage,
widgets: currentWidgets,
updatedAt: new Date().toISOString()
};
// Convert page to module
const pageModule = pageToModule(pageToSave);
// ✅ NEW: Save to universe's modules array
return from(
(async () => {
// Get current universe
const universes = this.spaceService.universes();
let universe = universes.find(u => u.id === universeId);
if (!universe) {
throw new Error(`Universe not found: ${universeId}`);
}
// Upsert page module into universe
universe = upsertModuleInUniverse(universe, pageModule);
// Save universe (pushes to MongoDB via SpaceService)
await this.spaceService.updateUniverse(universe.id, universe);
this.logger.info('✅ Page saved as module in universe', {
pageId: pageModule.id,
universeId: universe.id,
moduleType: 'page'
});
return pageToSave;
})()
).pipe(
map((savedPage) => ({
success: true,
data: {
pageId: savedPage.id,
widgetCount: savedPage.widgets?.length || 0,
updatedAt: savedPage.updatedAt
}
})),
catchError((error) => {
this.logger.error('❌ Failed to save page as module', { error: error.message });
return of({
success: false,
error: error.message
});
})
);
}
}
Phase 3: Update Editor Workspace to Load from Universe
File: libs/libs-structural/editor-web-lib/src/lib/components/editor-workspace/editor-workspace.component.ts
private async loadPageById(pageId: string): Promise<void> {
this.isLoading.set(true);
try {
// ✅ NEW: Load page from universe's modules array
const universeId = this.userContext.currentUniverseId();
if (!universeId) {
throw new Error('No universe context');
}
const universes = this.spaceService.universes();
const universe = universes.find(u => u.id === universeId);
if (!universe) {
throw new Error(`Universe not found: ${universeId}`);
}
// Find page module in universe
const pageModule = universe.modules?.find(m =>
m.id === pageId && m.type === 'page'
);
if (!pageModule) {
throw new Error(`Page module not found: ${pageId}`);
}
// Convert module back to page format
const page = moduleToPage(pageModule);
// Set in editor
this.editorService.setPage(page);
this.editorService.setWidgets(page.widgets || []);
this.loadedPage.set(page);
this.logger.info('✅ Page loaded from universe modules', {
pageId,
universeId,
widgetCount: page.widgets?.length || 0
});
} catch (error) {
this.logger.error('❌ Failed to load page', { error });
} finally {
this.isLoading.set(false);
}
}
🎯 Migration Strategy
Option 1: Big Bang Migration (Recommended for Development)
- Update Universe type to include
modulesarray - Create migration script to convert existing pages to modules
- Update save action to save to universe.modules
- Update load logic to read from universe.modules
- Deprecate PagesService (or repurpose as read-only cache)
Option 2: Gradual Migration (Production-Safe)
- Dual-write: Save to both PagesService AND universe.modules
- Gradual read migration: Try universe.modules first, fall back to PagesService
- Monitor and verify data consistency
- Eventually remove PagesService writes
✅ Success Criteria
After fixes are complete:
- Pages appear in MongoDB
universescollection undermodulesarray - Pages have
type: 'page'in the universe - Widgets are stored within the page module
- Editor can load pages from universe.modules
- Editor can save pages to universe.modules
- No more saves to separate
pagescollection/key - Galaxies and Worlds also stored as modules (optional phase 2)
🚨 Impact
HIGH PRIORITY - This affects:
- ✅ Data persistence (pages not reaching database)
- ✅ Editor save functionality
- ✅ Module architecture alignment
- ✅ MongoDB data structure
- ✅ Deployment (pages must be in universe to deploy)
📚 Related Documentation
- BREAKTHROUGH: Modules Are Structure - Core architecture
- Editor Modules Alignment - Route params cleanup
- Session Summary: Editor Save Complete - Previous session
Created: November 24, 2025 - 5:30 PM
Session: 11.25.1.1
Status: 🚨 CRITICAL - Awaiting Implementation
Priority: P0 - Must Fix Before Production