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)

  1. Update Universe type to include modules array
  2. Create migration script to convert existing pages to modules
  3. Update save action to save to universe.modules
  4. Update load logic to read from universe.modules
  5. Deprecate PagesService (or repurpose as read-only cache)

Option 2: Gradual Migration (Production-Safe)

  1. Dual-write: Save to both PagesService AND universe.modules
  2. Gradual read migration: Try universe.modules first, fall back to PagesService
  3. Monitor and verify data consistency
  4. Eventually remove PagesService writes

✅ Success Criteria

After fixes are complete:


🚨 Impact

HIGH PRIORITY - This affects:


📚 Related Documentation


Created: November 24, 2025 - 5:30 PM
Session: 11.25.1.1
Status: 🚨 CRITICAL - Awaiting Implementation
Priority: P0 - Must Fix Before Production