feat: Migrate 5 key molecules to JSON architecture

Converted AppBranding, DataSourceCard, CodeExplanationDialog, ComponentPalette, and CanvasRenderer from TSX to JSON-based architecture.

Changes:
- Created JSON definitions in src/components/json-definitions/
- Created TypeScript interfaces in src/lib/json-ui/interfaces/
- Added exports to src/lib/json-ui/json-components.ts
- Updated json-components-registry.json with 5 new components
- Updated registry statistics (total: 347, molecules: 50, jsonCompatible: 124)

All components use createJsonComponent for pure JSON rendering without hooks.
Build passes with no errors.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 02:25:05 +00:00
parent 8c1a8486c7
commit 53c8a72c0f
19 changed files with 2406 additions and 430 deletions

View File

@@ -0,0 +1,31 @@
{
"id": "app-branding-container",
"type": "Flex",
"props": {
"gap": "2",
"align": "center"
},
"children": [
{
"id": "app-branding-logo",
"type": "AppLogo",
"props": {
"size": "sm"
}
},
{
"id": "app-branding-text",
"type": "Heading",
"props": {
"level": 1,
"className": "font-semibold text-lg"
},
"children": [
{
"type": "text",
"content": "Spark"
}
]
}
]
}

View File

@@ -0,0 +1,99 @@
{
"id": "canvas-renderer",
"type": "Div",
"props": {
"className": "h-full w-full overflow-auto p-8 bg-gradient-to-br from-background via-muted/10 to-muted/20"
},
"children": [
{
"id": "canvas-main",
"type": "Div",
"props": {
"className": "min-h-full bg-card border border-border rounded-lg shadow-lg p-8"
},
"children": [
{
"id": "canvas-content",
"type": "ConditionalRender",
"props": {
"condition": {
"source": "components",
"transform": "data && data.length > 0"
}
},
"children": [
{
"id": "canvas-components",
"type": "Div",
"props": {
"className": "space-y-4"
},
"bindings": {
"children": {
"source": "components",
"transform": "data ? data.map(comp => ({id: `canvas-comp-${comp.id}`, type: 'CanvasRendererItem', props: {component: comp, selectedId: window.selectedId, hoveredId: window.hoveredId, draggedOverId: window.draggedOverId, dropPosition: window.dropPosition, onSelect: window.onSelect, onHover: window.onHover, onHoverEnd: window.onHoverEnd, onDragOver: window.onDragOver, onDragLeave: window.onDragLeave, onDrop: window.onDrop}})) : []"
}
}
}
]
},
{
"id": "canvas-empty",
"type": "ConditionalRender",
"props": {
"condition": {
"source": "components",
"transform": "!data || data.length === 0"
}
},
"children": [
{
"id": "canvas-empty-state",
"type": "Div",
"props": {
"className": "h-96 flex items-center justify-center text-muted-foreground border-2 border-dashed border-border rounded-lg"
},
"children": [
{
"id": "canvas-empty-content",
"type": "Div",
"props": {
"className": "text-center"
},
"children": [
{
"id": "canvas-empty-title",
"type": "Paragraph",
"props": {
"className": "text-lg font-medium mb-2"
},
"children": [
{
"type": "text",
"content": "Drop components here"
}
]
},
{
"id": "canvas-empty-subtitle",
"type": "Paragraph",
"props": {
"className": "text-sm"
},
"children": [
{
"type": "text",
"content": "Drag components from the palette to start building"
}
]
}
]
}
]
}
]
}
]
}
]
}

View File

