SESSION EDITOR SAVE COMPLETE

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Session Summary: Editor Save & Auto-Create Page Implementation

Date: 2025-11-24 Session Focus: Fix editor save functionality and auto-create pages Status: ✅ COMPLETED


🎯 Problems Solved

1. Action Registration Not Working

Problem: editor:save-current-page action was not found in registry

Root Cause: The @PublicAction decorator only stores metadata via Reflect.defineMetadata() - it doesn't actually register the action with ActionRegistryService.

Solution: Manually register all editor actions in app.config.ts during APP_INITIALIZER:

// apps/gograb-space/src/app/app.config.ts
const savePageAction = inject(SaveCurrentPageAction);
// ... other actions

actionRegistry.register({
  id: 'editor:save-current-page',
  actionClass: SaveCurrentPageAction,
  metadata: {
    name: 'Save Current Page',
    description: 'Save the current page being edited',
    type: 'service',              // ✅ Required
    tags: ['editor', 'crud', 'save'], // ✅ Required
    timeout: 30000,               // ✅ Required
    retryable: true,              // ✅ Required
    maxRetries: 2,                // ✅ Required
    requiredPermissions: ['editor:write']
  }
});

All 6 Editor Actions Registered:


2. EventBus Tenant Isolation Errors

Problem: Error: Tenant ID es requerido cuando tenant isolation está habilitado

Root Cause:

Solution: Made EventBus publishing conditional on tenant context

Fixed in ActionRegistryService:

// libs/core/action-registry/src/lib/core/action-registry.service.ts
const currentContext = this.contextService.currentContext();
const event = new ActionRegisteredEvent(fullMetadata, currentContext?.tenantId);

this.actionRegisteredSubject.next(event);

// Only publish to EventBus if we have a tenant context
if (currentContext?.tenantId) {
  this.eventBus.publish(event).subscribe({
    error: (err) => {
      this.logger.warn(`Failed to publish ActionRegistered event for '${id}': ${err.message}`);
    }
  });
}

Fixed in ActionHandlerService:

// libs/core/action-registry/src/lib/core/action-handler.service.ts

// For success events
const currentContext = this.contextService.currentContext();
const successEvent = new ActionExecutedEvent({
  actionId,
  executionId: execContext.executionId,
  result: successResult,
  duration: executionTime,
}, currentContext?.tenantId);

if (currentContext?.tenantId) {
  this.eventBus.publish(successEvent).subscribe({
    error: (err) => {
      this.logger.warn(`Failed to publish ActionExecuted event for '${actionId}': ${err.message}`);
    }
  });
}

// Same pattern for error events...

Benefits:


3. Page Not Found Error

Problem:

[PagesService] Page not found:
❌ Save current page action failed
└─ error: "Failed to update page: page-1762381516166-2m4fqy0or"

Root Cause: User navigated to /space/chrome-space-1/editor without a pageId in the route. The editor tried to save a page that didn't exist yet.

Solution: Auto-create a new page when navigating to /editor without a pageId

Implementation in EditorWorkspaceComponent:

// libs/libs-structural/editor-web-lib/src/lib/components/editor-workspace/editor-workspace.component.ts

ngOnInit() {
  // Read page ID from route params
  if (params['pageId']) {
    this.pageId.set(params['pageId']);
    console.log('🎨 Editor Workspace: Loading page:', params['pageId']);
  } else {
    // No pageId provided - auto-create a new page
    console.log('🎨 Editor Workspace: No pageId in route, auto-creating new page...');
    this.createNewPageAndNavigate();
  }
}

/**
 * Auto-create a new page when navigating to /editor without a pageId
 */
private async createNewPageAndNavigate(): Promise<void> {
  try {
    console.log('📝 Creating new page automatically...');
    
    // Create a new page with default values
    const newPage = await this.pageService.createPage({
      title: 'Untitled Page',
      description: 'A new page created in the editor',
      status: 'draft',
      widgets: [],
      universeId: this.universeId() || undefined,
    });

    console.log('✅ New page created:', newPage.id);

    // Navigate to the editor with the new page's ID
    const spaceSlug = this.route.parent?.snapshot.params['slug'] || this.route.snapshot.params['slug'];
    
    if (spaceSlug) {
      // Navigate to /space/:slug/editor/page/:pageId
      await this.router.navigate(['/space', spaceSlug, 'editor', 'page', newPage.id], {
        replaceUrl: true // Replace current history entry
      });
    } else {
      // Fallback: Just set the pageId signal to trigger loading
      this.pageId.set(newPage.id);
    }
  } catch (error) {
    console.error('❌ Failed to create new page:', error);
    // Don't crash - just stay on blank editor
  }
}

