This commit is contained in:
2026-01-18 15:48:29 +00:00
parent 9a9d76865b
commit f547d38539
134 changed files with 3863 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(find:*)",
"Bash(grep:*)"
]
}
}

View File

@@ -0,0 +1,262 @@
# Component Conversion Analysis
## Analysis of 68 React Components
After analyzing all 68 organism and molecule components, here's what can be converted to JSON:
### Categories
#### ✅ Fully Convertible to JSON (48 components)
These are presentational components with props, conditional rendering, and simple event handlers:
**Molecules (35):**
1. `LabelWithBadge` - ✅ Converted
2. `LoadingState` - ✅ Converted
3. `SaveIndicator` - ✅ Converted (computed sources replace hook)
4. `SearchInput` - ✅ Converted
5. `AppBranding` - Props + conditionals
6. `ActionBar` - Layout + buttons
7. `Breadcrumb` - ✅ Already converted
8. `DataCard` - ✅ Already converted
9. `EmptyState` - ✅ Already converted
10. `EmptyEditorState` - ✅ Already converted
11. `FileTabs` - ✅ Already converted
12. `NavigationGroupHeader` - Collapse trigger + state
13. `NavigationItem` - Button with active state
14. `PageHeaderContent` - Layout composition
15. `ToolbarButton` - Tooltip + IconButton
16. `TreeListHeader` - Buttons with events
17. `ComponentTreeEmptyState` - Config + icon lookup
18. `ComponentTreeHeader` - Counts + expand/collapse
19. `PropertyEditorEmptyState` - Config + icon lookup
20. `PropertyEditorHeader` - Title + count
21. `PropertyEditorSection` - Collapsible section
22. `DataSourceIdField` - Input with validation display
23. `KvSourceFields` - Form fields
24. `StaticSourceFields` - Form fields
25. `ComputedSourceFields` - Form fields
26. `GitHubBuildStatus` - Status display + polling
27. `LoadingFallback` - Spinner + message
28. `MonacoEditorPanel` - Layout wrapper (not editor itself)
29. `SearchBar` - SearchInput wrapper
30. `SeedDataManager` - Form + buttons (logic in parent)
31. `StorageSettings` - Form fields
32. `TreeCard` - Card + tree display
33. `TreeFormDialog` - Dialog with form (validation in parent)
34. `EditorActions` - Button group
35. `EditorToolbar` - Toolbar layout
**Organisms (13):**
1. `AppHeader` - ✅ Already converted
2. `EmptyCanvasState` - ✅ Already converted
3. `NavigationMenu` - ✅ Already converted
4. `PageHeader` - ✅ Already converted
5. `SchemaEditorLayout` - ✅ Already converted
6. `SchemaEditorSidebar` - ✅ Already converted
7. `SchemaEditorCanvas` - ✅ Already converted
8. `SchemaEditorPropertiesPanel` - ✅ Already converted
9. `SchemaEditorStatusBar` - Status display
10. `SchemaEditorToolbar` - Toolbar with actions
11. `ToolbarActions` - Action buttons
12. `SchemaCodeViewer` - Tabs + code display
13. `TreeListPanel` - List display
#### ⚠️ Needs Wrapper (Complex Hooks) (12 components)
These use hooks but the hook logic can be extracted to data sources or remain in a thin wrapper:
**Molecules (10):**
1. `BindingEditor` - Form with `useForm` hook → Extract to form state
2. `ComponentBindingDialog` - Dialog with `useForm` → Extract to form state
3. `DataSourceEditorDialog` - Complex form + validation → Wrapper + JSON form
4. `PropertyEditor` - Dynamic form generation → Computed source for fields
5. `ComponentPalette` - Search + filter → Computed source
6. `CanvasRenderer` - Recursive rendering → Could be JSON with loop support
7. `ComponentTree` - Tree state + drag/drop → State machine in JSON
8. `ComponentTreeNodes` - Recursive nodes → Loop construct
9. `CodeExplanationDialog` - Dialog + API call → Dialog JSON + API action
10. `DataSourceCard` - Card with actions + state → Separate state, JSON layout
**Organisms (2):**
1. `DataSourceManager` - Complex CRUD + hook → Extract `useDataSourceManager` logic
2. `JSONUIShowcase` - Examples display → Convert examples to JSON schema
#### ❌ Must Stay React (8 components)
These have imperative APIs, complex recursion, or third-party integration:
**Molecules (6):**
1. `LazyMonacoEditor` - Monaco integration (refs, imperative API)
2. `LazyInlineMonacoEditor` - Monaco integration
3. `MonacoEditorPanel` - Monaco wrapper
4. `LazyBarChart` - Recharts integration
5. `LazyLineChart` - Recharts integration
6. `LazyD3BarChart` - D3.js integration (imperative DOM manipulation)
**Organisms (2):**
1. `SchemaEditor` - Complex editor with drag-drop, undo/redo state machine
2. `DataBindingDesigner` - Visual flow editor with canvas manipulation
## Conversion Statistics
| Category | Count | Percentage |
|----------|-------|------------|
| ✅ Fully Convertible | 48 | 71% |
| ⚠️ Needs Wrapper | 12 | 18% |
| ❌ Must Stay React | 8 | 11% |
| **Total** | **68** | **100%** |
## Key Insights
### 1. Most Components Are Presentational
71% of components are pure presentation + simple logic that JSON can handle with:
- Data binding
- Computed sources
- Conditional rendering
- Event actions
- Loops (for lists)
### 2. Hooks Aren't a Blocker
Even components with hooks like `useSaveIndicator` can be converted:
- Time-based logic → Computed sources with polling
- Form state → Form data sources
- Local UI state → Page-level state
### 3. True Blockers
Only 8 components (11%) genuinely need React:
- Third-party library integrations (Monaco, D3, Recharts)
- Complex state machines (drag-drop, undo/redo)
- Imperative DOM manipulation
- Recursive algorithms (though loops might handle some)
### 4. Wrapper Pattern
The 12 "needs wrapper" components can have thin React wrappers that:
- Extract hooks to data source utilities
- Convert to JSON-configurable components
- Keep complex logic centralized
Example:
```tsx
// Thin wrapper
export function FormDialogWrapper({ schema, onSubmit }) {
const form = useForm()
return <JSONDialog schema={schema} formState={form} onSubmit={onSubmit} />
}
```
```json
// JSON configures it
{
"type": "FormDialogWrapper",
"props": {
"schema": { "$ref": "./schemas/user-form.json" }
}
}
```
## Recommended Conversion Priority
### Phase 1: Low-Hanging Fruit (35 molecules)
Convert all presentational molecules that are just composition:
- AppBranding, ActionBar, ToolbarButton, etc.
- **Impact**: Eliminate 51% of React components
### Phase 2: Organisms (13)
Convert layout organisms:
- TreeListPanel, SchemaCodeViewer, etc.
- **Impact**: Eliminate 70% of React components
### Phase 3: Extract Hooks (10 molecules)
Create data source utilities and convert:
- BindingEditor, ComponentPalette, etc.
- **Impact**: Eliminate 85% of React components
### Phase 4: Wrappers (2 organisms)
Create thin wrappers for complex components:
- DataSourceManager, JSONUIShowcase
- **Impact**: 89% conversion
### Final State
- **8 React components** (third-party integrations + complex editors)
- **60 JSON components** (89% of current React code)
- **100% JSON page definitions** (already achieved)
## Implementation Patterns
### Pattern 1: Simple Conversion
```tsx
// React
export function LabelWithBadge({ label, badge }) {
return (
<Flex>
<Text>{label}</Text>
{badge && <Badge>{badge}</Badge>}
</Flex>
)
}
```
```json
// JSON
{
"type": "div",
"className": "flex gap-2",
"children": [
{ "type": "Text", "dataBinding": { "children": { "source": "label" } } },
{
"type": "Badge",
"conditional": { "source": "badge", "operator": "truthy" },
"dataBinding": { "children": { "source": "badge" } }
}
]
}
```
### Pattern 2: Hook Extraction
```tsx
// React (before)
export function SaveIndicator({ lastSaved }) {
const { timeAgo, isRecent } = useSaveIndicator(lastSaved)
return <div>{isRecent ? 'Saved' : timeAgo}</div>
}
```
```json
// JSON (after) - hook logic → computed source
{
"dataSources": [
{
"id": "isRecent",
"type": "computed",
"compute": "(data) => Date.now() - data.lastSaved < 3000"
}
],
"type": "div",
"dataBinding": {
"children": {
"source": "isRecent",
"transform": "(isRecent, data) => isRecent ? 'Saved' : data.timeAgo"
}
}
}
```
### Pattern 3: Wrapper for Complex Logic
```tsx
// Thin React wrapper
export function DataSourceManagerWrapper(props) {
const manager = useDataSourceManager(props.dataSources)
return <JSONComponent schema={schema} data={manager} />
}
```
## Next Steps
1. ✅ Convert 35 simple molecules to JSON
2. ✅ Convert 13 layout organisms to JSON
3. ⚠️ Extract hooks to utilities for 10 components
4. ⚠️ Create wrappers for 2 complex organisms
5. ❌ Keep 8 third-party integrations as React
**Target: 60/68 components in JSON (89% conversion)**