@@ -0,0 +1,126 @@
{
"id": "code-explanation-dialog",
"type": "Dialog",
"props": {
"open": {
"source": "open"
},
"onOpenChange": {
"action": "call",
"target": "onOpenChange"
}
},
"children": [
{
"id": "code-explanation-content",
"type": "DialogContent",
"props": {
"className": "max-w-2xl"
},
"children": [
{
"id": "code-explanation-header",
"type": "DialogHeader",
"children": [
{
"id": "code-explanation-title",
"type": "DialogTitle",
"children": [
{
"type": "text",
"content": "Code Explanation"
}
]
},
{
"id": "code-explanation-description",
"type": "DialogDescription",
"bindings": {
"children": {
"source": "fileName",
"transform": "data ? `AI-generated explanation of ${data}` : 'AI-generated explanation'"
}
}
}
]
},
{
"id": "code-explanation-scroll",
"type": "ScrollArea",
"props": {
"className": "max-h-96"
},
"children": [
{
"id": "code-explanation-content-box",
"type": "Div",
"props": {
"className": "p-4 bg-muted rounded-lg"
},
"children": [
{
"id": "code-explanation-content-conditional",
"type": "ConditionalRender",
"props": {
"condition": {
"source": "isLoading"
}
},
"children": [
{
"id": "code-explanation-loading",
"type": "Flex",
"props": {
"align": "center",
"gap": "2",
"className": "text-muted-foreground"
},
"children": [
{
"id": "code-explanation-loading-icon",
"type": "Icon",
"props": {
"name": "Sparkle",
"className": "animate-pulse"
}
},
{
"type": "text",
"content": "Analyzing code..."
}
]
}
]
},
{
"id": "code-explanation-content-text",
"type": "ConditionalRender",
"props": {
"condition": {
"source": "isLoading",
"transform": "!data"
}
},
"children": [
{
"id": "code-explanation-text",
"type": "Paragraph",
"props": {
"className": "whitespace-pre-wrap text-sm"
},
"bindings": {
"children": {
"source": "explanation"
}
}
}
]
}
]
}
]
}
]
}
]
}

View File

@@ -0,0 +1,99 @@
{
"id": "component-palette",
"type": "Stack",
"props": {
"direction": "vertical",
"className": "h-full"
},
"children": [
{
"id": "component-palette-header",
"type": "Div",
"props": {
"className": "p-4"
},
"children": [
{
"id": "component-palette-panel-header",
"type": "PanelHeader",
"props": {
"title": "Components",
"subtitle": "Drag components to the canvas",
"icon": "Package",
"showSeparator": false
}
}
]
},
{
"id": "component-palette-tabs",
"type": "Tabs",
"props": {
"defaultValue": "layout",
"className": "flex-1 flex flex-col border-t border-border"
},
"children": [
{
"id": "component-palette-tab-list",
"type": "TabsList",
"props": {
"className": "w-full justify-start px-4 pt-2"
},
"bindings": {
"children": {
"source": "categories",
"transform": "data ? data.map(cat => ({id: `tab-trigger-${cat.id}`, type: 'TabsTrigger', props: {value: cat.id, className: 'text-xs'}, children: [{type: 'text', content: cat.label}]})) : []"
}
}
},
{
"id": "component-palette-content",
"type": "Map",
"props": {
"items": {
"source": "categories"
},
"keyField": "id",
"itemVariable": "category"
},
"children": [
{
"id": "component-palette-tab-content",
"type": "TabsContent",
"props": {
"value": {
"source": "category.id"
},
"className": "flex-1 m-0 mt-2"
},
"children": [
{
"id": "component-palette-scroll",
"type": "ScrollArea",
"props": {
"className": "h-full px-4 pb-4"
},
"children": [
{
"id": "component-palette-grid",
"type": "Div",
"props": {
"className": "grid grid-cols-2 gap-2"
},
"bindings": {
"children": {
"source": "category.id",
"transform": "data ? window.getCategoryComponents(data).map(comp => ({id: `palette-item-${comp.type}`, type: 'ComponentPaletteItem', props: {component: comp}, events: {onDragStart: {action: 'call', target: 'onDragStart', args: [comp]}}})) : []"
}
}
}
]
}
]
}
]
}
]
}
]
}

View File

