From 9f8baa835e4c068187e1d86aacec4ba9380bcd74 Mon Sep 17 00:00:00 2001 From: Guilherme Rodrigues Date: Tue, 2 Dec 2025 21:58:23 -0300 Subject: [PATCH] Plans for admin-cx in mesh stack --- apps/cms/PLAN.md | 124 +++++++ apps/cms/index.html | 15 + apps/cms/package.json | 40 +++ apps/cms/src/PLAN.md | 79 +++++ apps/cms/src/components/PLAN.md | 79 +++++ apps/cms/src/components/editor/PLAN.md | 126 +++++++ .../src/components/editor/json-schema/PLAN.md | 313 ++++++++++++++++ .../cms/src/components/editor/preview/PLAN.md | 261 ++++++++++++++ apps/cms/src/components/shell/PLAN.md | 134 +++++++ apps/cms/src/components/spaces/PLAN.md | 94 +++++ .../cms/src/components/spaces/actions/PLAN.md | 227 ++++++++++++ .../src/components/spaces/analytics/PLAN.md | 201 +++++++++++ apps/cms/src/components/spaces/apps/PLAN.md | 180 ++++++++++ apps/cms/src/components/spaces/assets/PLAN.md | 224 ++++++++++++ .../cms/src/components/spaces/loaders/PLAN.md | 213 +++++++++++ apps/cms/src/components/spaces/pages/PLAN.md | 168 +++++++++ .../src/components/spaces/releases/PLAN.md | 295 ++++++++++++++++ .../src/components/spaces/sections/PLAN.md | 159 +++++++++ .../src/components/spaces/settings/PLAN.md | 306 ++++++++++++++++ apps/cms/src/hooks/PLAN.md | 247 +++++++++++++ apps/cms/src/main.tsx | 209 +++++++++++ apps/cms/src/providers/PLAN.md | 221 ++++++++++++ apps/cms/src/routes/PLAN.md | 180 ++++++++++ apps/cms/vite.config.ts | 26 ++ packages/cms-sdk/PLAN.md | 334 ++++++++++++++++++ packages/cms-sdk/package.json | 52 +++ packages/cms-sdk/src/blocks/index.ts | 177 ++++++++++ packages/cms-sdk/src/daemon/index.ts | 233 ++++++++++++ packages/cms-sdk/src/index.ts | 41 +++ packages/cms-sdk/src/preview/index.ts | 178 ++++++++++ packages/cms-sdk/src/schema/index.ts | 218 ++++++++++++ packages/cms-sdk/tsconfig.json | 24 ++ plans/CMS_MIGRATION.md | 218 ++++++++++++ 33 files changed, 5596 insertions(+) create mode 100644 apps/cms/PLAN.md create mode 100644 apps/cms/index.html create mode 100644 apps/cms/package.json create mode 100644 apps/cms/src/PLAN.md create mode 100644 apps/cms/src/components/PLAN.md create mode 100644 apps/cms/src/components/editor/PLAN.md create mode 100644 apps/cms/src/components/editor/json-schema/PLAN.md create mode 100644 apps/cms/src/components/editor/preview/PLAN.md create mode 100644 apps/cms/src/components/shell/PLAN.md create mode 100644 apps/cms/src/components/spaces/PLAN.md create mode 100644 apps/cms/src/components/spaces/actions/PLAN.md create mode 100644 apps/cms/src/components/spaces/analytics/PLAN.md create mode 100644 apps/cms/src/components/spaces/apps/PLAN.md create mode 100644 apps/cms/src/components/spaces/assets/PLAN.md create mode 100644 apps/cms/src/components/spaces/loaders/PLAN.md create mode 100644 apps/cms/src/components/spaces/pages/PLAN.md create mode 100644 apps/cms/src/components/spaces/releases/PLAN.md create mode 100644 apps/cms/src/components/spaces/sections/PLAN.md create mode 100644 apps/cms/src/components/spaces/settings/PLAN.md create mode 100644 apps/cms/src/hooks/PLAN.md create mode 100644 apps/cms/src/main.tsx create mode 100644 apps/cms/src/providers/PLAN.md create mode 100644 apps/cms/src/routes/PLAN.md create mode 100644 apps/cms/vite.config.ts create mode 100644 packages/cms-sdk/PLAN.md create mode 100644 packages/cms-sdk/package.json create mode 100644 packages/cms-sdk/src/blocks/index.ts create mode 100644 packages/cms-sdk/src/daemon/index.ts create mode 100644 packages/cms-sdk/src/index.ts create mode 100644 packages/cms-sdk/src/preview/index.ts create mode 100644 packages/cms-sdk/src/schema/index.ts create mode 100644 packages/cms-sdk/tsconfig.json create mode 100644 plans/CMS_MIGRATION.md diff --git a/apps/cms/PLAN.md b/apps/cms/PLAN.md new file mode 100644 index 0000000000..f5d0ce3566 --- /dev/null +++ b/apps/cms/PLAN.md @@ -0,0 +1,124 @@ +# DecoCMS - Content Management System + +> Port of admin-cx (admin.deco.cx) to the new React/Vite stack + +## Overview + +This app provides the **Content Management System** for deco sites, complementing the **Context Management System** (mesh/MCP). It enables visual editing of pages, sections, loaders, actions, and other blocks that power deco-based websites. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ apps/cms │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Shell │ │ Spaces │ │ Editor │ │ +│ │ (Layout) │ │ (Views) │ │ (Form + Preview) │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ packages/cms-sdk │ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │ │ +│ │ │ Daemon │ │ Blocks │ │ Schema │ │ Preview │ │ │ +│ │ │ Client │ │ CRUD │ │ Fetcher │ │ URLs │ │ │ +│ │ └─────────┘ └─────────┘ └─────────┘ └───────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ deco site runtime │ + │ /.deco/blocks/*.json │ + │ /live/_meta │ + │ /live/previews/* │ + └─────────────────────────┘ +``` + +## Key Dependencies + +- **@deco/ui** - Shared UI components (buttons, inputs, tables, etc.) +- **@deco/sdk** - Auth, API client, React Query hooks +- **packages/cms-sdk** - NEW: CMS-specific SDK (daemon, blocks, schema) +- **react-router** - Client-side routing +- **react-hook-form** - Form state management +- **ajv** - JSON Schema validation + +## Features to Port from admin-cx + +### P0 - MVP (Must Have) +- [ ] Pages list and editor +- [ ] Sections list and editor +- [ ] JSON Schema form with core widgets +- [ ] Preview iframe with viewport controls +- [ ] Real-time daemon sync +- [ ] Block CRUD operations + +### P1 - Core Features +- [ ] Loaders list and editor +- [ ] Actions list and editor +- [ ] Apps management (install/uninstall) +- [ ] Assets upload and management +- [ ] Releases and git operations +- [ ] Analytics integration (Plausible) +- [ ] Logs viewer (HyperDX) +- [ ] SEO settings +- [ ] Redirects management + +### P2 - Advanced Features +- [ ] Themes editor +- [ ] Segments and experiments +- [ ] Blog management +- [ ] Records (Drizzle Studio) + +## Implementation Phases + +### Phase 1: Foundation (Week 1-2) +1. Create packages/cms-sdk with daemon client +2. Setup apps/cms with basic routing +3. Implement site connection flow + +### Phase 2: Core Editor (Week 2-3) +1. Port JSON Schema form system +2. Implement preview iframe +3. Create block editor component + +### Phase 3: Spaces (Week 4-6) +1. Pages space +2. Sections space +3. Loaders/Actions spaces +4. Apps space +5. Assets space + +### Phase 4: Operations (Week 6-7) +1. Releases and git operations +2. Settings (domains, team) +3. Navigation between mesh and cms + +### Phase 5: Analytics & Observability (Week 8) +1. Analytics integration +2. Logs viewer +3. Error monitoring + +## File Structure + +See individual `PLAN.md` files in each subdirectory for detailed implementation plans. + +## Running Locally + +```bash +# From repo root +npm run dev:cms + +# Or directly +cd apps/cms && npm run dev +``` + +## Environment Variables + +```env +VITE_API_URL=http://localhost:3000 +VITE_SITE_DOMAIN=.deco.site +``` + diff --git a/apps/cms/index.html b/apps/cms/index.html new file mode 100644 index 0000000000..006b0d9e5a --- /dev/null +++ b/apps/cms/index.html @@ -0,0 +1,15 @@ + + + + + + + Deco CMS + + + +
+ + + + diff --git a/apps/cms/package.json b/apps/cms/package.json new file mode 100644 index 0000000000..e16982ae5f --- /dev/null +++ b/apps/cms/package.json @@ -0,0 +1,40 @@ +{ + "name": "@deco/cms", + "version": "0.0.1", + "private": true, + "description": "Deco Content Management System", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "biome lint src/", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@deco/cms-sdk": "workspace:*", + "@deco/sdk": "workspace:*", + "@deco/ui": "workspace:*", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@hookform/resolvers": "^3.3.0", + "@tanstack/react-query": "^5.0.0", + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "react-hook-form": "^7.50.0", + "react-router": "^7.0.0" + }, + "devDependencies": { + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.2.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.4.0", + "vite": "^5.2.0" + } +} + diff --git a/apps/cms/src/PLAN.md b/apps/cms/src/PLAN.md new file mode 100644 index 0000000000..7dede5c53d --- /dev/null +++ b/apps/cms/src/PLAN.md @@ -0,0 +1,79 @@ +# apps/cms/src - Source Structure + +## Directory Layout + +``` +src/ +├── main.tsx # App entry point +├── routes/ # Route components +├── components/ # React components +│ ├── shell/ # Layout components +│ ├── spaces/ # Space views (pages, sections, etc.) +│ ├── editor/ # Form and preview +│ └── common/ # Shared components +├── hooks/ # React hooks +├── providers/ # Context providers +├── stores/ # Zustand stores (if needed) +└── utils/ # Utility functions +``` + +## Entry Point (main.tsx) + +```tsx +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RouterProvider } from "react-router"; +import { DecoQueryClientProvider } from "@deco/sdk"; +import { router } from "./routes"; + +import "@deco/ui/styles/global.css"; +import "./styles.css"; + +createRoot(document.getElementById("root")!).render( + + + + + +); +``` + +## Routing Structure + +The CMS will be accessible at `/:org/:project/cms/*` or as a standalone app. + +Routes: +- `/:org/:site` - Site dashboard +- `/:org/:site/pages` - Pages list +- `/:org/:site/pages/:pageId` - Page editor +- `/:org/:site/sections` - Sections list +- `/:org/:site/sections/:sectionId` - Section editor +- `/:org/:site/loaders` - Loaders list +- `/:org/:site/actions` - Actions list +- `/:org/:site/apps` - Apps management +- `/:org/:site/assets` - Asset library +- `/:org/:site/releases` - Release management +- `/:org/:site/analytics` - Analytics dashboard +- `/:org/:site/logs` - Logs viewer +- `/:org/:site/settings` - Site settings + +## Key Patterns + +### 1. Site Context Provider +All routes under `/:org/:site` will be wrapped with `SiteProvider` that: +- Establishes daemon connection +- Fetches site metadata (`/live/_meta`) +- Provides block access via React Query + +### 2. Space Pattern +Each "space" (pages, sections, etc.) follows the same pattern: +- List view with table/grid +- Detail/edit view with form + preview +- Uses shared `BlockEditor` component + +### 3. Real-time Updates +The daemon connection provides real-time file system updates: +- File changes trigger query invalidation +- Optimistic updates for better UX +- Conflict resolution for concurrent edits + diff --git a/apps/cms/src/components/PLAN.md b/apps/cms/src/components/PLAN.md new file mode 100644 index 0000000000..655fedf484 --- /dev/null +++ b/apps/cms/src/components/PLAN.md @@ -0,0 +1,79 @@ +# Components Structure + +## Overview + +Components are organized by function: + +``` +components/ +├── shell/ # App shell and navigation +├── spaces/ # Main content areas (pages, sections, etc.) +├── editor/ # Block editing (form + preview) +└── common/ # Shared/reusable components +``` + +## Component Guidelines + +### 1. Use @deco/ui Components +Always prefer `@deco/ui` components over creating new ones: +- `Button`, `Input`, `Select` from `@deco/ui/components` +- `ResourceTable` for data lists +- `Dialog`, `Sheet` for modals +- `Tabs`, `Accordion` for organization + +### 2. Colocation +Keep related files together: +``` +components/spaces/pages/ +├── index.tsx # Main export +├── PagesList.tsx # List component +├── PageEditor.tsx # Editor component +├── use-pages.ts # Page-specific hooks +└── types.ts # Type definitions +``` + +### 3. Props Pattern +Use explicit prop interfaces: +```tsx +interface PageEditorProps { + pageId: string; + onSave?: (page: Page) => void; + onCancel?: () => void; +} + +export function PageEditor({ pageId, onSave, onCancel }: PageEditorProps) { + // ... +} +``` + +## Key Components to Implement + +### Shell Components +- `CMSLayout` - Main app shell with sidebar + topbar +- `Sidebar` - Navigation between spaces +- `Topbar` - Breadcrumbs, site selector, user menu +- `SpaceContainer` - Container for space content + +### Space Components +- `PagesSpace` - Pages list and management +- `SectionsSpace` - Sections list and management +- `LoadersSpace` - Loaders list +- `ActionsSpace` - Actions list +- `AppsSpace` - App installation +- `AssetsSpace` - Asset library +- `ReleasesSpace` - Git releases +- `SettingsSpace` - Site configuration + +### Editor Components +- `BlockEditor` - Combined form + preview layout +- `JSONSchemaForm` - Form rendered from JSON Schema +- `Preview` - iframe preview with controls +- `Addressbar` - URL input with viewport switcher + +### Common Components +- `BlockCard` - Card displaying block info +- `BlockSelector` - Modal for selecting blocks +- `AssetPicker` - Modal for selecting/uploading assets +- `ColorPicker` - Color input with picker +- `CodeEditor` - Monaco-based code editor + diff --git a/apps/cms/src/components/editor/PLAN.md b/apps/cms/src/components/editor/PLAN.md new file mode 100644 index 0000000000..4680dfe694 --- /dev/null +++ b/apps/cms/src/components/editor/PLAN.md @@ -0,0 +1,126 @@ +# Editor Components + +## Overview + +The editor system consists of two main parts: +1. **JSON Schema Form** - Dynamic form generation from JSON Schema +2. **Preview** - Live iframe preview of the block being edited + +## Components + +### BlockEditor.tsx + +Main editor component combining form and preview. + +```tsx +interface BlockEditorProps { + blockId: string; + block: Block; + schema: JSONSchema; + showPreview?: boolean; + previewPath?: string; + onSave?: (block: Block) => void; +} + +export function BlockEditor({ + blockId, + block, + schema, + showPreview = true, + previewPath, + onSave, +}: BlockEditorProps) { + const [formData, setFormData] = useState(block); + + const handleChange = (data: Block) => { + setFormData(data); + onSave?.(data); + }; + + return ( + + + + + + + + {showPreview && ( + <> + + + + + + )} + + ); +} +``` + +## Directory Structure + +``` +editor/ +├── BlockEditor.tsx # Main editor layout +├── json-schema/ # Form system +│ ├── Form.tsx # Main form component +│ ├── widgets/ # Input widgets +│ ├── templates/ # Field/array/object templates +│ └── utils/ # Schema utilities +└── preview/ # Preview system + ├── Preview.tsx # Main preview component + ├── Addressbar.tsx # URL bar with viewport + └── ViewportSelector.tsx +``` + +## Implementation Priority + +### P0 - Required for MVP +1. `BlockEditor.tsx` - Layout component +2. `json-schema/Form.tsx` - Basic form rendering +3. `json-schema/widgets/StringField.tsx` +4. `json-schema/widgets/NumberField.tsx` +5. `json-schema/widgets/BooleanField.tsx` +6. `json-schema/widgets/ArrayField.tsx` +7. `json-schema/widgets/ObjectField.tsx` +8. `json-schema/widgets/SelectField.tsx` +9. `preview/Preview.tsx` +10. `preview/Addressbar.tsx` + +### P1 - Core Widgets +11. `widgets/BlockSelector.tsx` - Select sections/blocks +12. `widgets/MediaUpload.tsx` - Image/file upload +13. `widgets/ColorPicker.tsx` +14. `widgets/RichText.tsx` + +### P2 - Advanced Widgets +15. `widgets/CodeEditor.tsx` - Monaco editor +16. `widgets/SecretInput.tsx` +17. `widgets/MapPicker.tsx` +18. `widgets/DatePicker.tsx` +19. `widgets/IconSelector.tsx` +20. `widgets/DynamicOptions.tsx` + +## Porting Strategy + +The JSON Schema form is a critical component. Strategy: + +1. **Start Fresh** - Use `react-hook-form` + `ajv` instead of RJSF +2. **Port Widgets** - Convert Preact widgets to React one by one +3. **Maintain Compatibility** - Same schema format as admin-cx +4. **Improve UX** - Take opportunity to improve upon original + +See detailed plans: +- `json-schema/PLAN.md` +- `preview/PLAN.md` + diff --git a/apps/cms/src/components/editor/json-schema/PLAN.md b/apps/cms/src/components/editor/json-schema/PLAN.md new file mode 100644 index 0000000000..de0f8dba66 --- /dev/null +++ b/apps/cms/src/components/editor/json-schema/PLAN.md @@ -0,0 +1,313 @@ +# JSON Schema Form System + +## Overview + +The JSON Schema form system dynamically generates forms from JSON Schema definitions. This is the core of the CMS editing experience. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Form.tsx │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ FormProvider │ │ +│ │ (react-hook-form + ajv validation) │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ SchemaRenderer │ │ +│ │ - Resolves $ref │ │ +│ │ - Determines widget type │ │ +│ │ - Handles oneOf/anyOf │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌───────────────┼───────────────┐ │ +│ ▼ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ String │ │ Object │ │ Custom │ │ +│ │ Widget │ │ Template │ │ Widget │ │ +│ └──────────┘ └──────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Core Components + +### Form.tsx + +Main form component that orchestrates everything. + +```tsx +interface JSONSchemaFormProps { + schema: JSONSchema; + formData: unknown; + onChange: (data: unknown, errors?: ValidationError[]) => void; + blockId?: string; + className?: string; +} + +export function JSONSchemaForm({ + schema, + formData, + onChange, + blockId, + className, +}: JSONSchemaFormProps) { + const methods = useForm({ + defaultValues: formData, + resolver: ajvResolver(schema), + mode: 'onChange', + }); + + // Sync form changes with parent + useEffect(() => { + const subscription = methods.watch((data) => { + onChange(data, methods.formState.errors); + }); + return () => subscription.unsubscribe(); + }, [methods, onChange]); + + return ( + + +
+ + +
+
+ ); +} +``` + +### SchemaRenderer.tsx + +Recursively renders schema nodes. + +```tsx +interface SchemaRendererProps { + schema: JSONSchema; + path: string; +} + +export function SchemaRenderer({ schema, path }: SchemaRendererProps) { + // Resolve $ref + const resolvedSchema = useResolvedSchema(schema); + + // Handle oneOf/anyOf + if (resolvedSchema.oneOf || resolvedSchema.anyOf) { + return ; + } + + // Get widget for schema type + const Widget = getWidget(resolvedSchema); + + return ; +} +``` + +## Widget Resolution + +Widgets are resolved based on schema properties: + +```typescript +function getWidget(schema: JSONSchema): WidgetComponent { + // Check for explicit widget + if (schema.format === 'uri' && schema['x-widget'] === 'image') { + return MediaUploadWidget; + } + + // Check format + if (schema.format === 'color') return ColorPickerWidget; + if (schema.format === 'date') return DatePickerWidget; + if (schema.format === 'date-time') return DateTimePickerWidget; + if (schema.format === 'uri') return UrlInputWidget; + if (schema.format === 'code') return CodeEditorWidget; + + // Check type + switch (schema.type) { + case 'string': + if (schema.enum) return SelectWidget; + if (schema.maxLength > 100) return TextareaWidget; + return StringWidget; + case 'number': + case 'integer': + return NumberWidget; + case 'boolean': + return BooleanWidget; + case 'array': + return ArrayWidget; + case 'object': + return ObjectWidget; + default: + return StringWidget; + } +} +``` + +## Widgets Directory + +``` +widgets/ +├── primitives/ +│ ├── StringWidget.tsx # Text input +│ ├── NumberWidget.tsx # Number input +│ ├── BooleanWidget.tsx # Checkbox/toggle +│ ├── SelectWidget.tsx # Dropdown select +│ └── TextareaWidget.tsx # Multi-line text +├── complex/ +│ ├── ArrayWidget.tsx # Array field with add/remove +│ ├── ObjectWidget.tsx # Nested object +│ └── TypeSelector.tsx # oneOf/anyOf selector +├── custom/ +│ ├── BlockSelector.tsx # Section/block picker +│ ├── MediaUpload.tsx # Image/file upload +│ ├── ColorPicker.tsx # Color input +│ ├── CodeEditor.tsx # Monaco editor +│ ├── RichText.tsx # TipTap editor +│ ├── SecretInput.tsx # Password field +│ ├── MapPicker.tsx # Location picker +│ ├── DatePicker.tsx # Date input +│ └── IconSelector.tsx # Icon picker +└── templates/ + ├── FieldTemplate.tsx # Wrapper for all fields + ├── ArrayTemplate.tsx # Array item layout + └── ObjectTemplate.tsx # Object layout +``` + +## Key Widget Implementations + +### BlockSelector.tsx + +The most complex widget - allows selecting sections from library. + +```tsx +export function BlockSelector({ schema, path }: WidgetProps) { + const { setValue, watch } = useFormContext(); + const value = watch(path); + const [isOpen, setIsOpen] = useState(false); + + // Get available block types from schema + const blockTypes = getBlockTypes(schema); + + return ( + <> +
setIsOpen(true)} + > + {value?.__resolveType ? ( + + ) : ( + Select section... + )} +
+ + { + setValue(path, block); + setIsOpen(false); + }} + /> + + ); +} +``` + +### ArrayWidget.tsx + +Array field with drag-and-drop reordering. + +```tsx +export function ArrayWidget({ schema, path }: WidgetProps) { + const { control } = useFormContext(); + const { fields, append, remove, move } = useFieldArray({ + control, + name: path, + }); + + return ( +
+ { + if (over && active.id !== over.id) { + const oldIndex = fields.findIndex(f => f.id === active.id); + const newIndex = fields.findIndex(f => f.id === over.id); + move(oldIndex, newIndex); + } + }}> + f.id)}> + {fields.map((field, index) => ( + remove(index)} + /> + ))} + + + + +
+ ); +} +``` + +## Validation + +Using AJV for JSON Schema validation: + +```typescript +// utils/ajv-resolver.ts +import Ajv from 'ajv'; +import addFormats from 'ajv-formats'; + +const ajv = new Ajv({ allErrors: true }); +addFormats(ajv); + +export function ajvResolver(schema: JSONSchema) { + const validate = ajv.compile(schema); + + return async (data: unknown) => { + const valid = validate(data); + + if (valid) { + return { values: data, errors: {} }; + } + + const errors = validate.errors?.reduce((acc, error) => { + const path = error.instancePath.replace(/\//g, '.'); + acc[path] = { message: error.message }; + return acc; + }, {} as Record); + + return { values: {}, errors }; + }; +} +``` + +## Porting from admin-cx + +Key files to reference: +- `admin-cx/components/editor/JSONSchema/Form.tsx` +- `admin-cx/components/editor/JSONSchema/widgets/*.tsx` +- `admin-cx/components/editor/JSONSchema/utils.ts` +- `admin-cx/components/editor/JSONSchema/validator.ts` + +Main differences: +1. **React Hook Form** instead of custom form state +2. **AJV** instead of RJSF validation +3. **Tailwind/shadcn** instead of custom UI +4. **DnD Kit** instead of custom drag-and-drop + diff --git a/apps/cms/src/components/editor/preview/PLAN.md b/apps/cms/src/components/editor/preview/PLAN.md new file mode 100644 index 0000000000..0304a51f92 --- /dev/null +++ b/apps/cms/src/components/editor/preview/PLAN.md @@ -0,0 +1,261 @@ +# Preview System + +## Overview + +The preview system provides a live iframe preview of the block being edited. It communicates with the deco runtime to render blocks in real-time. + +## Components + +### Preview.tsx + +Main preview component with iframe and controls. + +```tsx +interface PreviewProps { + block: Block; + blockId?: string; + previewPath?: string; + className?: string; +} + +export function Preview({ + block, + blockId, + previewPath = '/', + className, +}: PreviewProps) { + const { site } = useSite(); + const iframeRef = useRef(null); + const [viewport, setViewport] = useState('desktop'); + const [isLoading, setIsLoading] = useState(true); + + // Build preview URL + const previewUrl = usePreviewUrl(site, block, { + path: previewPath, + viewport, + }); + + // Handle iframe communication + useLiveEditorEvents({ + iframeRef, + onEditProp: (paths) => { + // Scroll to field in form + const fieldId = paths.join('_'); + document.getElementById(fieldId)?.scrollIntoView({ behavior: 'smooth' }); + }, + }); + + return ( +
+ + +
+