10 KiB
Declarative System Documentation
Overview
CodeForge now uses a declarative, JSON-driven architecture that makes it easy to add, modify, and configure pages without touching core application code. This system reduces complexity, improves maintainability, and makes the codebase more scalable.
Key Benefits
✅ Add new pages by editing a JSON file - no need to modify App.tsx
✅ Dynamic component loading - components are lazy-loaded based on configuration
✅ Automatic keyboard shortcuts - defined in JSON, automatically wired up
✅ Feature toggle integration - pages automatically show/hide based on feature flags
✅ Consistent page structure - all pages follow the same rendering pattern
✅ Easy to test and maintain - configuration is separate from implementation
Architecture
Configuration Files
/src/config/pages.json
The main configuration file that defines all pages in the application.
{
"pages": [
{
"id": "dashboard",
"title": "Dashboard",
"icon": "ChartBar",
"component": "ProjectDashboard",
"enabled": true,
"shortcut": "ctrl+1",
"order": 1
},
{
"id": "code",
"title": "Code Editor",
"icon": "Code",
"component": "CodeEditor",
"enabled": true,
"toggleKey": "codeEditor",
"shortcut": "ctrl+2",
"order": 2,
"requiresResizable": true
}
]
}
Page Configuration Schema
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ | Unique identifier for the page (used in routing) |
title |
string | ✅ | Display name for the page |
icon |
string | ✅ | Phosphor icon name |
component |
string | ✅ | Component name (must exist in componentMap) |
enabled |
boolean | ✅ | Whether the page is available |
toggleKey |
string | ❌ | Feature toggle key (from FeatureToggles type) |
shortcut |
string | ❌ | Keyboard shortcut (e.g., "ctrl+1", "ctrl+shift+e") |
order |
number | ✅ | Display order in navigation |
requiresResizable |
boolean | ❌ | Special flag for split-pane layouts |
Core Functions
getPageConfig()
Returns the complete page configuration.
import { getPageConfig } from '@/config/page-loader'
const config = getPageConfig()
// Returns: { pages: PageConfig[] }
getEnabledPages(featureToggles)
Returns only pages that are enabled and pass feature toggle checks.
import { getEnabledPages } from '@/config/page-loader'
const enabledPages = getEnabledPages(featureToggles)
// Returns: PageConfig[]
getPageShortcuts(featureToggles)
Returns keyboard shortcuts for enabled pages.
import { getPageShortcuts } from '@/config/page-loader'
const shortcuts = getPageShortcuts(featureToggles)
// Returns: Array<{ key: string, ctrl?: boolean, shift?: boolean, description: string, action: string }>
How to Add a New Page
Step 1: Create Your Component
// src/components/MyNewDesigner.tsx
export function MyNewDesigner() {
return (
<div className="p-6">
<h1>My New Designer</h1>
</div>
)
}
Step 2: Add to Component Map
In src/App.tsx, add your component to the componentMap:
const componentMap: Record<string, React.LazyExoticComponent<any>> = {
// ... existing components
MyNewDesigner: lazy(() => import('@/components/MyNewDesigner').then(m => ({ default: m.MyNewDesigner }))),
}
Step 3: Add to pages.json
Add your page configuration to /src/config/pages.json:
{
"id": "my-new-page",
"title": "My New Designer",
"icon": "Sparkle",
"component": "MyNewDesigner",
"enabled": true,
"toggleKey": "myNewFeature",
"shortcut": "ctrl+shift+n",
"order": 21
}
Step 4: (Optional) Add Feature Toggle
If using a feature toggle, add it to the FeatureToggles type in /src/types/project.ts:
export interface FeatureToggles {
// ... existing toggles
myNewFeature: boolean
}
And update the default in /src/hooks/use-project-state.ts:
const DEFAULT_FEATURE_TOGGLES: FeatureToggles = {
// ... existing toggles
myNewFeature: true,
}
Step 5: (Optional) Add Props Mapping
If your component needs props, add it to getPropsForComponent in App.tsx:
const getPropsForComponent = (pageId: string) => {
const propsMap: Record<string, any> = {
// ... existing mappings
'MyNewDesigner': {
data: someData,
onDataChange: setSomeData,
},
}
return propsMap[pageId] || {}
}
That's it! Your new page will now:
- ✅ Appear in the navigation menu
- ✅ Be accessible via the keyboard shortcut
- ✅ Show/hide based on the feature toggle
- ✅ Be searchable in global search
- ✅ Follow the same rendering pattern as other pages
Component Map
The componentMap in App.tsx is the registry of all available components:
const componentMap: Record<string, React.LazyExoticComponent<any>> = {
ProjectDashboard: lazy(() => import('@/components/ProjectDashboard').then(m => ({ default: m.ProjectDashboard }))),
CodeEditor: lazy(() => import('@/components/CodeEditor').then(m => ({ default: m.CodeEditor }))),
// ... more components
}
All components are lazy-loaded for optimal performance. They only load when the user navigates to that page.
Special Layouts
Split-Pane Layout (Code Editor)
The code editor uses a special resizable split-pane layout. This is handled by the requiresResizable flag:
{
"id": "code",
"component": "CodeEditor",
"requiresResizable": true
}
The rendering logic in App.tsx checks for this flag and renders the appropriate layout.
Feature Toggles Integration
Pages can be conditionally enabled based on feature toggles:
{
"id": "playwright",
"toggleKey": "playwright",
"enabled": true
}
The page will only appear if:
enabledistruein the JSONfeatureToggles.playwrightistrue(or undefined)
Users can toggle features on/off in the Features page.
Keyboard Shortcuts
Shortcuts are automatically parsed from the configuration:
{
"shortcut": "ctrl+1"
}
Supported modifiers:
ctrl- Control keyshift- Shift keyalt- Alt key (not implemented yet, but easy to add)
Format: [modifier+]key (e.g., "ctrl+1", "ctrl+shift+e", "f")
Future Enhancements
The declarative system can be extended to support:
1. Dynamic Props from JSON
{
"component": "MyComponent",
"props": {
"title": "Dynamic Title",
"showToolbar": true
}
}
2. Layout Configuration
{
"layout": {
"type": "split",
"direction": "horizontal",
"panels": [
{ "component": "Sidebar", "size": 20 },
{ "component": "MainContent", "size": 80 }
]
}
}
3. Permission-Based Access
{
"permissions": ["admin", "editor"]
}
4. Page Groups/Categories
{
"category": "Design Tools",
"group": "styling"
}
5. Page Metadata
{
"description": "Design and test Playwright e2e tests",
"tags": ["testing", "automation"],
"beta": true
}
Advanced: Page Schema System
For even more advanced use cases, check out:
/src/config/page-schema.ts- TypeScript types for page schemas/src/components/orchestration/PageRenderer.tsx- Generic page renderer/src/config/default-pages.json- Alternative page configuration format
These files provide a more sophisticated schema-based system that can define:
- Complex layouts (split, tabs, grid)
- Component hierarchies
- Action handlers
- Context management
Migration Guide
If you have an existing page that's hardcoded in App.tsx:
Before (Hardcoded):
<TabsContent value="my-page" className="h-full m-0">
<Suspense fallback={<LoadingFallback message="Loading..." />}>
<MyComponent prop1={data} prop2={handler} />
</Suspense>
</TabsContent>
After (Declarative):
- Add to
pages.json:
{
"id": "my-page",
"title": "My Page",
"icon": "Star",
"component": "MyComponent",
"enabled": true,
"order": 10
}
- Add to
componentMap:
MyComponent: lazy(() => import('@/components/MyComponent').then(m => ({ default: m.MyComponent }))),
- Add props mapping if needed:
'MyComponent': {
prop1: data,
prop2: handler,
}
- Remove the hardcoded TabsContent - the system handles it automatically!
Troubleshooting
Page doesn't appear in navigation
- ✅ Check
enabled: truein pages.json - ✅ Check feature toggle is enabled (if using
toggleKey) - ✅ Verify component exists in
componentMap - ✅ Check console for errors
Component not loading
- ✅ Verify import path in
componentMap - ✅ Check component has a default export
- ✅ Look for TypeScript errors in component file
Keyboard shortcut not working
- ✅ Verify shortcut format (e.g., "ctrl+1")
- ✅ Check for conflicts with browser shortcuts
- ✅ Make sure page is enabled
Props not being passed
- ✅ Add mapping in
getPropsForComponent - ✅ Verify component name matches
pages.json - ✅ Check prop types match component interface
Best Practices
- Keep pages.json organized - Group related pages together, use consistent ordering
- Use meaningful IDs - Use kebab-case, descriptive IDs (e.g., "code-editor", not "ce")
- Choose appropriate icons - Use Phosphor icons that match the page purpose
- Document feature toggles - Add comments to FeatureToggles type
- Test shortcuts - Verify shortcuts don't conflict with browser/OS shortcuts
- Lazy load everything - Keep components in the lazy componentMap
- Type your props - Use TypeScript interfaces for component props
- Keep components small - Follow the <150 LOC guideline from refactoring
Summary
The declarative system transforms CodeForge from a monolithic React app into a flexible, configuration-driven platform. By moving page definitions to JSON, we gain:
- 🚀 Faster development - Add pages in minutes, not hours
- 🔧 Easier maintenance - Configuration is centralized and versioned
- 📦 Better performance - Lazy loading reduces initial bundle size
- 🎯 Cleaner code - Business logic separated from configuration
- 🧪 Simpler testing - Mock configuration instead of mocking components
The system is designed to grow with your needs - start simple with basic page definitions, then add advanced features like layout configuration, permissions, and dynamic props as needed.