471
docs/HYBRID_ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,471 @@
# Hybrid Architecture: JSON + React
## The Power of Both Worlds
This platform uses a **hybrid architecture** where JSON handles declarative UI composition while React provides the imperative implementation layer. This gives you the best of both worlds:
- **JSON** for structure, composition, and configuration
- **React** for complex logic, hooks, events, and interactivity
## What JSON Can't (and Shouldn't) Replace
### 1. Hooks
React hooks manage complex stateful logic that can't be represented declaratively:
```tsx
// ❌ Cannot be JSON
function useDataSourceManager(dataSources: DataSource[]) {
const [localSources, setLocalSources] = useState(dataSources)
const [editingSource, setEditingSource] = useState<DataSource | null>(null)
useEffect(() => {
// Sync with external API
syncDataSources(localSources)
}, [localSources])
const getDependents = useCallback((id: string) => {
return localSources.filter(ds => ds.dependencies?.includes(id))
}, [localSources])
return { localSources, editingSource, getDependents, ... }
}
```
**Why React?** Hooks encapsulate complex imperative logic: side effects, memoization, refs, context. JSON is declarative and can't express these patterns.
### 2. Event Handlers with Complex Logic
Simple actions work in JSON, but complex event handling needs code:
```tsx
// ✅ Simple actions in JSON
{
"events": [{
"event": "onClick",
"actions": [
{ "type": "setState", "target": "count", "value": 1 },
{ "type": "toast", "title": "Clicked!" }
]
}]
}
// ❌ Complex logic needs React
function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
// Validate file type
const validTypes = ['image/png', 'image/jpeg', 'image/svg+xml']
if (!validTypes.includes(file.type)) {
toast.error('Invalid file type')
return
}
// Check file size
const maxSize = 5 * 1024 * 1024 // 5MB
if (file.size > maxSize) {
toast.error('File too large')
return
}
// Convert to base64, compress, upload
compressImage(file).then(compressed => {
uploadToServer(compressed).then(url => {
updateState({ faviconUrl: url })
toast.success('Uploaded!')
})
})
}
```
**Why React?** Branching logic, async operations, error handling, file processing. JSON actions are linear and synchronous.
### 3. Classes and Interfaces
Type systems and OOP patterns require TypeScript:
```tsx
// ❌ Cannot be JSON
export interface DataSource {
id: string
type: DataSourceType
dependencies?: string[]
compute?: string
}
export class ThemeManager {
private themes: Map<string, Theme>
private listeners: Set<ThemeListener>
constructor(initialThemes: Theme[]) {
this.themes = new Map(initialThemes.map(t => [t.id, t]))
this.listeners = new Set()
}
applyTheme(themeId: string): void {
const theme = this.themes.get(themeId)
if (!theme) throw new Error(`Theme ${themeId} not found`)
// Apply CSS variables
Object.entries(theme.colors).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--${key}`, value)
})
// Notify listeners
this.listeners.forEach(listener => listener.onThemeChange(theme))
}
}
```
**Why React/TS?** Type safety, encapsulation, methods, private state. JSON is just data.
### 4. Complex Rendering Logic
Conditional rendering with complex business rules:
```tsx
// ❌ Cannot be JSON
function ComponentTree({ components }: ComponentTreeProps) {
const renderNode = (component: Component, depth: number): ReactNode => {
const hasChildren = component.children && component.children.length > 0
const isExpanded = expandedNodes.has(component.id)
const isDragging = draggedNode === component.id
const isDropTarget = dropTarget === component.id
// Determine visual state
const className = cn(
'tree-node',
{ 'tree-node--expanded': isExpanded },
{ 'tree-node--dragging': isDragging },
{ 'tree-node--drop-target': isDropTarget && canDrop(component) }
)
return (
<div
className={className}
style={{ paddingLeft: `${depth * 20}px` }}
onDragStart={() => handleDragStart(component)}
onDragOver={(e) => handleDragOver(e, component)}
onDrop={() => handleDrop(component)}
>
{/* Recursive rendering */}
{hasChildren && isExpanded && (
<div className="tree-children">
{component.children.map(child =>
renderNode(child, depth + 1)
)}
</div>
)}
</div>
)
}
return <div className="tree-root">{components.map(c => renderNode(c, 0))}</div>
}
```
**Why React?** Recursion, dynamic styling, drag-and-drop state, event coordination. JSON can't express recursive algorithms.
### 5. Third-Party Integrations
Libraries with imperative APIs need wrapper components:
```tsx
// ❌ Cannot be JSON
import MonacoEditor from '@monaco-editor/react'
export function LazyMonacoEditor({ value, onChange, language }: EditorProps) {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>()
const [isValid, setIsValid] = useState(true)
useEffect(() => {
// Configure Monaco
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
})
// Add custom validation
monaco.editor.onDidChangeMarkers(([uri]) => {
const markers = monaco.editor.getModelMarkers({ resource: uri })
setIsValid(markers.filter(m => m.severity === 8).length === 0)
})
}, [])
return (
<MonacoEditor
value={value}
onChange={onChange}
language={language}
onMount={(editor) => {
editorRef.current = editor
editor.addAction({
id: 'format-document',
label: 'Format Document',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
run: () => editor.getAction('editor.action.formatDocument')?.run()
})
}}
/>
)
}
```
**Why React?** Third-party libraries expect imperative APIs (refs, lifecycle methods). JSON can reference the wrapper, but can't create it.
## The Hybrid Pattern
### JSON References React Components
JSON schemas can reference any React component via the component registry:
```json
{
"id": "code-editor-section",
"type": "div",
"children": [
{
"id": "monaco-editor",
"type": "LazyMonacoEditor",
"props": {
"language": "typescript",
"theme": "vs-dark"
}
}
]
}
```
The `LazyMonacoEditor` is a React component with hooks, refs, and complex logic. JSON just *configures* it.
### Component Registry: The Bridge
```tsx
// src/lib/json-ui/component-registry.ts
export const componentRegistry: ComponentRegistry = {
// Simple components (could be JSON, but registered for convenience)
'Button': Button,
'Card': Card,
'Input': Input,
// Complex components (MUST be React)
'LazyMonacoEditor': LazyMonacoEditor,
'DataSourceManager': DataSourceManager,
'ComponentTree': ComponentTree,
'SchemaEditor': SchemaEditor,
// Hook-based components
'ProjectDashboard': ProjectDashboard, // uses multiple hooks
'CodeEditor': CodeEditor, // uses useEffect, useRef
'JSONModelDesigner': JSONModelDesigner, // uses custom hooks
}
```
### The 68 React Components
These aren't legacy cruft - they're **essential implementation**:
| Component Type | Count | Why React? |
|----------------|-------|------------|
| Hook-based managers | 15 | useState, useEffect, useCallback |
| Event-heavy UIs | 12 | Complex event handlers, drag-and-drop |
| Third-party wrappers | 8 | Monaco, Chart.js, D3 integrations |
| Recursive renderers | 6 | Tree views, nested structures |
| Complex forms | 10 | Validation, multi-step flows |
| Dialog/Modal managers | 8 | Portal rendering, focus management |
| Real-time features | 5 | WebSocket, polling, live updates |
| Lazy loaders | 4 | Code splitting, dynamic imports |
## When to Use What
### Use JSON When:
✅ Composing existing components
✅ Configuring layouts and styling
✅ Defining data sources and bindings
✅ Simple linear action chains
✅ Static page structure
✅ Theming and branding
✅ Feature flags and toggles
### Use React When:
✅ Complex state management (hooks)
✅ Imperative APIs (refs, third-party libs)
✅ Advanced event handling (validation, async)
✅ Recursive algorithms
✅ Performance optimization (memo, virtualization)
✅ Type-safe business logic (classes, interfaces)
✅ Side effects and lifecycle management
## Real-World Example: Data Source Manager
### What's in JSON
```json
{
"id": "data-source-section",
"type": "Card",
"children": [
{
"type": "CardHeader",
"children": [
{ "type": "CardTitle", "children": "Data Sources" }
]
},
{
"type": "CardContent",
"children": [
{
"id": "ds-manager",
"type": "DataSourceManager",
"dataBinding": {
"dataSources": { "source": "pageSources" }
},
"events": [{
"event": "onChange",
"actions": [
{ "type": "setState", "target": "pageSources", "valueFrom": "event" }
]
}]
}
]
}
]
}
```
**JSON handles:** Layout, composition, data binding, simple state updates
### What's in React
```tsx
// src/components/organisms/DataSourceManager.tsx
export function DataSourceManager({ dataSources, onChange }: Props) {
// ✅ Hook for complex state management
const {
dataSources: localSources,
addDataSource,
updateDataSource,
deleteDataSource,
getDependents, // ← Complex computed logic
} = useDataSourceManager(dataSources)
// ✅ Local UI state
const [editingSource, setEditingSource] = useState<DataSource | null>(null)
const [dialogOpen, setDialogOpen] = useState(false)
// ✅ Complex event handler with validation
const handleDeleteSource = (id: string) => {
const dependents = getDependents(id)
if (dependents.length > 0) {
toast.error(`Cannot delete: ${dependents.length} sources depend on it`)
return
}
deleteDataSource(id)
onChange(localSources.filter(ds => ds.id !== id))
toast.success('Data source deleted')
}
// ✅ Conditional rendering based on complex state
const groupedSources = useMemo(() => ({
kv: localSources.filter(ds => ds.type === 'kv'),
computed: localSources.filter(ds => ds.type === 'computed'),
static: localSources.filter(ds => ds.type === 'static'),
}), [localSources])
return (
<div>
{localSources.length === 0 ? (
<EmptyState />
) : (
<Stack>
<DataSourceGroup sources={groupedSources.kv} />
<DataSourceGroup sources={groupedSources.static} />
<DataSourceGroup sources={groupedSources.computed} />
</Stack>
)}
<DataSourceEditorDialog
open={dialogOpen}
dataSource={editingSource}
onSave={handleSaveSource}
/>
</div>
)
}
```
**React handles:** Hooks, validation, dependency checking, grouping logic, dialog state
## The Power of Hybrid
### Flexibility
- **JSON**: Quick changes, visual editing, non-developer friendly
- **React**: Full programming power when needed
### Composition
- **JSON**: Compose pages from molecules and organisms
- **React**: Implement the organisms with complex logic
### Evolution
- **Start Simple**: Build in JSON, reference simple React components
- **Add Complexity**: When logic grows, extract to custom React component
- **Stay Declarative**: JSON schema stays clean, complexity hidden in components
### Example Evolution
**Day 1 - Pure JSON:**
```json
{
"type": "Button",
"events": [{ "event": "onClick", "actions": [{ "type": "toast" }] }]
}
```
**Day 30 - Need validation:**
```json
{
"type": "ValidatedButton", // ← Custom React component
"props": { "validationRules": ["required", "email"] }
}
```
```tsx
// Custom component when JSON isn't enough
function ValidatedButton({ validationRules, onClick, ...props }) {
const validate = useValidation(validationRules)
const handleClick = () => {
if (!validate()) {
toast.error('Validation failed')
return
}
onClick?.()
}
return <Button onClick={handleClick} {...props} />
}
```
**Day 90 - Complex workflow:**
```json
{
"type": "WorkflowButton", // ← Even more complex component
"props": { "workflowId": "user-onboarding" }
}
```
The JSON stays simple. The complexity lives in well-tested React components.
## Conclusion
The **68 React components aren't cruft** - they're the **essential implementation layer** that makes the JSON system powerful:
- **Hooks** manage complex state
- **Events** handle imperative interactions
- **Interfaces** provide type safety
- **Classes** encapsulate business logic
- **Third-party integrations** extend capabilities
JSON provides the **declarative structure**. React provides the **imperative power**.
Together, they create a system that's:
- **Easy** for simple cases (JSON)
- **Powerful** for complex cases (React)
- **Scalable** (add React components as needed)
- **Maintainable** (JSON is readable, React is testable)
This is the architecture of modern low-code platforms - not "no code," but **"right tool for the right job."**

388
docs/JSON_ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,388 @@
# JSON-First Architecture
## Overview
This low-code platform uses a **JSON-first architecture** where the entire application is defined declaratively in JSON, eliminating React boilerplate and enabling visual editing, version control, and runtime customization.
## Core Principles
### 1. Everything is JSON
- **Pages**: All 35 application pages defined in JSON schemas
- **Components**: Atomic design library (atoms, molecules, organisms) in JSON
- **Themes**: Complete theming system configurable via JSON
- **Data**: State, bindings, and data sources declared in JSON
- **Actions**: Event handlers and side effects defined in JSON
### 2. Composition via $ref
JSON files reference each other using JSON Schema `$ref`:
```json
{
"id": "dashboard",
"components": [
{ "$ref": "./molecules/dashboard-header.json" },
{ "$ref": "./molecules/stats-grid.json" }
]
}
```
### 3. One Definition Per File
Following single-responsibility principle:
- 1 function per TypeScript file
- 1 type per TypeScript file
- 1 component definition per JSON file
- Compose larger structures via $ref
## Architecture Layers
```
┌─────────────────────────────────────┐
│ pages.json (35 pages) │ ← Router configuration
└──────────────┬──────────────────────┘
│ references
┌──────────────▼──────────────────────┐
│ Page Schemas (55 .json files) │ ← Page definitions
└──────────────┬──────────────────────┘
│ compose via $ref
┌──────────────▼──────────────────────┐
│ Organisms (8 .json files) │ ← Complex layouts
└──────────────┬──────────────────────┘
│ compose via $ref
┌──────────────▼──────────────────────┐
│ Molecules (23 .json files) │ ← Composed components
└──────────────┬──────────────────────┘
│ compose via $ref
┌──────────────▼──────────────────────┐
│ Atoms (23 .json files) │ ← Base components
└──────────────┬──────────────────────┘
│ reference
┌──────────────▼──────────────────────┐
│ React Components (68 .tsx) │ ← Implementation
│ Component Registry (100+ mapped) │
└─────────────────────────────────────┘
```
## File Structure
```
src/config/pages/
├── atoms/ # 23 base components
│ ├── button-primary.json
│ ├── heading-1.json
│ ├── text-muted.json
│ └── ...
├── molecules/ # 23 composed components
│ ├── dashboard-header.json
│ ├── stats-grid.json
│ ├── stat-card-base.json
│ └── ...
├── organisms/ # 8 complex layouts
│ ├── app-header.json
│ ├── navigation-menu.json
│ └── ...
├── layouts/ # Layout templates
│ └── single-column.json
├── data-sources/ # Data source templates
│ └── kv-storage.json
└── *.json # 55 page schemas
├── dashboard-simple.json
├── settings-page.json
└── ...
```
## JSON Schema Features
### Page Schema
```json
{
"$schema": "./schema/page-schema.json",
"id": "dashboard-simple",
"name": "Project Dashboard",
"description": "Overview of your project",
"icon": "ChartBar",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [
{
"id": "projectStats",
"$ref": "./data-sources/kv-storage.json",
"key": "project-stats",
"defaultValue": { "files": 0, "models": 0 }
}
],
"components": [
{ "$ref": "./molecules/dashboard-header.json" },
{ "$ref": "./molecules/stats-grid.json" }
]
}
```
### Data Binding
```json
{
"id": "files-value",
"type": "div",
"props": {
"className": "text-2xl font-bold",
"children": "0"
},
"dataBinding": {
"source": "projectStats",
"path": "files"
}
}
```
### Actions
```json
{
"type": "Button",
"events": [
{
"event": "onClick",
"actions": [
{
"type": "setState",
"target": "selectedTab",
"value": "colors"
},
{
"type": "toast",
"title": "Tab changed",
"variant": "success"
}
]
}
]
}
```
### Conditionals
```json
{
"type": "div",
"conditional": {
"source": "customColorCount",
"operator": "eq",
"value": 0
},
"children": [
{ "type": "p", "children": "No custom colors" }
]
}
```
## Theming System
### JSON Theme Definition
The entire theming system is JSON-based (theme.json):
```json
{
"sidebar": {
"width": "16rem",
"backgroundColor": "oklch(0.19 0.02 265)",
"foregroundColor": "oklch(0.95 0.01 265)"
},
"colors": {
"primary": "oklch(0.58 0.24 265)",
"accent": "oklch(0.75 0.20 145)",
"background": "oklch(0.15 0.02 265)"
},
"typography": {
"fontFamily": {
"body": "'IBM Plex Sans', sans-serif",
"heading": "'JetBrains Mono', monospace"
}
},
"spacing": {
"radius": "0.5rem"
}
}
```
### Runtime Theme Editing
Users can create theme variants and customize colors/fonts via JSON:
```json
{
"activeVariantId": "dark",
"variants": [
{
"id": "dark",
"name": "Dark Mode",
"colors": {
"primary": "#7c3aed",
"secondary": "#38bdf8",
"customColors": {
"success": "#10b981",
"warning": "#f59e0b"
}
}
}
]
}
```
## Data Sources
### KV Storage
```json
{
"id": "userData",
"type": "kv",
"key": "user-settings",
"defaultValue": { "theme": "dark" }
}
```
### Computed Sources
```json
{
"id": "totalFiles",
"type": "computed",
"compute": "(data) => data.files.length",
"dependencies": ["files"]
}
```
### Static Sources
```json
{
"id": "tabs",
"type": "static",
"defaultValue": ["colors", "typography", "preview"]
}
```
## Benefits Over Traditional React
### Traditional React Component (~50 lines)
```tsx
import { useState } from 'react'
import { Card } from '@/components/ui/card'
interface DashboardProps {
initialData?: { files: number }
}
export function Dashboard({ initialData }: DashboardProps) {
const [stats, setStats] = useState(initialData || { files: 0 })
return (
<div className="p-6">
<div className="border-b pb-4">
<h1 className="text-2xl font-bold">Dashboard</h1>
</div>
<Card className="p-6">
<div className="text-2xl font-bold">{stats.files}</div>
<div className="text-sm text-muted">Files</div>
</Card>
</div>
)
}
```
### JSON Equivalent (~15 lines)
```json
{
"id": "dashboard",
"dataSources": [
{ "id": "stats", "type": "kv", "key": "stats" }
],
"components": [
{ "$ref": "./molecules/dashboard-header.json" },
{
"$ref": "./molecules/stat-card.json",
"dataBinding": { "source": "stats", "path": "files" }
}
]
}
```
## Eliminated Boilerplate
**No imports** - Components referenced by type string
**No TypeScript interfaces** - Types inferred from registry
**No useState/useEffect** - State declared in dataSources
**No event handlers** - Actions declared in events array
**No prop drilling** - Data binding handles it
**No component exports** - Automatic via registry
**No JSX nesting** - Flat JSON structure with $ref
## Coverage Statistics
- **35/35 pages** use JSON schemas (100%)
- **0/35 pages** use React component references
- **109 JSON component files** created
- 23 atoms
- 23 molecules
- 8 organisms
- 55 page schemas
- **68 React components** remain as implementation layer
## Potential Cleanup Targets
### Deprecated Files (Safe to Remove)
- `src/config/default-pages.json` - Replaced by pages.json
- `src/config/json-demo.json` - Old demo file
- `src/config/template-ui.json` - Replaced by JSON schemas
### Keep (Still Used)
- `src/config/pages.json` - Active router configuration
- `theme.json` - Active theming system
- `src/config/feature-toggle-settings.json` - Feature flags
- All JSON schemas in `src/config/pages/`
## Best Practices
### 1. Atomic Granularity
Break components into smallest reusable units:
```
❌ dashboard.json (monolithic)
✅ dashboard-header.json + stats-grid.json + stat-card.json
```
### 2. $ref Composition
Always compose via references, never inline:
```json
{ "type": "div", "children": [ ... 50 lines ... ] }
{ "$ref": "./molecules/complex-section.json" }
```
### 3. Single Responsibility
One purpose per JSON file:
```
✅ stat-card-base.json (template)
✅ stat-card-files.json (specific instance)
✅ stat-card-models.json (specific instance)
```
### 4. Descriptive IDs
Use semantic IDs that describe purpose:
```json
{ "id": "dashboard-header" } // ✅ Good
{ "id": "div-1" } // ❌ Bad
```
## Future Enhancements
- [ ] Visual JSON editor for drag-and-drop page building
- [ ] Theme marketplace with sharable JSON themes
- [ ] Component library with searchable JSON snippets
- [ ] JSON validation and IntelliSense in VSCode
- [ ] Hot-reload JSON changes without app restart
- [ ] A/B testing via JSON variant switching
- [ ] Multi-tenant customization via tenant-specific JSONs
## Conclusion
This JSON-first architecture transforms React development from code-heavy to configuration-driven, enabling:
- **Visual editing** without touching code
- **Version control** friendly (JSON diffs)
- **Runtime customization** (load different JSONs)
- **Non-developer accessibility** (JSON is readable)
- **Rapid prototyping** (compose existing pieces)
- **Consistent patterns** (enforced by schema)
All without sacrificing the power of React when you need it - complex interactive components can still be written in React and referenced from JSON.

View File

@@ -0,0 +1,25 @@
import { PageRenderer } from '@/lib/json-ui/page-renderer'
import { LoadingFallback } from '@/components/molecules'
import { useSchemaLoader } from '@/hooks/use-schema-loader'
interface JSONSchemaPageLoaderProps {
schemaPath: string
}
export function JSONSchemaPageLoader({ schemaPath }: JSONSchemaPageLoaderProps) {
const { schema, loading, error } = useSchemaLoader(schemaPath)
if (loading) {
return <LoadingFallback message={`Loading ${schemaPath}...`} />
}
if (error || !schema) {
return (
<div className="p-8 text-center">
<p className="text-destructive">{error || 'Schema not found'}</p>
</div>
)
}
return <PageRenderer schema={schema} />
}

View File

@@ -0,0 +1,17 @@
import pagesConfig from './pages.json'
import { PageConfig } from '@/types/page-config'
import { FeatureToggles } from '@/types/project'
export function getEnabledPages(featureToggles?: FeatureToggles): PageConfig[] {
console.log('[CONFIG] 🔍 getEnabledPages called with toggles:', featureToggles)
const enabled = pagesConfig.pages.filter(page => {
if (!page.enabled) {
console.log('[CONFIG] ⏭️ Skipping disabled page:', page.id)
return false
}
if (!page.toggleKey) return true
return featureToggles?.[page.toggleKey as keyof FeatureToggles] !== false
}).sort((a, b) => a.order - b.order)
console.log('[CONFIG] ✅ Enabled pages:', enabled.map(p => p.id).join(', '))
return enabled as PageConfig[]
}

View File

@@ -0,0 +1,9 @@
import pagesConfig from './pages.json'
import { PageConfig } from '@/types/page-config'
export function getPageById(id: string): PageConfig | undefined {
console.log('[CONFIG] 🔍 getPageById called for:', id)
const page = pagesConfig.pages.find(page => page.id === id)
console.log('[CONFIG]', page ? '✅ Page found' : '❌ Page not found')
return page as PageConfig | undefined
}

View File

@@ -0,0 +1,9 @@
import pagesConfig from './pages.json'
import { PagesConfig } from '@/types/pages-config'
export function getPageConfig(): PagesConfig {
console.log('[CONFIG] 📄 getPageConfig called')
const config = pagesConfig as PagesConfig
console.log('[CONFIG] ✅ Pages config loaded:', config.pages.length, 'pages')
return config
}

View File

@@ -0,0 +1,30 @@
import { FeatureToggles } from '@/types/project'
import { getEnabledPages } from './get-enabled-pages'
export function getPageShortcuts(featureToggles?: FeatureToggles): Array<{
key: string
ctrl?: boolean
shift?: boolean
description: string
action: string
}> {
console.log('[CONFIG] ⌨️ getPageShortcuts called')
const shortcuts = getEnabledPages(featureToggles)
.filter(page => page.shortcut)
.map(page => {
const parts = page.shortcut!.toLowerCase().split('+')
const ctrl = parts.includes('ctrl')
const shift = parts.includes('shift')
const key = parts[parts.length - 1]
return {
key,
ctrl,
shift,
description: `Go to ${page.title}`,
action: page.id
}
})
console.log('[CONFIG] ✅ Shortcuts configured:', shortcuts.length)
return shortcuts
}

View File

@@ -0,0 +1,4 @@
{
"type": "create",
"target": ""
}

View File

@@ -0,0 +1,4 @@
{
"type": "delete",
"target": ""
}

View File

@@ -0,0 +1,6 @@
{
"type": "navigate",
"params": {
"to": ""
}
}

View File

@@ -0,0 +1,4 @@
{
"type": "update",
"target": ""
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "atomic-library-showcase-page",
"name": "Atomic Library Showcase",
"description": "Showcase of atomic design components",
"icon": "Atom",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "atomic-library-showcase-component",
"type": "AtomicLibraryShowcase",
"props": {}
}
]
}

View File

@@ -0,0 +1,6 @@
{
"type": "Alert",
"props": {
"variant": "default"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "Badge",
"props": {
"variant": "default"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "Button",
"props": {
"variant": "destructive"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "Button",
"props": {
"variant": "outline"
}
}

View File

@@ -0,0 +1,3 @@
{
"type": "CardContent"
}

View File

@@ -0,0 +1,3 @@
{
"type": "CardFooter"
}

View File

@@ -0,0 +1,3 @@
{
"type": "CardHeader"
}

View File

@@ -0,0 +1,3 @@
{
"type": "Card"
}

View File

@@ -0,0 +1,6 @@
{
"type": "div",
"props": {
"className": "flex flex-col"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "div",
"props": {
"className": "flex"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "div",
"props": {
"className": "grid"
}
}

View File

@@ -0,0 +1,7 @@
{
"type": "Heading",
"props": {
"level": 1,
"className": "text-3xl font-bold"
}
}

View File

@@ -0,0 +1,7 @@
{
"type": "Heading",
"props": {
"level": 2,
"className": "text-2xl font-semibold"
}
}

View File

@@ -0,0 +1,7 @@
{
"id": "icon-base",
"type": "Icon",
"props": {
"size": 20
}
}

View File

@@ -0,0 +1,7 @@
{
"id": "icon-folder",
"$ref": "./icon-base.json",
"props": {
"name": "Folder"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "Input",
"props": {
"type": "text"
}
}

View File

@@ -0,0 +1,3 @@
{
"type": "Label"
}

View File

@@ -0,0 +1,7 @@
{
"id": "loading-spinner",
"type": "LoadingSpinner",
"props": {
"size": "md"
}
}

View File

@@ -0,0 +1,3 @@
{
"type": "section"
}

View File

@@ -0,0 +1,6 @@
{
"type": "Separator",
"props": {
"className": "my-4"
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "Text",
"props": {
"className": "text-muted-foreground"
}
}

View File

@@ -0,0 +1,7 @@
{
"id": "text-small",
"type": "Text",
"props": {
"variant": "small"
}
}

View File

@@ -0,0 +1,3 @@
{
"type": "Text"
}

View File

@@ -0,0 +1,6 @@
{
"type": "Textarea",
"props": {
"rows": 4
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "code-editor-page",
"name": "Code Editor",
"description": "Edit your project files",
"icon": "Code",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "code-editor-component",
"type": "CodeEditor",
"props": {}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "component-tree-builder-page",
"name": "Component Tree Builder",
"description": "Build component hierarchies",
"icon": "Tree",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "component-tree-builder-component",
"type": "ComponentTreeBuilder",
"props": {}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "component-tree-manager-page",
"name": "Component Tree Manager",
"description": "Manage component trees",
"icon": "Tree",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "component-tree-manager-component",
"type": "ComponentTreeManager",
"props": {}
}
]
}

View File

@@ -0,0 +1,30 @@
{
"id": "components-page",
"name": "Components",
"description": "Component tree builder",
"icon": "Cube",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [
{
"id": "components",
"$ref": "./data-sources/kv-storage.json",
"key": "components",
"defaultValue": []
}
],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Components"
}
}
]
}
]
}

View File

@@ -0,0 +1,7 @@
{
"id": "button-primary",
"type": "Button",
"props": {
"variant": "default"
}
}

View File

@@ -0,0 +1,7 @@
{
"id": "button-secondary",
"type": "Button",
"props": {
"variant": "secondary"
}
}

View File

@@ -0,0 +1,14 @@
{
"id": "card-container",
"type": "Card",
"children": [
{
"id": "card-header-slot",
"type": "CardHeader"
},
{
"id": "card-content-slot",
"type": "CardContent"
}
]
}

View File

@@ -0,0 +1,17 @@
{
"id": "form-input",
"type": "div",
"props": {
"className": "space-y-2"
},
"children": [
{
"id": "input-label",
"type": "Label"
},
{
"id": "input-field",
"type": "Input"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"id": "grid-2-col",
"type": "div",
"props": {
"className": "grid grid-cols-1 md:grid-cols-2 gap-4"
}
}

View File

@@ -0,0 +1,7 @@
{
"id": "grid-3-col",
"type": "div",
"props": {
"className": "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
}
}

View File

@@ -0,0 +1,24 @@
{
"id": "page-header",
"type": "section",
"props": {
"className": "mb-6"
},
"children": [
{
"id": "page-title",
"type": "Heading",
"props": {
"level": 1,
"className": "text-3xl font-bold"
}
},
{
"id": "page-description",
"type": "Text",
"props": {
"className": "text-muted-foreground mt-2"
}
}
]
}

View File

@@ -0,0 +1,30 @@
{
"id": "stat-card",
"type": "Card",
"props": {
"className": "p-6"
},
"children": [
{
"id": "stat-icon",
"type": "div",
"props": {
"className": "mb-4"
}
},
{
"id": "stat-value",
"type": "div",
"props": {
"className": "text-2xl font-bold"
}
},
{
"id": "stat-label",
"type": "div",
"props": {
"className": "text-sm text-muted-foreground"
}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "conflict-resolution-page",
"name": "Conflict Resolution",
"description": "Resolve merge conflicts",
"icon": "GitMerge",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "conflict-resolution-component",
"type": "ConflictResolutionPage",
"props": {}
}
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "./schema/page-schema.json",
"id": "dashboard-simple",
"name": "Project Dashboard",
"description": "Overview of your project",
"icon": "ChartBar",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [
{
"id": "projectStats",
"$ref": "./data-sources/kv-storage.json",
"key": "project-stats",
"defaultValue": {
"files": 0,
"models": 0,
"components": 0
}
}
],
"components": [
{
"$ref": "./molecules/dashboard-header.json"
},
{
"$ref": "./molecules/stats-grid.json"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "data-binding-page",
"name": "Data Binding",
"description": "Data binding designer",
"icon": "Link",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Data Binding Designer"
}
}
]
}
]
}

View File

@@ -0,0 +1,5 @@
{
"id": "kv-storage",
"type": "kv",
"defaultValue": {}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "docker-build-debugger-page",
"name": "Docker Build Debugger",
"description": "Debug Docker builds",
"icon": "Bug",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "docker-build-debugger-component",
"type": "DockerBuildDebugger",
"props": {}
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "docs-page",
"name": "Documentation",
"description": "Project docs",
"icon": "Book",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Documentation"
}
}
]
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "documentation-view-page",
"name": "Documentation",
"description": "View project documentation",
"icon": "Book",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "documentation-view-component",
"type": "DocumentationView",
"props": {}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "error-panel-page",
"name": "Error Panel",
"description": "View application errors",
"icon": "Warning",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "error-panel-component",
"type": "ErrorPanel",
"props": {}
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "errors-page",
"name": "Errors",
"description": "Error diagnosis",
"icon": "Warning",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Errors & Diagnostics"
}
}
]
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "favicon-designer-page",
"name": "Favicon Designer",
"description": "Design your application favicon",
"icon": "Image",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "favicon-designer-component",
"type": "FaviconDesigner",
"props": {}
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "favicon-page",
"name": "Favicon Designer",
"description": "Design your favicon",
"icon": "Image",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Favicon Designer"
}
}
]
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "feature-idea-cloud-page",
"name": "Feature Ideas",
"description": "Browse and manage feature ideas",
"icon": "Lightbulb",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "feature-idea-cloud-component",
"type": "FeatureIdeaCloud",
"props": {}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "feature-toggle-settings-page",
"name": "Feature Toggle Settings",
"description": "Manage feature toggles",
"icon": "ToggleLeft",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "feature-toggle-settings-component",
"type": "FeatureToggleSettings",
"props": {}
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "features-page",
"name": "Features",
"description": "Feature toggles",
"icon": "ToggleLeft",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Feature Toggles"
}
}
]
}
]
}

View File

@@ -0,0 +1,38 @@
{
"id": "flask-page",
"name": "Flask API",
"description": "Flask API designer",
"icon": "Flask",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [
{
"id": "flaskConfig",
"$ref": "./data-sources/kv-storage.json",
"key": "flask-config",
"defaultValue": {
"blueprints": []
}
}
],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Flask API"
}
},
{
"$ref": "./atoms/text-muted.json",
"props": {
"children": "Design your Flask REST API"
}
}
]
}
]
}

View File

@@ -0,0 +1,5 @@
{
"$ref": "./dashboard-simple.json",
"id": "home-page",
"name": "Home"
}

View File

@@ -0,0 +1,23 @@
{
"id": "ideas-page",
"name": "Feature Ideas",
"description": "Brainstorm features",
"icon": "Lightbulb",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Feature Ideas"
}
}
]
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "json-component-tree-manager-page",
"name": "JSON Component Tree Manager",
"description": "Manage JSON component trees",
"icon": "Tree",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "json-component-tree-manager-component",
"type": "JSONComponentTreeManager",
"props": {}
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "json-ui-page",
"name": "JSON UI Showcase",
"description": "JSON UI components",
"icon": "Code",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [],
"components": [
{
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "JSON UI Showcase"
}
}
]
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "json-ui-showcase-page",
"name": "JSON UI Showcase",
"description": "Showcase of JSON UI components",
"icon": "Stack",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "json-ui-showcase-component",
"type": "JSONUIShowcase",
"props": {}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "lambda-designer-page",
"name": "Lambda Designer",
"description": "Design serverless functions",
"icon": "Function",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "lambda-designer-component",
"type": "JSONLambdaDesigner",
"props": {}
}
]
}

View File

@@ -0,0 +1,54 @@
{
"id": "lambdas-page",
"name": "Lambda Functions",
"description": "Serverless functions",
"icon": "Function",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [
{
"id": "lambdas",
"$ref": "./data-sources/kv-storage.json",
"key": "lambdas",
"defaultValue": []
}
],
"components": [
{
"id": "lambdas-header",
"$ref": "./molecules/page-header-standard.json",
"children": [
{
"$ref": "./atoms/heading-1.json",
"props": {
"children": "Lambda Functions"
}
},
{
"$ref": "./atoms/text-muted.json",
"props": {
"children": "Create serverless backend functions"
}
}
]
},
{
"$ref": "./molecules/toolbar-with-actions.json",
"children": [
{
"id": "toolbar-right",
"type": "div",
"children": [
{
"$ref": "./components/button-primary.json",
"props": {
"children": "New Lambda"
}
}
]
}
]
}
]
}

View File

@@ -0,0 +1,3 @@
{
"type": "single"
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "./schema/page-schema.json",
"id": "model-designer-page",
"name": "Model Designer",
"description": "Design your data models",
"icon": "Database",
"layout": {
"$ref": "./layouts/single-column.json"
},
"components": [
{
"id": "model-designer-component",
"type": "JSONModelDesigner",
"props": {}
}
]
}

View File

@@ -0,0 +1,119 @@
{
"id": "models-page",
"name": "Models Designer",
"description": "Design your database models",
"icon": "Database",
"layout": {
"$ref": "./layouts/single-column.json"
},
"dataSources": [
{
"id": "models",
"$ref": "./data-sources/kv-storage.json",
"key": "models",
"defaultValue": []
}
],
"components": [
{
"id": "models-header",
"type": "div",
"props": {
"className": "p-6"
},
"children": [
{
"id": "models-title",
"type": "Heading",
"props": {
"level": 1,
"children": "Database Models"
}
},
{
"id": "models-description",
"type": "Text",
"props": {
"children": "Define your Prisma database schemas",
"className": "text-muted-foreground mt-2"
}
}
]
},
{
"id": "models-toolbar",
"type": "div",
"props": {
"className": "flex justify-between items-center p-6 border-b"
},
"children": [
{
"id": "model-count",
"type": "Text",
"props": {
"className": "text-sm text-muted-foreground"
},
"children": [
{
"type": "span",
"dataBinding": {
"source": "models",
"transform": "data => `${data.length} models`"
}
}
]
},
{
"id": "add-model-button",
"$ref": "./components/button-primary.json",
"props": {
"children": "Add Model"
},
"events": {
"onClick": {
"actions": [
{
"$ref": "./actions/create-action.json",
"target": "models",
"value": {
"id": "",
"name": "NewModel",
"fields": []
}
}
]
}
}
}
]
},
{
"id": "models-list",
"type": "div",
"props": {
"className": "p-6 space-y-4"
},
"children": [],
"loop": {
"source": "models",
"itemVar": "model",
"template": {
"id": "model-card-${model.id}",
"$ref": "./components/card-container.json",
"children": [
{
"type": "div",
"props": {
"className": "font-semibold"
},
"dataBinding": {
"source": "model",
"path": "name"
}
}
]
}
}
}
]
}

View File

@@ -0,0 +1,17 @@
{
"id": "action-bar",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center gap-2 p-4 border-b bg-muted/30"
},
"children": [
{
"id": "action-primary",
"$ref": "../atoms/button-primary.json"
},
{
"id": "action-secondary",
"$ref": "../atoms/button-secondary.json"
}
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "app-branding",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center gap-3"
},
"children": [
{
"id": "logo",
"$ref": "../atoms/text.json",
"props": {
"className": "text-xl font-bold",
"children": "CodeForge"
}
},
{
"id": "version",
"$ref": "../atoms/badge.json",
"props": {
"children": "v1.0",
"variant": "secondary"
}
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "breadcrumb",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center gap-2 text-sm"
},
"children": [
{
"id": "breadcrumb-home",
"$ref": "../atoms/text-muted.json",
"props": {
"children": "Home"
}
},
{
"id": "breadcrumb-separator",
"$ref": "../atoms/text-muted.json",
"props": {
"children": "/"
}
}
]
}

View File

@@ -0,0 +1,30 @@
{
"type": "Card",
"children": [
{
"id": "card-header",
"type": "CardHeader",
"children": [
{
"id": "card-title",
"type": "CardTitle"
},
{
"id": "card-description",
"type": "CardDescription"
}
]
},
{
"id": "card-content",
"type": "CardContent"
},
{
"id": "card-footer",
"type": "CardFooter",
"props": {
"className": "flex justify-end gap-2"
}
}
]
}

View File

@@ -0,0 +1,24 @@
{
"id": "component-palette",
"$ref": "../atoms/div-flex-col.json",
"props": {
"className": "p-2 space-y-2"
},
"children": [
{
"id": "palette-search",
"$ref": "../atoms/input-text.json",
"props": {
"placeholder": "Search components...",
"className": "mb-2"
}
},
{
"id": "palette-list",
"$ref": "../atoms/div-flex-col.json",
"props": {
"className": "space-y-1"
}
}
]
}

View File

@@ -0,0 +1,7 @@
{
"id": "component-tree",
"$ref": "../atoms/div-flex-col.json",
"props": {
"className": "space-y-1"
}
}

View File

@@ -0,0 +1,24 @@
{
"id": "dashboard-header",
"type": "div",
"props": {
"className": "p-6"
},
"children": [
{
"id": "dashboard-title",
"$ref": "../atoms/heading-1.json",
"props": {
"children": "Project Dashboard"
}
},
{
"id": "dashboard-description",
"$ref": "../atoms/text-muted.json",
"props": {
"children": "Overview of your CodeForge project",
"className": "text-muted-foreground mt-2"
}
}
]
}

View File

@@ -0,0 +1,18 @@
{
"id": "data-card",
"$ref": "../atoms/card.json",
"children": [
{
"$ref": "../atoms/card-header.json",
"children": [
{
"id": "card-title",
"$ref": "../atoms/heading-2.json"
}
]
},
{
"$ref": "../atoms/card-content.json"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"id": "editor-toolbar",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center justify-between border-b p-2 bg-muted/30"
},
"children": [
{
"id": "toolbar-left",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center gap-2"
}
},
{
"id": "toolbar-right",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center gap-2"
}
}
]
}

View File

@@ -0,0 +1,27 @@
{
"id": "empty-editor-state",
"$ref": "./empty-state.json",
"children": [
{
"id": "empty-title",
"$ref": "../atoms/heading-2.json",
"props": {
"children": "No items yet"
}
},
{
"id": "empty-description",
"$ref": "../atoms/text-muted.json",
"props": {
"children": "Get started by creating your first item"
}
},
{
"id": "empty-action",
"$ref": "../atoms/button-primary.json",
"props": {
"children": "Create Item"
}
}
]
}

View File

@@ -0,0 +1,33 @@
{
"type": "div",
"props": {
"className": "flex flex-col items-center justify-center p-12 text-center"
},
"children": [
{
"id": "empty-icon",
"type": "div",
"props": {
"className": "text-muted-foreground mb-4"
}
},
{
"id": "empty-title",
"$ref": "../atoms/heading-2.json",
"props": {
"className": "text-xl font-semibold mb-2"
}
},
{
"id": "empty-description",
"$ref": "../atoms/text-muted.json"
},
{
"id": "empty-action",
"$ref": "../atoms/button-primary.json",
"props": {
"className": "mt-4"
}
}
]
}

View File

@@ -0,0 +1,7 @@
{
"id": "file-tabs",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center gap-1 border-b bg-muted/30 overflow-x-auto"
}
}

View File

@@ -0,0 +1,23 @@
{
"type": "div",
"props": {
"className": "space-y-2"
},
"children": [
{
"id": "field-label",
"type": "Label"
},
{
"id": "field-input",
"$ref": "../atoms/input-text.json"
},
{
"id": "field-error",
"$ref": "../atoms/text-muted.json",
"props": {
"className": "text-destructive text-sm"
}
}
]
}

View File

@@ -0,0 +1,29 @@
{
"id": "label-with-badge",
"type": "div",
"props": {
"className": "flex items-center gap-2"
},
"children": [
{
"id": "label-text",
"$ref": "../atoms/text-small.json",
"props": {
"className": "font-medium"
}
},
{
"id": "badge",
"type": "Badge",
"props": {
"variant": "secondary",
"className": "text-xs"
},
"conditional": {
"source": "badge",
"operator": "neq",
"value": null
}
}
]
}

View File

@@ -0,0 +1,21 @@
{
"id": "loading-state",
"type": "div",
"props": {
"className": "flex flex-col items-center justify-center gap-3 py-12"
},
"children": [
{
"id": "loading-spinner",
"$ref": "../atoms/loading-spinner.json"
},
{
"id": "loading-message",
"$ref": "../atoms/text-muted.json",
"props": {
"className": "text-sm text-muted-foreground",
"children": "Loading..."
}
}
]
}

View File

@@ -0,0 +1,30 @@
{
"id": "page-header-content",
"type": "div",
"props": {
"className": "flex items-start gap-3"
},
"children": [
{
"id": "page-icon",
"$ref": "../atoms/icon-base.json"
},
{
"id": "page-info",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex flex-col gap-1"
},
"children": [
{
"id": "page-title",
"$ref": "../atoms/heading-2.json"
},
{
"id": "page-description",
"$ref": "../atoms/text-muted.json"
}
]
}
]
}

View File

@@ -0,0 +1,19 @@
{
"type": "div",
"props": {
"className": "p-6 border-b"
},
"children": [
{
"id": "page-title",
"$ref": "../atoms/heading-1.json"
},
{
"id": "page-description",
"$ref": "../atoms/text-muted.json",
"props": {
"className": "text-muted-foreground mt-2"
}
}
]
}

View File

@@ -0,0 +1,69 @@
{
"id": "save-indicator",
"dataSources": [
{
"id": "lastSaved",
"type": "static",
"defaultValue": null
},
{
"id": "currentTime",
"type": "static",
"defaultValue": 0,
"polling": {
"interval": 10000,
"update": "() => Date.now()"
}
},
{
"id": "isRecent",
"type": "computed",
"compute": "(data) => { if (!data.lastSaved) return false; return Date.now() - data.lastSaved < 3000; }",
"dependencies": ["lastSaved", "currentTime"]
},
{
"id": "timeAgo",
"type": "computed",
"compute": "(data) => { if (!data.lastSaved) return ''; const seconds = Math.floor((Date.now() - data.lastSaved) / 1000); if (seconds < 60) return 'just now'; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; return `${Math.floor(seconds / 86400)}d ago`; }",
"dependencies": ["lastSaved", "currentTime"]
}
],
"type": "div",
"props": {
"className": "flex items-center gap-1.5 text-xs text-muted-foreground"
},
"conditional": {
"source": "lastSaved",
"operator": "neq",
"value": null
},
"children": [
{
"id": "status-icon",
"type": "StatusIcon",
"dataBinding": {
"type": {
"source": "isRecent",
"transform": "isRecent => isRecent ? 'saved' : 'synced'"
},
"animate": {
"source": "isRecent"
}
}
},
{
"id": "time-text",
"type": "span",
"props": {
"className": "hidden sm:inline"
},
"dataBinding": {
"children": {
"source": "isRecent",
"path": null,
"transform": "(isRecent, data) => isRecent ? 'Saved' : data.timeAgo"
}
}
}
]
}

View File

@@ -0,0 +1,20 @@
{
"id": "schema-editor-status-bar",
"$ref": "../atoms/div-flex.json",
"props": {
"className": "flex items-center justify-between border-t p-2 text-sm bg-muted/30"
},
"children": [
{
"id": "status-left",
"$ref": "../atoms/text-muted.json",
"props": {
"children": "Ready"
}
},
{
"id": "status-right",
"$ref": "../atoms/text-muted.json"
}
]
}

View File

@@ -0,0 +1,78 @@
{
"id": "search-input",
"type": "div",
"props": {
"className": "relative flex items-center"
},
"children": [
{
"id": "search-icon",
"type": "icon",
"props": {
"name": "MagnifyingGlass",
"className": "absolute left-3 text-muted-foreground",
"size": 16
}
},
{
"id": "search-field",
"type": "Input",
"props": {
"placeholder": "Search...",
"className": "pl-9 pr-9"
},
"dataBinding": {
"value": {
"source": "searchValue"
}
},
"events": [
{
"event": "onChange",
"actions": [
{
"type": "setState",
"target": "searchValue",
"valueFrom": "event.target.value"
}
]
}
]
},
{
"id": "clear-button",
"type": "Button",
"props": {
"variant": "ghost",
"size": "sm",
"className": "absolute right-1 h-7 w-7 p-0"
},
"conditional": {
"source": "searchValue",
"operator": "neq",
"value": ""
},
"events": [
{
"event": "onClick",
"actions": [
{
"type": "setState",
"target": "searchValue",
"value": ""
}
]
}
],
"children": [
{
"type": "icon",
"props": {
"name": "X",
"size": 14
}
}
]
}
]
}

View File

@@ -0,0 +1,24 @@
{
"id": "stat-card-base",
"type": "Card",
"props": {
"className": "p-6"
},
"children": [
{
"id": "stat-value",
"type": "div",
"props": {
"className": "text-2xl font-bold",
"children": "0"
}
},
{
"id": "stat-label",
"type": "div",
"props": {
"className": "text-sm text-muted-foreground mt-2"
}
}
]
}

View File

@@ -0,0 +1,26 @@
{
"id": "components-stat",
"$ref": "./stat-card-base.json",
"children": [
{
"id": "components-value",
"type": "div",
"props": {
"className": "text-2xl font-bold",
"children": "0"
},
"dataBinding": {
"source": "projectStats",
"path": "components"
}
},
{
"id": "components-label",
"type": "div",
"props": {
"className": "text-sm text-muted-foreground mt-2",
"children": "React Components"
}
}
]
}

View File

@@ -0,0 +1,26 @@
{
"id": "files-stat",
"$ref": "./stat-card-base.json",
"children": [
{
"id": "files-value",
"type": "div",
"props": {
"className": "text-2xl font-bold",
"children": "0"
},
"dataBinding": {
"source": "projectStats",
"path": "files"
}
},
{
"id": "files-label",
"type": "div",
"props": {
"className": "text-sm text-muted-foreground mt-2",
"children": "Code Files"
}
}
]
}

View File

@@ -0,0 +1,26 @@
{
"id": "models-stat",
"$ref": "./stat-card-base.json",
"children": [
{
"id": "models-value",
"type": "div",
"props": {
"className": "text-2xl font-bold",
"children": "0"
},
"dataBinding": {
"source": "projectStats",
"path": "models"
}
},
{
"id": "models-label",
"type": "div",
"props": {
"className": "text-sm text-muted-foreground mt-2",
"children": "Database Models"
}
}
]
}

View File

@@ -0,0 +1,18 @@
{
"id": "stats-grid",
"type": "div",
"props": {
"className": "grid grid-cols-1 md:grid-cols-3 gap-4 p-6"
},
"children": [
{
"$ref": "./stat-card-files.json"
},
{
"$ref": "./stat-card-models.json"
},
{
"$ref": "./stat-card-components.json"
}
]
}

Some files were not shown because too many files have changed in this diff Show More