# Quick Reference: JSON-Driven Pages ## File Structure ``` src/ ├── config/pages/ │ ├── model-designer.json # Models page schema │ ├── component-tree.json # Component Trees page schema │ └── workflow-designer.json # Workflows page schema ├── components/ │ ├── JSONModelDesigner.tsx # Models wrapper component │ ├── JSONComponentTreeManager.tsx # Trees wrapper component │ └── JSONWorkflowDesigner.tsx # Workflows wrapper component └── lib/ └── json-ui/ └── page-renderer.tsx # Core JSON renderer ``` ## Creating a New JSON Page ### 1. Create the JSON Schema `src/config/pages/my-page.json`: ```json { "id": "my-page", "name": "My Page", "layout": { "type": "single" }, "dataSources": [ { "id": "items", "type": "kv", "key": "app-items", "defaultValue": [] }, { "id": "selectedId", "type": "static", "defaultValue": null }, { "id": "selectedItem", "type": "computed", "compute": "(data) => data.items?.find(i => i.id === data.selectedId)", "dependencies": ["items", "selectedId"] } ], "components": [ { "id": "root", "type": "div", "props": { "className": "h-full p-6" }, "children": [] } ] } ``` ### 2. Create the Wrapper Component `src/components/JSONMyPage.tsx`: ```tsx import { PageRenderer } from '@/lib/json-ui/page-renderer' import myPageSchema from '@/config/pages/my-page.json' import { PageSchema } from '@/types/json-ui' interface JSONMyPageProps { items: any[] onItemsChange: (items: any[]) => void } export function JSONMyPage({ items, onItemsChange }: JSONMyPageProps) { const schema = myPageSchema as PageSchema const handleCustomAction = async (action: any, event?: any) => { console.log('[JSONMyPage] Custom action:', action, event) } return } ``` ### 3. Register in Component Registry `src/lib/component-registry.ts`: ```typescript JSONMyPage: lazyWithPreload( () => import('@/components/JSONMyPage').then(m => ({ default: m.JSONMyPage })), 'JSONMyPage' ), ``` ### 4. Add to Pages Config `src/config/pages.json`: ```json { "id": "my-page-json", "title": "My Page (JSON)", "icon": "Icon", "component": "JSONMyPage", "enabled": true, "toggleKey": "myPageJSON", "order": 99, "props": { "state": ["items"], "actions": ["onItemsChange:setItems"] } } ``` ### 5. Create Seed Data ```typescript seed_kv_store_tool({ key: "app-items", operation: "set", value: [ { id: "1", name: "Item 1", description: "First item" }, { id: "2", name: "Item 2", description: "Second item" } ] }) ``` ## Common Patterns ### Data Source Types **KV (Persistent)**: ```json { "id": "myData", "type": "kv", "key": "app-my-data", "defaultValue": [] } ``` **Static (Component State)**: ```json { "id": "tempValue", "type": "static", "defaultValue": "" } ``` **Computed (Derived)**: ```json { "id": "filteredItems", "type": "computed", "compute": "(data) => data.items.filter(i => i.active)", "dependencies": ["items"] } ``` ### Component Bindings **Simple Binding**: ```json { "type": "Text", "bindings": { "children": { "source": "itemName" } } } ``` **Path Binding**: ```json { "type": "Text", "bindings": { "children": { "source": "selectedItem", "path": "name" } } } ``` **Transform Binding**: ```json { "type": "Badge", "bindings": { "variant": { "source": "status", "transform": "(val) => val === 'active' ? 'success' : 'secondary'" } } } ``` ### Event Handlers **Simple Action**: ```json { "type": "Button", "events": [ { "event": "click", "actions": [ { "id": "open-dialog", "type": "set-value", "target": "dialogOpen", "value": true } ] } ] } ``` **Conditional Action**: ```json { "event": "click", "actions": [...], "condition": "(data) => data.items.length > 0" } ``` ### Conditional Rendering ```json { "type": "div", "condition": { "source": "selectedItem", "transform": "(val) => !!val" }, "children": [...] } ``` ## Layout Patterns ### Sidebar + Main Content ```json { "type": "div", "props": { "className": "h-full flex" }, "children": [ { "id": "sidebar", "type": "div", "props": { "className": "w-80 border-r" }, "children": [] }, { "id": "main", "type": "div", "props": { "className": "flex-1" }, "children": [] } ] } ``` ### Empty State ```json { "type": "div", "condition": { "source": "items", "transform": "(val) => !val || val.length === 0" }, "props": { "className": "text-center p-12" }, "children": [ { "type": "Heading", "props": { "children": "No Items Yet" } }, { "type": "Button", "props": { "children": "Create First Item" }, "events": [...] } ] } ``` ### List/Grid ```json { "type": "div", "props": { "className": "grid grid-cols-3 gap-4" }, "children": [] } ``` ## Debugging Tips ### Log Data Sources Add this to your wrapper component: ```typescript console.log('[Page] Schema:', schema) console.log('[Page] Data sources:', schema.dataSources) ``` ### Check Computed Values The PageRenderer's `data` object contains all data sources: ```typescript const { data } = useDataSources(schema.dataSources) console.log('[Page] All data:', data) ``` ### Validate Bindings Ensure source IDs match data source IDs: ```json { "bindings": { "prop": { "source": "myDataSource" // Must match dataSources[].id } } } ``` ## Best Practices 1. **Use KV for persistent data** - User preferences, saved items, app state 2. **Use static for UI state** - Dialog open/closed, selected tabs, temp values 3. **Use computed for derived data** - Filtered lists, calculated totals, selected items 4. **Keep compute functions simple** - Complex logic should be in custom hooks 5. **Name sources descriptively** - `selectedWorkflow` not `sel`, `filteredItems` not `items2` 6. **Document complex schemas** - Add comments in the JSON (strip before runtime) 7. **Test with seed data** - Always provide realistic default data 8. **Validate schemas** - Use TypeScript types to catch errors early ## Performance Tips - Minimize computed dependencies - Only include what's actually used - Use path bindings - `{ source: "item", path: "name" }` is more efficient - Lazy load heavy components - Use code splitting for complex editors - Cache expensive computations - Consider memoization for heavy transforms - Limit nesting depth - Deep component trees slow rendering ## Common Issues **Issue**: Computed value not updating **Fix**: Check dependencies array includes all used sources **Issue**: Binding shows undefined **Fix**: Ensure data source exists and has a value before binding **Issue**: Event not firing **Fix**: Verify event name matches React event (e.g., `click` not `onClick`) **Issue**: Condition not working **Fix**: Transform function must return boolean, check for null/undefined **Issue**: Component not rendering **Fix**: Ensure component type matches registry name exactly