16 KiB
Generic Page System - Reducing Hardcoded TSX Dependency
Overview
MetaBuilder now features a Generic Page System that allows Level 1-3 (Homepage, User Area, Admin Panel) to be defined declaratively using JSON configuration and Lua scripts, dramatically reducing dependence on hardcoded TSX files.
Key Improvements
Before (Iteration 1-23)
- ❌ Level1.tsx, Level2.tsx, Level3.tsx were hardcoded TSX files
- ❌ Each level had fixed UI structure
- ❌ Changes required code modification
- ❌ IRC was the only declarative component
After (Iteration 24+)
- ✅ PageDefinition system allows declarative page configuration
- ✅ GenericPage component renders any page from JSON
- ✅ PageRenderer manages page loading, permissions, and Lua execution
- ✅ PageDefinitionBuilder provides default pages
- ✅ Levels 1-3 can be fully customized from Level 4/5 without code changes
Architecture
┌─────────────────────────────────────────────────────────────┐
│ PAGE DEFINITION (JSON) │
│ - Components tree │
│ - Layout configuration │
│ - Permission rules │
│ - Lua script hooks │
│ - Metadata (header, sidebar, footer) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ PAGE RENDERER │
│ - Loads page from database │
│ - Checks permissions │
│ - Executes onLoad/onUnload Lua scripts │
│ - Provides context to components │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ GENERIC PAGE (React) │
│ - Renders layout (default, sidebar, dashboard, blank) │
│ - Renders header/footer based on metadata │
│ - Renders component tree using RenderComponent │
│ - Handles navigation and logout │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ RENDER COMPONENT │
│ - Recursively renders component tree │
│ - Supports shadcn components │
│ - Supports declarative components (IRC, Forum, etc.) │
│ - Executes Lua handlers for interactions │
└─────────────────────────────────────────────────────────────┘
Core Files
1. /src/lib/page-renderer.ts
PageRenderer class that manages pages:
registerPage(page)- Register a page definitionloadPages()- Load pages from databasegetPage(id)- Get page by IDgetPagesByLevel(level)- Get all pages for a levelcheckPermissions(page, user)- Verify user can access pageexecuteLuaScript(scriptId, context)- Run Lua scriptonPageLoad(page, context)- Lifecycle hookonPageUnload(page, context)- Lifecycle hook
2. /src/lib/page-definition-builder.ts
PageDefinitionBuilder creates default pages:
initializeDefaultPages()- Creates Level 1-3 default pagesbuildLevel1Homepage()- Public homepage with hero + featuresbuildLevel2UserDashboard()- User dashboard with profile + commentsbuildLevel3AdminPanel()- Admin panel with user/content managementgetPages()- Returns all built pages
3. /src/components/GenericPage.tsx
GenericPage React component that renders any page:
- Loads page definition from PageRenderer
- Checks permissions
- Renders appropriate layout
- Executes Lua lifecycle hooks
- Handles navigation and user actions
PageDefinition Structure
interface PageDefinition {
id: string // Unique page identifier
level: 1 | 2 | 3 | 4 | 5 // Application level
title: string // Page title
description?: string // Optional description
layout: 'default' | 'sidebar' | 'dashboard' | 'blank' // Layout type
components: ComponentInstance[] // Component tree
luaScripts?: {
onLoad?: string // Script ID to run on page load
onUnload?: string // Script ID to run on page unload
handlers?: Record<string, string> // Event handlers (Lua script IDs)
}
permissions?: {
requiresAuth: boolean // Requires authentication?
requiredRole?: string // Minimum role required
customCheck?: string // Custom Lua permission check
}
metadata?: {
showHeader?: boolean // Show header?
showFooter?: boolean // Show footer?
headerTitle?: string // Header title
headerActions?: ComponentInstance[] // Header buttons/actions
sidebarItems?: Array<{ // Sidebar navigation items
id: string
label: string
icon: string
action: 'navigate' | 'lua' | 'external'
target: string
}>
}
}
Layout Types
1. Default Layout
Standard page with header, content area, and footer:
┌────────────────────────────────────┐
│ Header │
├────────────────────────────────────┤
│ │
│ Content │
│ │
├────────────────────────────────────┤
│ Footer │
└────────────────────────────────────┘
2. Sidebar Layout
Page with persistent sidebar navigation:
┌────────────────────────────────────┐
│ Header │
├────────┬───────────────────────────┤
│ │ │
│ Sidebar│ Content │
│ │ │
└────────┴───────────────────────────┘
3. Dashboard Layout
Full application dashboard with sidebar + header:
┌────────────────────────────────────┐
│ Header │
├────────┬───────────────────────────┤
│ │ │
│ Sidebar│ Dashboard Content │
│ │ (usually cards/widgets) │
└────────┴───────────────────────────┘
4. Blank Layout
No header/footer, just content (for custom layouts):
┌────────────────────────────────────┐
│ │
│ Full Content │
│ │
└────────────────────────────────────┘
Usage Examples
Example 1: Create a Custom Homepage
import { getPageRenderer } from '@/lib/page-renderer'
const customHomepage: PageDefinition = {
id: 'page_custom_home',
level: 1,
title: 'My Custom Homepage',
layout: 'default',
components: [
{
id: 'hero',
type: 'Container',
props: {
className: 'py-20 text-center bg-gradient-to-br from-blue-500 to-purple-600'
},
children: [
{
id: 'hero_title',
type: 'Heading',
props: {
level: 1,
children: 'Welcome to Our Platform',
className: 'text-5xl font-bold text-white mb-4'
},
children: []
},
{
id: 'hero_cta',
type: 'Button',
props: {
children: 'Get Started',
variant: 'default',
size: 'lg'
},
children: []
}
]
}
],
permissions: {
requiresAuth: false
},
metadata: {
showHeader: true,
showFooter: true
}
}
const renderer = getPageRenderer()
await renderer.registerPage(customHomepage)
Example 2: User Dashboard with Sidebar
const userDashboard: PageDefinition = {
id: 'page_user_dash',
level: 2,
title: 'Dashboard',
layout: 'dashboard',
components: [
{
id: 'stats_card',
type: 'Card',
props: { className: 'p-6' },
children: [
{
id: 'stats_title',
type: 'Heading',
props: { level: 2, children: 'Your Stats' },
children: []
}
]
}
],
permissions: {
requiresAuth: true,
requiredRole: 'user'
},
metadata: {
showHeader: true,
headerTitle: 'Dashboard',
sidebarItems: [
{ id: 'nav_home', label: 'Home', icon: '🏠', action: 'navigate', target: '1' },
{ id: 'nav_profile', label: 'Profile', icon: '👤', action: 'navigate', target: '2' }
]
}
}
Example 3: Page with Lua Lifecycle Hooks
// First, create Lua scripts in database
const onLoadScript: LuaScript = {
id: 'lua_page_analytics',
name: 'Track Page View',
code: `
function trackPageView(userId, pageId)
log("User " .. userId .. " viewed page " .. pageId)
-- Could save to database, call API, etc.
return true
end
return trackPageView
`,
parameters: [
{ name: 'userId', type: 'string' },
{ name: 'pageId', type: 'string' }
],
returnType: 'boolean'
}
await Database.addLuaScript(onLoadScript)
// Then reference it in page definition
const trackedPage: PageDefinition = {
id: 'page_tracked',
level: 2,
title: 'Tracked Page',
layout: 'default',
components: [],
luaScripts: {
onLoad: 'lua_page_analytics', // Runs when page loads
onUnload: 'lua_page_cleanup' // Runs when page unloads
},
permissions: {
requiresAuth: true
}
}
Rendering a Generic Page
In App.tsx or any level component:
import { GenericPage } from '@/components/GenericPage'
// Instead of:
// <Level1 onNavigate={handleNavigate} />
// Use:
<GenericPage
pageId="page_level1_home"
user={currentUser}
level={1}
onNavigate={handleNavigate}
onLogout={handleLogout}
/>
Extending the System
Add Custom Component Types
Register new component types in component-catalog.ts:
{
type: 'VideoPlayer',
label: 'Video Player',
icon: 'Play',
category: 'Media',
allowsChildren: false,
defaultProps: {
src: '',
controls: true
},
propSchema: [
{ name: 'src', label: 'Video URL', type: 'string' },
{ name: 'controls', label: 'Show Controls', type: 'boolean' }
]
}
Then add rendering logic in RenderComponent.tsx:
case 'VideoPlayer':
return (
<video
src={props.src}
controls={props.controls}
className={props.className}
/>
)
Create Package-Based Pages
Create a complete application package with pages:
const forumPackage = {
manifest: {
id: 'forum-app',
name: 'Forum Application',
version: '1.0.0'
},
content: {
pages: [
{
id: 'page_forum_home',
level: 2,
title: 'Forum Home',
layout: 'sidebar',
components: [...],
permissions: { requiresAuth: true, requiredRole: 'user' }
},
{
id: 'page_forum_thread',
level: 2,
title: 'Thread View',
layout: 'default',
components: [...],
permissions: { requiresAuth: true, requiredRole: 'user' }
}
],
luaScripts: [
// Forum-specific scripts
]
}
}
Benefits
🚀 Flexibility
- Pages can be modified from Level 4/5 GUI
- No code deployment needed
- Real-time preview of changes
🔒 Security
- Permission checks at page level
- Role-based access control
- Custom Lua permission logic
📦 Packages
- Distribute complete applications as packages
- Forum, blog, e-commerce as installable packages
- Community-shareable templates
⚡ Performance
- Pages loaded on-demand
- Lua scripts cached
- Component tree optimized
🛠️ Developer Experience
- Clear separation of concerns
- Type-safe definitions
- Easy to test and debug
Migration Path
Phase 1: ✅ COMPLETE (Iteration 24)
- ✅ Created
PageRenderersystem - ✅ Created
GenericPagecomponent - ✅ Created
PageDefinitionBuilder - ✅ Created default Level 1-3 pages
Phase 2: 🚧 IN PROGRESS
- Update
App.tsxto useGenericPageinstead of Level1/2/3 - Add page management UI in Level 4/5
- Allow god users to edit page definitions
- Add visual page builder drag-and-drop
Phase 3: 📋 PLANNED
- Remove
Level1.tsx,Level2.tsx,Level3.tsxfiles - Create more default page templates
- Build page marketplace/library
- Add page versioning and rollback
Comparison: Before vs After
Level 1 Homepage - Before
// Level1.tsx - 300+ lines of hardcoded TSX
export function Level1({ onNavigate }: Level1Props) {
return (
<div className="min-h-screen">
<header>...</header>
<section className="hero">
<h1>Welcome to MetaBuilder</h1>
{/* Hardcoded content */}
</section>
<footer>...</footer>
</div>
)
}
Level 1 Homepage - After
// App.tsx - Generic, data-driven
<GenericPage
pageId="page_level1_home"
user={null}
level={1}
onNavigate={handleNavigate}
/>
// Page definition stored in database, fully customizable
Next Steps
To complete the migration:
- Update App.tsx to use
GenericPagefor levels 1-3 - Add Page Builder UI in Level 4/5 panels
- Create Page Templates for common layouts
- Build Page Editor with drag-and-drop
- Test Thoroughly before removing old TSX files
Conclusion
The Generic Page System represents a major architectural improvement:
- Less hardcoded TSX = more flexibility
- Declarative pages = easier to maintain
- Database-driven = dynamic without deployment
- Package system = shareable applications
This moves MetaBuilder closer to being a true "no-code" platform where the builder UI (Levels 4-5) is the only hardcoded TSX, and everything else is procedurally generated from data.