@@ -0,0 +1,187 @@
{
"id": "data-source-card",
"type": "Card",
"props": {
"className": "bg-card/50 backdrop-blur hover:bg-card/70 transition-colors"
},
"children": [
{
"id": "data-source-card-content",
"type": "Div",
"props": {
"className": "p-4"
},
"children": [
{
"id": "data-source-card-flex",
"type": "Flex",
"props": {
"justify": "between",
"align": "start",
"gap": "md"
},
"children": [
{
"id": "data-source-card-info",
"type": "Stack",
"props": {
"className": "flex-1 min-w-0"
},
"children": [
{
"id": "data-source-badge-row",
"type": "Flex",
"props": {
"align": "center",
"gap": "sm"
},
"children": [
{
"id": "data-source-badge",
"type": "DataSourceBadge",
"props": {
"type": {
"source": "dataSource.type"
}
}
},
{
"id": "data-source-id",
"type": "Text",
"props": {
"variant": "small",
"className": "font-mono font-medium truncate"
},
"bindings": {
"children": {
"source": "dataSource.id"
}
}
}
]
},
{
"id": "data-source-type-info",
"type": "ConditionalRender",
"props": {
"condition": {
"source": "dataSource.type",
"transform": "data === 'kv'"
}
},
"children": [
{
"id": "kv-info-text",
"type": "Text",
"props": {
"variant": "caption",
"className": "font-mono bg-muted/30 px-2 py-1 rounded"
},
"bindings": {
"children": {
"source": "dataSource.key",
"transform": "data ? `Key: ${data}` : 'Key: Not set'"
}
}
}
]
},
{
"id": "data-source-dependents",
"type": "ConditionalRender",
"props": {
"condition": {
"source": "dependents",
"transform": "data && data.length > 0"
}
},
"children": [
{
"id": "dependents-border",
"type": "Div",
"props": {
"className": "pt-2 border-t border-border/50"
},
"children": [
{
"id": "dependents-text",
"type": "Text",
"props": {
"variant": "caption"
},
"bindings": {
"children": {
"source": "dependents",
"transform": "data && data.length > 0 ? `Used by ${data.length} dependent ${data.length === 1 ? 'source' : 'sources'}` : ''"
}
}
}
]
}
]
}
]
},
{
"id": "data-source-actions",
"type": "Flex",
"props": {
"align": "center",
"gap": "xs"
},
"children": [
{
"id": "data-source-edit-btn",
"type": "IconButton",
"props": {
"variant": "ghost",
"size": "sm",
"icon": "Pencil"
},
"events": {
"onClick": {
"action": "call",
"target": "onEdit",
"args": [
{
"source": "dataSource.id"
}
]
}
}
},
{
"id": "data-source-delete-btn",
"type": "IconButton",
"props": {
"variant": "ghost",
"size": "sm",
"icon": "Trash",
"className": "text-destructive hover:text-destructive"
},
"bindings": {
"disabled": {
"source": "dependents",
"transform": "data && data.length > 0"
}
},
"events": {
"onClick": {
"action": "call",
"target": "onDelete",
"args": [
{
"source": "dataSource.id"
}
]
}
}
}
]
}
]
}
]
}
]
}

View File

@@ -0,0 +1,51 @@
{
"id": "loading-screen-container",
"type": "div",
"bindings": {
"className": {
"source": null,
"transform": "'fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm'"
}
},
"children": [
{
"id": "loading-screen-content",
"type": "div",
"bindings": {
"className": {
"source": null,
"transform": "'flex flex-col items-center justify-center gap-4'"
}
},
"children": [
{
"id": "loading-screen-spinner",
"type": "Loader2",
"bindings": {
"className": {
"source": null,
"transform": "'animate-spin text-primary'"
}
},
"props": {
"size": 48
}
},
{
"id": "loading-screen-text",
"type": "p",
"bindings": {
"className": {
"source": null,
"transform": "'text-sm font-medium text-muted-foreground'"
},
"children": {
"source": "message",
"transform": "data || 'Loading...'"
}
}
}
]
}
]
}

View File

@@ -0,0 +1,6 @@
export interface AppBrandingProps {
/**
* CSS class name for custom styling
*/
className?: string
}

View File

@@ -0,0 +1,58 @@
import { UIComponent } from '@/types/json-ui'
export interface CanvasRendererProps {
/**
* Array of components to render on the canvas
*/
components: UIComponent[]
/**
* ID of the currently selected component
*/
selectedId: string | null
/**
* ID of the currently hovered component
*/
hoveredId: string | null
/**
* ID of the component being dragged over
*/
draggedOverId: string | null
/**
* Position where a drop will occur (before, after, or inside component)
*/
dropPosition: 'before' | 'after' | 'inside' | null
/**
* Callback when a component is selected
*/
onSelect: (id: string) => void
/**
* Callback when mouse enters a component
*/
onHover: (id: string) => void
/**
* Callback when mouse leaves a component
*/
onHoverEnd: () => void
/**
* Callback when dragging over a component
*/
onDragOver: (id: string, e: React.DragEvent) => void
/**
* Callback when dragging leaves a component
*/
onDragLeave: (e: React.DragEvent) => void
/**
* Callback when dropping on a component
*/
onDrop: (id: string, e: React.DragEvent) => void
}

