From 4f7540909627e9edb2012597d36c4f00fc749b65 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Wed, 21 Jan 2026 02:28:50 +0000 Subject: [PATCH] feat: Migrate 4 key organisms to JSON architecture Migrated EmptyCanvasState, SchemaCodeViewer, ToolbarActions, and AppHeader from TSX to JSON-based components. These are display-focused organisms with no complex state management requirements. Changes: - Create JSON definitions for 4 organisms in src/components/json-definitions/ - Create TypeScript interfaces in src/lib/json-ui/interfaces/ - Add exports to src/lib/json-ui/json-components.ts - Update interfaces/index.ts to export new interfaces - Registry entries already marked as jsonCompatible: true All organisms are pure JSON components (no custom hooks needed): - EmptyCanvasState: Display component with optional action buttons - SchemaCodeViewer: Display component showing JSON schema with tabs - ToolbarActions: Flex container with conditional toolbar buttons - AppHeader: Complex header layout with multiple sub-components Build passing with no errors. Co-Authored-By: Claude Haiku 4.5 --- .../json-definitions/app-header.json | 146 +++++++++++++++ .../json-definitions/empty-canvas-state.json | 94 ++++++++++ .../json-definitions/schema-code-viewer.json | 118 ++++++++++++ .../json-definitions/toolbar-actions.json | 172 ++++++++++++++++++ src/lib/json-ui/interfaces/app-header.ts | 17 ++ .../json-ui/interfaces/empty-canvas-state.ts | 4 + src/lib/json-ui/interfaces/index.ts | 4 + .../json-ui/interfaces/schema-code-viewer.ts | 6 + src/lib/json-ui/interfaces/toolbar-actions.ts | 10 + src/lib/json-ui/json-components.ts | 12 ++ 10 files changed, 583 insertions(+) create mode 100644 src/components/json-definitions/app-header.json create mode 100644 src/components/json-definitions/empty-canvas-state.json create mode 100644 src/components/json-definitions/schema-code-viewer.json create mode 100644 src/components/json-definitions/toolbar-actions.json create mode 100644 src/lib/json-ui/interfaces/app-header.ts create mode 100644 src/lib/json-ui/interfaces/empty-canvas-state.ts create mode 100644 src/lib/json-ui/interfaces/schema-code-viewer.ts create mode 100644 src/lib/json-ui/interfaces/toolbar-actions.ts diff --git a/src/components/json-definitions/app-header.json b/src/components/json-definitions/app-header.json new file mode 100644 index 0000000..2f97cfba --- /dev/null +++ b/src/components/json-definitions/app-header.json @@ -0,0 +1,146 @@ +{ + "id": "app-header", + "type": "header", + "bindings": { + "className": { "value": "border-b border-border bg-card" } + }, + "children": [ + { + "id": "header-stack", + "type": "Stack", + "props": { + "direction": "vertical", + "spacing": "none" + }, + "children": [ + { + "id": "header-top", + "type": "Stack", + "props": { + "className": "px-4 sm:px-6 py-3 sm:py-4" + }, + "children": [ + { + "id": "header-flex", + "type": "Flex", + "props": { + "justify": "between", + "align": "center", + "gap": "sm" + }, + "children": [ + { + "id": "header-left", + "type": "Flex", + "props": { + "align": "center", + "gap": "sm", + "className": "flex-1 min-w-0" + }, + "children": [ + { + "id": "sidebar-trigger", + "type": "SidebarTrigger" + }, + { + "id": "app-branding", + "type": "AppBranding" + }, + { + "id": "save-indicator", + "type": "SaveIndicator", + "bindings": { + "lastSaved": { "source": "lastSaved" } + } + } + ] + }, + { + "id": "header-right", + "type": "Flex", + "props": { + "gap": "xs", + "shrink": true, + "className": "shrink-0" + }, + "children": [ + { + "id": "project-manager", + "type": "ProjectManager", + "bindings": { + "currentProject": { "source": "currentProject" } + }, + "events": { + "onProjectLoad": { + "type": "callback", + "name": "onProjectLoad" + } + } + }, + { + "id": "toolbar-actions", + "type": "ToolbarActions", + "bindings": { + "errorCount": { "source": "errorCount" }, + "showErrorButton": { + "source": "featureToggles,errorCount", + "transform": "data && data.featureToggles && data.featureToggles.errorRepair && data.errorCount > 0" + } + }, + "events": { + "onSearch": { + "type": "callback", + "name": "onSearch" + }, + "onShowShortcuts": { + "type": "callback", + "name": "onShowShortcuts" + }, + "onGenerateAI": { + "type": "callback", + "name": "onGenerateAI" + }, + "onExport": { + "type": "callback", + "name": "onExport" + }, + "onPreview": { + "type": "callback", + "name": "onPreview" + }, + "onShowErrors": { + "type": "callback", + "name": "onShowErrors" + } + } + } + ] + } + ] + } + ] + }, + { + "id": "separator", + "type": "Separator", + "props": { + "className": "opacity-50" + } + }, + { + "id": "header-bottom", + "type": "Stack", + "props": { + "className": "px-4 sm:px-6 py-2" + }, + "children": [ + { + "id": "breadcrumb", + "type": "Breadcrumb" + } + ] + } + ] + } + ] +} diff --git a/src/components/json-definitions/empty-canvas-state.json b/src/components/json-definitions/empty-canvas-state.json new file mode 100644 index 0000000..4b495bc --- /dev/null +++ b/src/components/json-definitions/empty-canvas-state.json @@ -0,0 +1,94 @@ +{ + "id": "empty-canvas-state", + "type": "Stack", + "bindings": { + "direction": { "value": "vertical" }, + "spacing": { "value": "none" }, + "className": { "value": "h-full flex flex-col items-center justify-center p-8 bg-muted/20" } + }, + "children": [ + { + "id": "empty-state", + "type": "EmptyState", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Folder", + "size": 64, + "weight": "duotone" + } + }, + "title": "Empty Canvas", + "description": "Start building your UI by dragging components from the left panel, or import an existing schema." + }, + "children": [ + { + "id": "actions-container", + "type": "Stack", + "props": { + "direction": "horizontal", + "spacing": "md", + "className": "mt-4" + }, + "children": [ + { + "id": "import-button", + "type": "ActionButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Folder", + "size": 16 + } + }, + "label": "Import Schema", + "variant": "outline" + }, + "events": { + "onClick": { + "type": "callback", + "name": "onImportSchema" + } + }, + "bindings": { + "hidden": { + "source": "!onImportSchema", + "transform": "!data" + } + } + }, + { + "id": "add-button", + "type": "ActionButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Plus", + "size": 16 + } + }, + "label": "Add Component", + "variant": "default" + }, + "events": { + "onClick": { + "type": "callback", + "name": "onAddFirstComponent" + } + }, + "bindings": { + "hidden": { + "source": "!onAddFirstComponent", + "transform": "!data" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/components/json-definitions/schema-code-viewer.json b/src/components/json-definitions/schema-code-viewer.json new file mode 100644 index 0000000..baf5456 --- /dev/null +++ b/src/components/json-definitions/schema-code-viewer.json @@ -0,0 +1,118 @@ +{ + "id": "schema-code-viewer", + "type": "Stack", + "bindings": { + "direction": { "value": "vertical" }, + "spacing": { "value": "none" }, + "className": { "value": "h-full flex flex-col bg-card" } + }, + "children": [ + { + "id": "panel-header", + "type": "PanelHeader", + "props": { + "title": "Schema Output", + "icon": { + "type": "Icon", + "props": { + "name": "Code", + "size": 20, + "weight": "duotone" + } + } + } + }, + { + "id": "tabs", + "type": "Tabs", + "props": { + "defaultValue": "json", + "className": "flex-1 flex flex-col" + }, + "children": [ + { + "id": "tabs-list", + "type": "TabsList", + "props": { + "className": "w-full justify-start px-4 pt-2" + }, + "children": [ + { + "id": "json-trigger", + "type": "TabsTrigger", + "props": { + "value": "json", + "children": "JSON" + } + }, + { + "id": "preview-trigger", + "type": "TabsTrigger", + "props": { + "value": "preview", + "children": "Preview" + } + } + ] + }, + { + "id": "json-content", + "type": "TabsContent", + "props": { + "value": "json", + "className": "flex-1 m-0 mt-2" + }, + "children": [ + { + "id": "scroll-area", + "type": "ScrollArea", + "props": { + "className": "h-full" + }, + "children": [ + { + "id": "code-block", + "type": "Code", + "bindings": { + "children": { + "source": "schema", + "transform": "data ? JSON.stringify(data, null, 2) : ''" + }, + "className": { "value": "p-4 text-xs font-mono text-foreground" } + } + } + ] + } + ] + }, + { + "id": "preview-content", + "type": "TabsContent", + "props": { + "value": "preview", + "className": "flex-1 m-0 mt-2" + }, + "children": [ + { + "id": "preview-text", + "type": "Stack", + "props": { + "className": "p-4" + }, + "children": [ + { + "id": "preview-message", + "type": "Text", + "props": { + "variant": "muted", + "children": "Live preview coming soon" + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/components/json-definitions/toolbar-actions.json b/src/components/json-definitions/toolbar-actions.json new file mode 100644 index 0000000..7e19619 --- /dev/null +++ b/src/components/json-definitions/toolbar-actions.json @@ -0,0 +1,172 @@ +{ + "id": "toolbar-actions", + "type": "Flex", + "bindings": { + "gap": { "value": "xs" }, + "shrink": { "value": true }, + "className": { "value": "shrink-0" } + }, + "children": [ + { + "id": "search-button", + "type": "ToolbarButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "MagnifyingGlass", + "size": 18 + } + }, + "label": "Search (Ctrl+K)", + "data-search-trigger": true + }, + "events": { + "onClick": { + "type": "callback", + "name": "onSearch" + } + } + }, + { + "id": "error-button-wrapper", + "type": "Stack", + "bindings": { + "hidden": { + "source": "showErrorButton,errorCount", + "transform": "!(data.showErrorButton && data.errorCount > 0)" + } + }, + "children": [ + { + "id": "error-button", + "type": "ToolbarButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Wrench", + "size": 18 + } + }, + "variant": "outline", + "className": "border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground relative" + }, + "bindings": { + "label": { + "source": "errorCount", + "transform": "data ? `${data} ${data === 1 ? 'Error' : 'Errors'}` : 'Errors'" + } + }, + "events": { + "onClick": { + "type": "callback", + "name": "onShowErrors" + } + } + }, + { + "id": "error-badge", + "type": "ErrorBadge", + "bindings": { + "count": { "source": "errorCount" } + }, + "props": { + "size": "sm" + } + } + ] + }, + { + "id": "preview-button", + "type": "ToolbarButton", + "bindings": { + "hidden": { + "source": "!onPreview", + "transform": "!data" + } + }, + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Eye", + "size": 18 + } + }, + "label": "Preview (Ctrl+P)", + "variant": "outline" + }, + "events": { + "onClick": { + "type": "callback", + "name": "onPreview" + } + } + }, + { + "id": "shortcuts-button", + "type": "ToolbarButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Keyboard", + "size": 18 + } + }, + "label": "Keyboard Shortcuts (Ctrl+/)", + "variant": "ghost", + "className": "hidden sm:flex" + }, + "events": { + "onClick": { + "type": "callback", + "name": "onShowShortcuts" + } + } + }, + { + "id": "ai-button", + "type": "ToolbarButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Sparkle", + "size": 18, + "weight": "duotone" + } + }, + "label": "AI Generate (Ctrl+Shift+G)" + }, + "events": { + "onClick": { + "type": "callback", + "name": "onGenerateAI" + } + } + }, + { + "id": "export-button", + "type": "ToolbarButton", + "props": { + "icon": { + "type": "Icon", + "props": { + "name": "Download", + "size": 18 + } + }, + "label": "Export Project (Ctrl+E)", + "variant": "default" + }, + "events": { + "onClick": { + "type": "callback", + "name": "onExport" + } + } + } + ] +} diff --git a/src/lib/json-ui/interfaces/app-header.ts b/src/lib/json-ui/interfaces/app-header.ts new file mode 100644 index 0000000..77b117c --- /dev/null +++ b/src/lib/json-ui/interfaces/app-header.ts @@ -0,0 +1,17 @@ +import type { FeatureToggles, Project } from '@/types/project' + +export interface AppHeaderProps { + activeTab: string + onTabChange: (tab: string) => void + featureToggles: FeatureToggles + errorCount: number + lastSaved: number | null + currentProject: Project + onProjectLoad: (project: Project) => void + onSearch: () => void + onShowShortcuts: () => void + onGenerateAI: () => void + onExport: () => void + onPreview?: () => void + onShowErrors: () => void +} diff --git a/src/lib/json-ui/interfaces/empty-canvas-state.ts b/src/lib/json-ui/interfaces/empty-canvas-state.ts new file mode 100644 index 0000000..02a5bd3 --- /dev/null +++ b/src/lib/json-ui/interfaces/empty-canvas-state.ts @@ -0,0 +1,4 @@ +export interface EmptyCanvasStateProps { + onAddFirstComponent?: () => void + onImportSchema?: () => void +} diff --git a/src/lib/json-ui/interfaces/index.ts b/src/lib/json-ui/interfaces/index.ts index c8915a7..9f8c352 100644 --- a/src/lib/json-ui/interfaces/index.ts +++ b/src/lib/json-ui/interfaces/index.ts @@ -122,3 +122,7 @@ export * from './data-source-card' export * from './code-explanation-dialog' export * from './component-palette' export * from './canvas-renderer' +export * from './empty-canvas-state' +export * from './schema-code-viewer' +export * from './toolbar-actions' +export * from './app-header' diff --git a/src/lib/json-ui/interfaces/schema-code-viewer.ts b/src/lib/json-ui/interfaces/schema-code-viewer.ts new file mode 100644 index 0000000..553ae41 --- /dev/null +++ b/src/lib/json-ui/interfaces/schema-code-viewer.ts @@ -0,0 +1,6 @@ +import type { UIComponent } from '@/types/json-ui' + +export interface SchemaCodeViewerProps { + components: UIComponent[] + schema: any +} diff --git a/src/lib/json-ui/interfaces/toolbar-actions.ts b/src/lib/json-ui/interfaces/toolbar-actions.ts new file mode 100644 index 0000000..0a27df6 --- /dev/null +++ b/src/lib/json-ui/interfaces/toolbar-actions.ts @@ -0,0 +1,10 @@ +export interface ToolbarActionsProps { + onSearch: () => void + onShowShortcuts: () => void + onGenerateAI: () => void + onExport: () => void + onPreview?: () => void + onShowErrors?: () => void + errorCount?: number + showErrorButton?: boolean +} diff --git a/src/lib/json-ui/json-components.ts b/src/lib/json-ui/json-components.ts index afb06c8..db969a9 100644 --- a/src/lib/json-ui/json-components.ts +++ b/src/lib/json-ui/json-components.ts @@ -131,6 +131,10 @@ import type { CodeExplanationDialogProps, ComponentPaletteProps, CanvasRendererProps, + EmptyCanvasStateProps, + SchemaCodeViewerProps, + ToolbarActionsProps, + AppHeaderProps, } from './interfaces' // Import JSON definitions @@ -258,6 +262,10 @@ import dataSourceCardDef from '@/components/json-definitions/data-source-card.js 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' +import emptyCanvasStateDef from '@/components/json-definitions/empty-canvas-state.json' +import schemaCodeViewerDef from '@/components/json-definitions/schema-code-viewer.json' +import toolbarActionsDef from '@/components/json-definitions/toolbar-actions.json' +import appHeaderDef from '@/components/json-definitions/app-header.json' // Create pure JSON components (no hooks) export const BindingIndicator = createJsonComponent(bindingIndicatorDef) @@ -363,6 +371,10 @@ export const DataSourceCard = createJsonComponent(dataSourc export const CodeExplanationDialog = createJsonComponent(codeExplanationDialogDef) export const ComponentPalette = createJsonComponent(componentPaletteDef) export const CanvasRenderer = createJsonComponent(canvasRendererDef) +export const EmptyCanvasState = createJsonComponent(emptyCanvasStateDef) +export const SchemaCodeViewer = createJsonComponent(schemaCodeViewerDef) +export const ToolbarActions = createJsonComponent(toolbarActionsDef) +export const AppHeader = createJsonComponent(appHeaderDef) // Create JSON components with hooks export const SaveIndicator = createJsonComponentWithHooks(saveIndicatorDef, {