Page Module Architecture

Clean separation between platform structure (modules) and visual content (pages)

Page Module Architecture

🎯 Problem Statement

Previously, UniversalModule tried to do too much:

This mixing caused confusion and made it hard to:

✅ Solution: Clean Separation

The Two Domains

┌────────────────────────────────────┬────────────────────────────────────┐
│   UniversalModule                  │   Page (editor-web-lib)            │
│   (Platform Structure)             │   (Visual Content)                 │
├────────────────────────────────────┼────────────────────────────────────┤
│   WHERE things go:                 │   WHAT things look like:           │
│   • route                          │   • widgets: Widget[]              │
│   • navigation config              │   • layout: ResponsiveLayout       │
│   • permissions                    │   • meta: PageMeta (SEO)           │
│   • module hierarchy               │   • devicePreferences              │
│   • behavior (flows)               │   • draftInfo, templates           │
│   • slots for customization        │   • status (draft/published)       │
└────────────────────────────────────┴────────────────────────────────────┘

The Bridge: page-module Type

// UniversalModule with type: 'page-module'
const pageModule: UniversalModule = {
  id: 'products-page',
  name: 'Products Page',
  slug: 'products',
  type: 'page-module',  // 🆕 The new type!
  route: '/products',
  
  // PageModuleConfig bridges to the actual Page content
  config: {
    pageId: 'page_12345',  // Reference to Page from editor-web-lib
    navigation: {
      isNavigable: true,
      navLabel: 'Products',
      navIcon: 'fa-shopping-cart',
      navOrder: 2,
      showBreadcrumbs: true
    },
    slots: [
      { id: 'breadcrumb', position: 'before-content', isVisible: true }
    ],
    flowTriggers: {
      'onLoad': 'flow_load_products'
    }
  } as PageModuleConfig
};

📦 Key Interfaces

PageModuleConfig

interface PageModuleConfig extends ModuleConfig {
  // Content reference
  pageId: string;                    // Reference to Page from editor-web-lib
  
  // Routing
  routePath?: string;                // URL route path
  routeParams?: Record<string, string>;
  routeGuards?: string[];
  redirectTo?: string;
  
  // Navigation
  navigation: PageNavigationConfig;
  
  // Customization
  slots?: PageSlot[];                // Add modules around page content
  
  // Behavior
  flowTriggers?: Record<string, string>;
  flowParams?: Record<string, Record<string, string>>;
  
  // Layout override
  layoutOverride?: 'default' | 'fullscreen' | 'sidebar' | 'modal';
  
  // Permissions
  requiredPermissions?: string[];
}

PageNavigationConfig

interface PageNavigationConfig {
  isNavigable: boolean;              // Show in navigation menus
  navLabel?: string;                 // Label (defaults to page title)
  navIcon?: string;                  // FontAwesome icon
  navOrder?: number;                 // Order in menu
  navGroup?: string;                 // Group/section
  showBreadcrumbs?: boolean;
}

PageSlot

interface PageSlot {
  id: string;
  position: 'before-content' | 'after-content' | 'header' | 'footer' | 'sidebar-left' | 'sidebar-right';
  moduleId?: string;                 // Module to render in this slot
  isVisible: boolean;
  className?: string;
}

🔄 Migration Guide

Before (Deprecated)

// ❌ OLD WAY - widgets/layout directly on module
const oldModule: UniversalModule = {
  type: 'page',
  widgets: [...],        // Mixed content with structure!
  layout: {...},         // Mixed content with structure!
  route: '/products'
};

After (New Way)

// ✅ NEW WAY - Clean separation
// 1. Page (in editor-web-lib) - content only
const page: Page = {
  id: 'page_12345',
  title: 'Products',
  widgets: [...],
  layout: {...},
  meta: { title: 'Products | My Store', ... }
};

// 2. PageModule (in modular-universe) - structure only
const pageModule: UniversalModule = {
  type: 'page-module',
  route: '/products',
  config: {
    pageId: 'page_12345',
    navigation: { isNavigable: true, navLabel: 'Products' }
  } as PageModuleConfig
};

🎯 Benefits

  1. Reusability: Same page content can be used in multiple platform contexts
  2. Clarity: Clear what belongs where (structure vs content)
  3. Flexibility: Change navigation/routing without touching page content
  4. Customization: Add slots for breadcrumbs, headers, sidebars without modifying page
  5. AI-Friendly: Clear boundaries make AI training easier

📝 Deprecated Types

type: 'page' - DEPRECATED

The old page type mixed structure with content. Use page-module instead.

// ❌ Deprecated
type: 'page'

// ✅ Use instead
type: 'page-module'

widgets, layout, sections on UniversalModule - DEPRECATED

These fields on UniversalModule are deprecated. Content should be in the Page model.

// ❌ Deprecated - don't use these on UniversalModule
widgets?: any[];
layout?: any;
sections?: any[];

// ✅ Use PageModuleConfig.pageId to reference Page content
config: {
  pageId: 'page_12345'
} as PageModuleConfig

🔗 Related Files


Last Updated: November 26, 2025 Author: Architecture Team