View File

@@ -0,0 +1,26 @@
export interface CodeExplanationDialogProps {
/**
* Whether the dialog is open
*/
open: boolean
/**
* Callback when open state changes
*/
onOpenChange: (open: boolean) => void
/**
* File name being explained
*/
fileName?: string
/**
* The code explanation text
*/
explanation: string
/**
* Whether the explanation is being loaded
*/
isLoading: boolean
}

View File

@@ -0,0 +1,16 @@
import { ComponentDefinition } from '@/lib/component-definition-types'
export interface ComponentPaletteProps {
/**
* Callback when a component drag starts
*/
onDragStart: (component: ComponentDefinition, e: React.DragEvent) => void
/**
* Array of component categories
*/
categories?: Array<{
id: string
label: string
}>
}

View File

@@ -0,0 +1,23 @@
import { DataSource } from '@/types/json-ui'
export interface DataSourceCardProps {
/**
* The data source to display
*/
dataSource: DataSource
/**
* Data sources that depend on this one
*/
dependents?: DataSource[]
/**
* Callback when edit button is clicked
*/
onEdit: (id: string) => void
/**
* Callback when delete button is clicked
*/
onDelete: (id: string) => void
}

View File

@@ -117,3 +117,8 @@ export * from './metric-display'
export * from './modal'
export * from './notification'
export * from './number-input'
export * from './app-branding'
export * from './data-source-card'
export * from './code-explanation-dialog'
export * from './component-palette'
export * from './canvas-renderer'

View File

@@ -0,0 +1,3 @@
export interface LoadingScreenProps {
message?: string
}

View File

@@ -126,6 +126,11 @@ import type {
ModalProps,
NotificationProps,
NumberInputProps,
AppBrandingProps,
DataSourceCardProps,
CodeExplanationDialogProps,
ComponentPaletteProps,
CanvasRendererProps,
} from './interfaces'
// Import JSON definitions
@@ -248,6 +253,11 @@ import metricDisplayDef from '@/components/json-definitions/metric-display.json'
import modalDef from '@/components/json-definitions/modal.json'
import notificationDef from '@/components/json-definitions/notification.json'
import numberInputDef from '@/components/json-definitions/number-input.json'
import appBrandingDef from '@/components/json-definitions/app-branding.json'
import dataSourceCardDef from '@/components/json-definitions/data-source-card.json'
import codeExplanationDialogDef from '@/components/json-definitions/code-explanation-dialog.json'
import componentPaletteDef from '@/components/json-definitions/component-palette.json'
import canvasRendererDef from '@/components/json-definitions/canvas-renderer.json'
// Create pure JSON components (no hooks)
export const BindingIndicator = createJsonComponent<BindingIndicatorProps>(bindingIndicatorDef)
@@ -348,6 +358,11 @@ export const MetricDisplay = createJsonComponent<MetricDisplayProps>(metricDispl
export const Modal = createJsonComponent<ModalProps>(modalDef)
export const Notification = createJsonComponent<NotificationProps>(notificationDef)
export const NumberInput = createJsonComponent<NumberInputProps>(numberInputDef)
export const AppBranding = createJsonComponent<AppBrandingProps>(appBrandingDef)
export const DataSourceCard = createJsonComponent<DataSourceCardProps>(dataSourceCardDef)
export const CodeExplanationDialog = createJsonComponent<CodeExplanationDialogProps>(codeExplanationDialogDef)
export const ComponentPalette = createJsonComponent<ComponentPaletteProps>(componentPaletteDef)
export const CanvasRenderer = createJsonComponent<CanvasRendererProps>(canvasRendererDef)
// Create JSON components with hooks
export const SaveIndicator = createJsonComponentWithHooks<SaveIndicatorProps>(saveIndicatorDef, {

View File

@@ -337,6 +337,12 @@ export const jsonUIComponentTypes = [
"AppRouterLayout",
"AppMainPanel",
"AppDialogs",
"single",
"kv",
"create",
"delete",
"navigate",
"update",
] as const
export type JSONUIComponentType = typeof jsonUIComponentTypes[number]