Editor Routes (already existed):

// libs/libs-structural/editor-web-lib/src/lib/editor.routes.ts
export const EditorRoutes: Routes = [
  {
    path: '',                    // → /editor (blank, now auto-creates)
    loadComponent: () => import('./components/editor-workspace/editor-workspace.component')
  },
  {
    path: 'page/:pageId',        // → /editor/page/:pageId
    loadComponent: () => import('./components/editor-workspace/editor-workspace.component')
  },
  {
    path: 'universe/:universeId/page/:pageId',  // → /editor/universe/:id/page/:id
    loadComponent: () => import('./components/editor-workspace/editor-workspace.component')
  }
]

🚀 User Workflow Now

  1. User navigates to /space/chrome-space-1/editor
  2. System auto-creates a new draft page: { title: 'Untitled Page', status: 'draft', widgets: [] }
  3. System auto-navigates to /space/chrome-space-1/editor/page/{newPageId}
  4. Editor loads the new page via EditorWorkspaceComponent.loadPageById()
  5. User edits the page (add widgets, change layout, etc.)
  6. User clicks Save → SaveCurrentPageAction executes
  7. Page persists to GgStorage via PagesService.updatePage()
  8. Success! ✅

📦 Files Modified

  1. apps/gograb-space/src/app/app.config.ts

    • Imported ActionRegistryService
    • Manually registered all 6 editor actions with complete metadata
  2. libs/core/action-registry/src/lib/core/action-registry.service.ts

    • Made EventBus.publish() conditional on tenantId availability
    • Added error handling for EventBus failures
  3. libs/core/action-registry/src/lib/core/action-handler.service.ts

    • Made EventBus.publish() conditional for both success and error events
    • Added tenantId to ActionExecutedEvent and ActionExecutionFailedEvent
  4. libs/libs-structural/editor-web-lib/src/lib/components/editor-workspace/editor-workspace.component.ts

    • Added createNewPageAndNavigate() method
    • Modified ngOnInit() to auto-create page when pageId is missing
    • Fixed navigation path to use /editor/page/:pageId

✅ Completed TODOs

  1. ✅ Fix Configuration tab not showing for Page Renderer
  2. ✅ Unify pages storage structure (PagesService + EditorModule)
  3. ✅ Implement page routing in editor (/editor/:pageId)
  4. ✅ Test end-to-end page creation and rendering
  5. ✅ Fix editor action registration (save-current-page)
  6. ✅ Fix EventBus tenant isolation errors

⏳ Pending TODOs

  1. ⏳ Add draft system to editor with auto-save
  2. ⏳ Create pages collection structure in MongoDB

🎯 Next Steps

Immediate Priority: Draft System

Data Architecture: MongoDB Pages Collection


🧪 Testing Checklist


📝 Architecture Notes

Why Manual Action Registration?

The @PublicAction decorator was designed to mark a class as a public action, but it doesn't automatically register it. This is by design:

  1. Dependency Injection: Actions need to be injected to be instantiated
  2. Module Context: Actions should be registered as part of their parent module
  3. Lifecycle Control: App developers control when/how actions are registered

Pattern:

// Decorator stores metadata
@PublicAction({ id: 'my-action', ... })
export class MyAction implements BaseAction { }

// Bootstrap explicitly registers
const myAction = inject(MyAction);
actionRegistry.register({ id: 'my-action', actionClass: MyAction, metadata: {...} });

EventBus Tenant Isolation Strategy

Problem: EventBus enforces tenant isolation for security, but some events fire during bootstrap (before login).

Solution: Conditional publishing based on tenant context availability.

Benefits:


Session End: All critical issues resolved! Editor save functionality is now fully operational. 🎉