diff --git a/DECLARATIVE_SYSTEM.md b/DECLARATIVE_SYSTEM.md new file mode 100644 index 0000000..60be009 --- /dev/null +++ b/DECLARATIVE_SYSTEM.md @@ -0,0 +1,399 @@ +# 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. + +```json +{ + "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. + +```typescript +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. + +```typescript +import { getEnabledPages } from '@/config/page-loader' + +const enabledPages = getEnabledPages(featureToggles) +// Returns: PageConfig[] +``` + +#### `getPageShortcuts(featureToggles)` +Returns keyboard shortcuts for enabled pages. + +```typescript +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 + +```typescript +// src/components/MyNewDesigner.tsx +export function MyNewDesigner() { + return ( +
+

My New Designer

+
+ ) +} +``` + +### Step 2: Add to Component Map + +In `src/App.tsx`, add your component to the `componentMap`: + +```typescript +const componentMap: Record> = { + // ... 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`: + +```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`: + +```typescript +export interface FeatureToggles { + // ... existing toggles + myNewFeature: boolean +} +``` + +And update the default in `/src/hooks/use-project-state.ts`: + +```typescript +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`: + +```typescript +const getPropsForComponent = (pageId: string) => { + const propsMap: Record = { + // ... 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: + +```typescript +const componentMap: Record> = { + 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: + +```json +{ + "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: + +```json +{ + "id": "playwright", + "toggleKey": "playwright", + "enabled": true +} +``` + +The page will only appear if: +1. `enabled` is `true` in the JSON +2. `featureToggles.playwright` is `true` (or undefined) + +Users can toggle features on/off in the **Features** page. + +## Keyboard Shortcuts + +Shortcuts are automatically parsed from the configuration: + +```json +{ + "shortcut": "ctrl+1" +} +``` + +Supported modifiers: +- `ctrl` - Control key +- `shift` - Shift key +- `alt` - 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 +```json +{ + "component": "MyComponent", + "props": { + "title": "Dynamic Title", + "showToolbar": true + } +} +``` + +### 2. Layout Configuration +```json +{ + "layout": { + "type": "split", + "direction": "horizontal", + "panels": [ + { "component": "Sidebar", "size": 20 }, + { "component": "MainContent", "size": 80 } + ] + } +} +``` + +### 3. Permission-Based Access +```json +{ + "permissions": ["admin", "editor"] +} +``` + +### 4. Page Groups/Categories +```json +{ + "category": "Design Tools", + "group": "styling" +} +``` + +### 5. Page Metadata +```json +{ + "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): +```typescript + + }> + + + +``` + +### After (Declarative): +1. Add to `pages.json`: +```json +{ + "id": "my-page", + "title": "My Page", + "icon": "Star", + "component": "MyComponent", + "enabled": true, + "order": 10 +} +``` + +2. Add to `componentMap`: +```typescript +MyComponent: lazy(() => import('@/components/MyComponent').then(m => ({ default: m.MyComponent }))), +``` + +3. Add props mapping if needed: +```typescript +'MyComponent': { + prop1: data, + prop2: handler, +} +``` + +4. Remove the hardcoded TabsContent - the system handles it automatically! + +## Troubleshooting + +### Page doesn't appear in navigation +- ✅ Check `enabled: true` in 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 + +1. **Keep pages.json organized** - Group related pages together, use consistent ordering +2. **Use meaningful IDs** - Use kebab-case, descriptive IDs (e.g., "code-editor", not "ce") +3. **Choose appropriate icons** - Use Phosphor icons that match the page purpose +4. **Document feature toggles** - Add comments to FeatureToggles type +5. **Test shortcuts** - Verify shortcuts don't conflict with browser/OS shortcuts +6. **Lazy load everything** - Keep components in the lazy componentMap +7. **Type your props** - Use TypeScript interfaces for component props +8. **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. diff --git a/EXAMPLE_NEW_PAGE.md b/EXAMPLE_NEW_PAGE.md new file mode 100644 index 0000000..7e0b0c5 --- /dev/null +++ b/EXAMPLE_NEW_PAGE.md @@ -0,0 +1,265 @@ +# Example: Adding a New "API Tester" Page + +This example demonstrates the complete process of adding a new page to CodeForge using the declarative system. + +## Goal + +Add an "API Tester" page that allows users to test REST API endpoints. + +## Step 1: Create the Component + +Create `src/components/ApiTester.tsx`: + +```typescript +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Textarea } from '@/components/ui/textarea' + +export function ApiTester() { + const [method, setMethod] = useState('GET') + const [url, setUrl] = useState('') + const [response, setResponse] = useState('') + const [loading, setLoading] = useState(false) + + const handleTest = async () => { + setLoading(true) + try { + const res = await fetch(url, { method }) + const data = await res.json() + setResponse(JSON.stringify(data, null, 2)) + } catch (error) { + setResponse(`Error: ${error}`) + } finally { + setLoading(false) + } + } + + return ( +
+
+
+
+

API Tester

+

+ Test REST API endpoints and inspect responses +

+
+ + + + Request + + +
+ + setUrl(e.target.value)} + className="flex-1" + /> + +
+
+
+ + {response && ( + + + Response + + +