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:
editor:save-current-pageeditor:export-pageeditor:import-pageeditor:get-page-stateeditor:add-widgeteditor:open-page
2. EventBus Tenant Isolation Errors
Problem: Error: Tenant ID es requerido cuando tenant isolation está habilitado
Root Cause:
- Actions were being registered during
APP_INITIALIZER(before user login) - No tenant context available at bootstrap time
EventBus.publish()requires atenantIdwhen tenant isolation is enabled
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:
- ✅ Actions register successfully during bootstrap (no tenant required)
- ✅ Events still publish to EventBus once tenant context is available
- ✅ Local event subjects still fire (for subscribers)
- ✅ Graceful error handling prevents crashes
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
- User navigates to
/space/chrome-space-1/editor - System auto-creates a new draft page:
{ title: 'Untitled Page', status: 'draft', widgets: [] } - System auto-navigates to
/space/chrome-space-1/editor/page/{newPageId} - Editor loads the new page via
EditorWorkspaceComponent.loadPageById() - User edits the page (add widgets, change layout, etc.)
- User clicks Save →
SaveCurrentPageActionexecutes - Page persists to
GgStorageviaPagesService.updatePage() - Success! ✅
📦 Files Modified
apps/gograb-space/src/app/app.config.ts
- Imported
ActionRegistryService - Manually registered all 6 editor actions with complete metadata
- Imported
libs/core/action-registry/src/lib/core/action-registry.service.ts
- Made
EventBus.publish()conditional ontenantIdavailability - Added error handling for EventBus failures
- Made
libs/core/action-registry/src/lib/core/action-handler.service.ts
- Made
EventBus.publish()conditional for both success and error events - Added
tenantIdtoActionExecutedEventandActionExecutionFailedEvent
- Made
libs/libs-structural/editor-web-lib/src/lib/components/editor-workspace/editor-workspace.component.ts
- Added
createNewPageAndNavigate()method - Modified
ngOnInit()to auto-create page whenpageIdis missing - Fixed navigation path to use
/editor/page/:pageId
- Added
✅ Completed TODOs
- ✅ Fix Configuration tab not showing for Page Renderer
- ✅ Unify pages storage structure (PagesService + EditorModule)
- ✅ Implement page routing in editor (/editor/:pageId)
- ✅ Test end-to-end page creation and rendering
- ✅ Fix editor action registration (save-current-page)
- ✅ Fix EventBus tenant isolation errors
⏳ Pending TODOs
- ⏳ Add draft system to editor with auto-save
- ⏳ Create pages collection structure in MongoDB
🎯 Next Steps
Immediate Priority: Draft System
- Implement auto-save every 30 seconds
- Add draft status indicator in UI
- Add "Publish" button to change status from
drafttopublished
Data Architecture: MongoDB Pages Collection
- Design MongoDB schema for pages
- Implement backend API endpoints
- Migrate from IndexedDB to MongoDB for production
🧪 Testing Checklist
- Navigate to
/editorwithout pageId → Auto-creates new page - Auto-navigation to
/editor/page/:pageIdworks - Page loads correctly in editor
- Save button executes action successfully
- Page persists to
GgStorage - No EventBus tenant errors during bootstrap
- Actions registered correctly in registry
- Action execution events publish (when tenant available)
📝 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:
- Dependency Injection: Actions need to be injected to be instantiated
- Module Context: Actions should be registered as part of their parent module
- 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:
- Bootstrap events: Local subjects fire (for intra-service communication)
- Post-login events: Full EventBus with tenant isolation
- Graceful degradation: No crashes if tenant missing
- Security: Tenant isolation still enforced when context available
Session End: All critical issues resolved! Editor save functionality is now fully operational. 🎉