Files
site-builder/craft/CLAUDE.md
Josh Knapp 91a6b6f34b Add Craft.js site builder (v2) - complete rebuild from GrapesJS
Rebuilt the visual site builder from scratch using Craft.js, React 18,
and TypeScript. The new editor renders directly in the DOM (no iframe),
supports 40+ components, multi-page with shared header/footer, 16
templates, full-spectrum color/gradient controls, custom head code
injection, save/publish workflow, and auto-save.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:31:16 -07:00

22 KiB

WHP Site Builder v2 (Craft.js) - Project Documentation

Overview

A visual drag-and-drop website builder rebuilt from the ground up using Craft.js, React 18, and TypeScript. Replaces the legacy GrapesJS-based editor (/workspace/site-builder/). Users create multi-page websites without writing code, with server-side storage through WHP's PHP API layer.

Stack: Vite 6 + React 18 + TypeScript 5 + @craftjs/core 0.2.x Bundle: ~460KB JS + ~15KB CSS Version: 2.0.0

File Structure

craft/
├── index.html                          # HTML shell (loads fonts, FA icons, mounts React)
├── package.json                        # Dependencies and scripts (v2.0.0)
├── tsconfig.json                       # TypeScript config (ES2020, strict, path aliases)
├── vite.config.ts                      # Vite config (builds to dist/js/editor.js + dist/css/editor.css)
├── CLAUDE.md                           # This file
├── README.md                           # Brief project readme
├── FEATURES.md                         # User-facing features list
├── dist/                               # Build output (not committed)
│   ├── index.html
│   ├── js/editor.js
│   ├── css/editor.css
│   └── assets/
├── src/
│   ├── main.tsx                        # Entry point: reads WHP_CONFIG, mounts <App>
│   ├── App.tsx                         # Wraps <Editor> with providers, passes resolver
│   │
│   ├── types/
│   │   └── index.ts                    # WhpConfig, PageData, AssetData, StyleProps, DeviceMode
│   │
│   ├── state/
│   │   ├── EditorConfigContext.tsx      # React context for WHP_CONFIG (useEditorConfig hook)
│   │   ├── PageContext.tsx             # Multi-page state (pages, header, footer, CRUD, switching)
│   │   └── SiteDesignContext.tsx       # Site-wide design tokens (17 properties, Basic/Advanced)
│   │
│   ├── editor/
│   │   ├── EditorShell.tsx             # 3-panel layout: TopBar + LeftPanel + Canvas + RightPanel + ContextMenu
│   │   └── Canvas.tsx                  # Craft.js <Frame> with device-width switching
│   │
│   ├── components/
│   │   ├── resolver.ts                 # Component map for Craft.js serialization (20 components)
│   │   ├── layout/
│   │   │   ├── Container.tsx           # Generic container (div/section/article/header/footer/main)
│   │   │   ├── Section.tsx             # Full-width section with centered inner container
│   │   │   ├── ColumnLayout.tsx        # Flex columns (1-6, with split ratios)
│   │   │   ├── BackgroundSection.tsx   # Section with background image/gradient overlay
│   │   │   ├── HeaderZone.tsx          # Page-level header zone wrapper
│   │   │   └── FooterZone.tsx          # Page-level footer zone wrapper
│   │   ├── basic/
│   │   │   ├── Heading.tsx             # Inline-editable heading (h1-h6)
│   │   │   ├── TextBlock.tsx           # Inline-editable paragraph
│   │   │   ├── ButtonLink.tsx          # Styled <a> with color presets
│   │   │   ├── Navbar.tsx              # Navigation bar (text/image logo, page links, external links, CTA)
│   │   │   ├── Footer.tsx              # Footer component (links, copyright, social)
│   │   │   ├── Divider.tsx             # Horizontal rule (color, thickness)
│   │   │   └── Spacer.tsx              # Vertical spacing element
│   │   ├── media/
│   │   │   ├── ImageBlock.tsx          # Image with placeholder, upload, browse, sizing
│   │   │   └── VideoBlock.tsx          # Video embed (YouTube, Vimeo, direct files, background mode)
│   │   ├── sections/
│   │   │   ├── HeroSimple.tsx          # Pre-built hero section with heading, subtext, CTA
│   │   │   ├── FeaturesGrid.tsx        # 3-column feature cards grid
│   │   │   └── CTASection.tsx          # Call-to-action banner section
│   │   └── forms/
│   │       ├── FormContainer.tsx       # Form wrapper with action/method
│   │       ├── InputField.tsx          # Input field with label and placeholder
│   │       ├── TextareaField.tsx       # Textarea field with label
│   │       └── FormButton.tsx          # Submit button with styling
│   │
│   ├── panels/
│   │   ├── topbar/
│   │   │   ├── TopBar.tsx              # Back button, domain badge, device switcher, undo/redo, save, templates
│   │   │   └── TemplateModal.tsx       # Template browser with categories and one-click loading
│   │   ├── left/
│   │   │   ├── LeftPanel.tsx           # Tabs: Blocks | Pages | Layers | Assets
│   │   │   ├── BlocksPanel.tsx         # Draggable block toolbox with categories
│   │   │   ├── PagesPanel.tsx          # Multi-page CRUD, header/footer editing
│   │   │   ├── LayersPanel.tsx         # Component hierarchy tree view
│   │   │   └── AssetsPanel.tsx         # Asset browser with upload, drag-drop, thumbnails
│   │   ├── right/
│   │   │   ├── RightPanel.tsx          # Tabs: Styles | Settings | Head
│   │   │   ├── GuidedStyles.tsx        # Context-aware style panel (shows selected type)
│   │   │   └── SiteDesignPanel.tsx     # Site-wide design tokens editor (Basic/Advanced tabs)
│   │   └── context-menu/
│   │       └── ContextMenu.tsx         # Right-click context menu (duplicate, copy, paste, delete, etc.)
│   │
│   ├── hooks/
│   │   ├── useWhpApi.ts               # Save/load/deploy via WHP API (with auto-save)
│   │   ├── useAssets.ts               # Asset upload, browse, delete via WHP API
│   │   ├── useContextMenu.ts          # Right-click menu state management
│   │   └── useKeyboardShortcuts.ts    # Keyboard shortcut handler (undo, redo, delete, etc.)
│   │
│   ├── templates/
│   │   ├── index.ts                   # Template exports
│   │   └── definitions.ts            # 16 template definitions across 4 categories
│   │
│   ├── ui/
│   │   └── SettingsTabs.tsx           # Reusable General/Style/Advanced tabs for component settings
│   │
│   ├── constants/
│   │   └── presets.ts                  # Color, font, spacing, radius, gradient, device width presets
│   │
│   ├── utils/
│   │   ├── style-helpers.ts            # cssPropsToString(), mergeStyles()
│   │   └── html-export.ts             # Recursive node-to-HTML renderer, full page export
│   │
│   └── styles/
│       └── editor.css                  # Dark theme, CSS variables

Running Locally

cd /workspace/site-builder/craft
npm install
npm run dev

Opens at http://localhost:5173. The Vite dev server proxies /api requests to http://192.168.1.105:8080 (the WHP staging server) for save/load during development.

In standalone mode (no WHP_CONFIG on window), the editor runs fully client-side without save/load functionality.

Building

npm run build

Runs tsc && vite build. Output goes to dist/:

  • dist/index.html - HTML shell
  • dist/js/editor.js - Single JS bundle
  • dist/css/editor.css - All styles
  • dist/assets/ - Static assets (if any)

Deploying to WHP

Copy the built dist/ contents into the WHP site-builder web directory:

# Build
cd /workspace/site-builder/craft && npm run build

# Deploy to WHP Docker container
cp dist/index.html /docker/whp/web/site-builder/editor.html
cp -r dist/js/ /docker/whp/web/site-builder/js/
cp -r dist/css/ /docker/whp/web/site-builder/css/

The PHP wrapper (/docker/whp/web/site-builder/index.php) injects WHP_CONFIG into the HTML before serving it, so the editor gets the current user's session, CSRF token, site ID, etc.

Architecture

Key Decisions

  1. No iframe - The canvas renders directly in the DOM (unlike GrapesJS which uses an iframe). This simplifies drag-and-drop and avoids cross-origin issues but means editor CSS must not leak into user content.

  2. Inline styles - All component styling uses React CSSProperties (inline styles). No class-based CSS for user content. This makes HTML export trivial and avoids stylesheet management.

  3. Single Frame, multi-page - Craft.js <Frame> holds one page at a time. Page switching serializes the current state, stores it, and deserializes the new page's state.

  4. Header/Footer as separate pages - Header and Footer are stored as independent Craft.js states (like pages) that render above and below every page. Editing them uses the same canvas but with a distinct editing mode. This provides site-wide shared navigation and footer.

  5. API compatibility - The save endpoint sends data in the same format as the GrapesJS version ({ site_id, name, html, css, grapesjs: serializedJson }), so the PHP backend doesn't need changes.

  6. Component-based architecture - Each visual element is a React component that doubles as a Craft.js UserComponent. All rendering, settings UI, and HTML export are co-located in one file.

  7. Site Design Tokens - A SiteDesignContext provides 17 design properties (colors, fonts, radii, nav style) that components can reference. Templates import their own design tokens when loaded.

Component Architecture

Every component in src/components/ follows this pattern:

import { UserComponent, useNode } from '@craftjs/core';

// 1. Props interface
interface MyComponentProps {
  text?: string;
  style?: CSSProperties;
}

// 2. The component itself (renders in editor canvas)
export const MyComponent: UserComponent<MyComponentProps> = ({ text, style }) => {
  const { connectors: { connect, drag } } = useNode();
  return <div ref={(r) => { if (r) connect(drag(r)); }} style={style}>{text}</div>;
};

// 3. Settings panel (rendered in right panel when selected)
const MyComponentSettings: React.FC = () => {
  const { actions: { setProp }, props } = useNode((node) => ({
    props: node.data.props as MyComponentProps,
  }));
  return <div>/* preset buttons, inputs, etc. */</div>;
};

// 4. Craft config (displayName, default props, rules, related settings)
MyComponent.craft = {
  displayName: 'My Component',
  props: { text: 'Default text', style: {} },
  rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true },
  related: { settings: MyComponentSettings },
};

// 5. HTML export (static method for serializing to HTML string)
(MyComponent as any).toHtml = (props: MyComponentProps, childrenHtml: string) => {
  return { html: `<div style="...">${childrenHtml}</div>` };
};

Component Resolver

All components must be registered in src/components/resolver.ts. This map is passed to <Editor resolver={componentResolver}> so Craft.js can serialize/deserialize the node tree.

export const componentResolver = {
  Container, Section, ColumnLayout, BackgroundSection,
  Heading, TextBlock, ButtonLink, Navbar, Footer, Divider, Spacer,
  ImageBlock, VideoBlock,
  HeroSimple, FeaturesGrid, CTASection,
  FormContainer, InputField, TextareaField, FormButton,
};

WHP Integration

PHP Wrapper

The WHP control panel serves the editor through index.php, which:

  1. Verifies user authentication
  2. Validates the site_id parameter
  3. Generates a CSRF token
  4. Injects a WHP_CONFIG object into the HTML as a <script> tag before the app bundle
window.WHP_CONFIG = {
  user: "username",
  apiUrl: "/panel/api/site-builder",
  csrfToken: "abc123...",
  siteId: 42,
  siteDomain: "example.com",
  siteName: "My Site",
  backUrl: "/panel/sites",
  isRoot: false
};

API Endpoints

The editor communicates with WHP through these endpoints (all require CSRF token):

Method Endpoint Description
POST /panel/api/site-builder?action=save Save project (JSON body with site_id, html, css, craft state)
GET /panel/api/site-builder?action=load&site_id=N Load project for a site
POST /panel/api/site-builder?action=upload Upload asset (multipart)
GET /panel/api/site-builder?action=assets&site_id=N List assets for a site
DELETE /panel/api/site-builder?action=delete_asset Delete an asset
POST /panel/api/site-builder?action=deploy&site_id=N Deploy/publish site to document root

Auto-Save

The editor auto-saves every 30 seconds when running inside WHP. The save status is displayed in the top bar.

EditorConfigContext

useEditorConfig() provides access to WHP_CONFIG throughout the React tree:

  • whpConfig - The full config object (or null in standalone mode)
  • isWHP - Boolean shorthand for whether we're running inside WHP

All Components (22)

# Component Type File Features
1 Container Layout layout/Container.tsx Generic wrapper, tag selector (div/section/article/header/footer/main), bg color, padding, radius
2 Section Layout layout/Section.tsx Full-width with centered inner container, bg color/gradient, vertical padding, inner max-width
3 ColumnLayout Layout layout/ColumnLayout.tsx 1-6 columns, split ratios (50-50, 30-70, 70-30, 33-33-33, 25-25-25-25, etc.), gap control
4 BackgroundSection Layout layout/BackgroundSection.tsx Section with background image, gradient overlay, parallax-ready
5 HeaderZone Layout layout/HeaderZone.tsx Page-level header wrapper zone, used by PageContext
6 FooterZone Layout layout/FooterZone.tsx Page-level footer wrapper zone, used by PageContext
7 Heading Basic basic/Heading.tsx Inline-editable, h1-h6 level, color, font family/size/weight, text align
8 TextBlock Basic basic/TextBlock.tsx Inline-editable paragraph, color, font family/size/weight, text align, line height
9 ButtonLink Basic basic/ButtonLink.tsx Link text/URL/target, 8 color presets (auto text contrast), radius, padding, font size
10 Navbar Basic basic/Navbar.tsx Text or image logo, page links, external links, CTA buttons, light/dark nav style
11 Footer Basic basic/Footer.tsx Footer with links, copyright, social links
12 Divider Basic basic/Divider.tsx Horizontal rule with color and thickness controls
13 Spacer Basic basic/Spacer.tsx Vertical spacing element with height control
14 ImageBlock Media media/ImageBlock.tsx SVG placeholder, URL input, upload, browse assets, alt text, width/height, object-fit, radius
15 VideoBlock Media media/VideoBlock.tsx YouTube, Vimeo, direct files (.mp4/.webm/.ogg), background mode, autoplay, loop
16 HeroSimple Section sections/HeroSimple.tsx Pre-built hero with heading, subtext, CTA button, gradient/color background
17 FeaturesGrid Section sections/FeaturesGrid.tsx 3-column feature cards with icons, titles, descriptions
18 CTASection Section sections/CTASection.tsx Call-to-action banner with heading, text, button
19 FormContainer Form forms/FormContainer.tsx Form wrapper with action URL and method
20 InputField Form forms/InputField.tsx Text input with label, placeholder, type (text/email/tel/password/number)
21 TextareaField Form forms/TextareaField.tsx Textarea with label and placeholder
22 FormButton Form forms/FormButton.tsx Submit button with color and style controls

Site Design Tokens

The SiteDesignContext provides 17 design properties organized into Basic and Advanced tabs:

Basic Tab (6 properties)

Property Default Description
primaryColor #3b82f6 Primary brand color
secondaryColor #8b5cf6 Secondary brand color
accentColor #10b981 Accent/highlight color
headingFont Inter, sans-serif Font for headings
bodyFont Inter, sans-serif Font for body text
linkColor #3b82f6 Default link color

Advanced Tab (11 properties)

Property Default Description
successColor #10b981 Success state color
warningColor #f59e0b Warning state color
errorColor #ef4444 Error state color
backgroundColor #ffffff Page background
textColor #1f2937 Default text color
mutedTextColor #6b7280 Muted/secondary text
borderColor #e5e7eb Default border color
borderRadius 8px Global border radius
buttonFont Inter, sans-serif Button font family
buttonRadius 8px Button border radius
navStyle light Navbar style (light/dark)

Design tokens are imported from templates and can be edited via the Site Design panel. Components can read these tokens via useSiteDesign().

Templates

16 pre-built templates organized into 4 categories:

Category Templates
Business Restaurant, Small Business, SaaS Landing, Agency, Medical
Creative Portfolio, Photography, Content Creator, Event/Conference
Personal Resume/CV, Blog, Wedding, Coming Soon
Community Church, Non-Profit, Fitness/Gym

Each template includes:

  • Site design tokens (color palette, fonts)
  • Header content (Navbar)
  • Footer content
  • One or more pages with pre-built content
  • SVG thumbnail preview

Templates are loaded via the Template Modal (opened from TopBar). Loading a template replaces all pages, header, footer, and optionally imports the design tokens.

Multi-Page System

Pages are managed through PageContext:

  • Each page has: id, name, slug, craftState, headCode
  • Header and Footer are stored as separate "page" entries with fixed IDs (__header__, __footer__)
  • Page switching serializes the current canvas, stores it, then deserializes the target page
  • Header/Footer editing puts the canvas in a distinct mode
  • The PagesPanel provides add, rename, delete, reorder, and header/footer edit buttons

Keyboard Shortcuts

Shortcut Action
Ctrl/Cmd + Z Undo
Ctrl/Cmd + Shift + Z Redo
Ctrl/Cmd + Y Redo (alternative)
Delete / Backspace Delete selected element

Shortcuts are disabled when focus is on input, textarea, select, or contentEditable elements.

Context Menu (Right-Click)

The context menu appears on right-click within the canvas and provides:

  • Duplicate element
  • Copy / Paste
  • Move Up / Move Down
  • Select Parent
  • Delete (with danger styling)

Items are disabled contextually (e.g., cannot delete ROOT, cannot move if already first/last).

Layers Panel

The Layers panel shows a hierarchical tree of all components on the current page. Clicking a layer selects the corresponding component. The tree displays component display names and nests children with indentation.

Asset Management

The Assets panel (AssetsPanel.tsx) provides:

  • Upload button and drag-and-drop zone
  • Thumbnail grid of uploaded assets
  • Copy URL to clipboard
  • Delete asset
  • Integration with WHP API for server-side storage

Image and Video components also have inline asset selection (browse button in settings).

Adding New Components

  1. Create src/components/<category>/<ComponentName>.tsx following the pattern above
  2. Add the component to src/components/resolver.ts
  3. Add a block entry in src/panels/left/BlocksPanel.tsx under the appropriate category
  4. Implement the toHtml static for HTML export
  5. Build and test: npm run dev, drag the block onto the canvas, verify settings panel, verify HTML export

Checklist for a new component:

  • Props interface with style?: CSSProperties
  • useNode() with connect(drag(ref)) on the root element
  • Settings panel using useNode() with setProp()
  • .craft config with displayName, default props, rules, related.settings
  • .toHtml() static method using cssPropsToString()
  • Registered in resolver.ts
  • Block added to BlocksPanel.tsx

CSS / Theme

The editor uses a dark theme defined via CSS custom properties in src/styles/editor.css:

  • Base: #16161a
  • Surface: #1c1c24
  • Accent: #3b82f6 (blue)
  • Text: #e4e4e7
  • Border: #2d2d3a
  • Font: Inter

All editor chrome (panels, topbar, settings) is styled via editor.css. User content on the canvas uses inline styles exclusively.

Presets

Style presets are defined in src/constants/presets.ts:

  • TEXT_COLORS - 8 text color swatches
  • BG_COLORS - 8 background color swatches
  • FONT_FAMILIES - 8 Google Fonts
  • TEXT_SIZES - XS through 2XL
  • FONT_WEIGHTS - Light through Bold
  • SPACING_PRESETS - None through XL
  • RADIUS_PRESETS - None through Full (9999px)
  • GRADIENTS - 12 gradient presets
  • DEVICE_WIDTHS - Desktop (100%), Tablet (768px), Mobile (375px)

HTML Export

Every component has a static toHtml(props, childrenHtml) method. The html-export.ts utility recursively walks the Craft.js node tree and calls each component's toHtml to produce a complete HTML document. Export includes:

  • Full <!DOCTYPE html> document structure
  • Google Fonts preload links (optional)
  • Inline styles throughout
  • Header and footer wrapping each page

Testing Approach

  • Manual testing: Run npm run dev, drag components, edit props, verify settings panels
  • Type checking: tsc --noEmit (part of build step)
  • HTML export: Verify toHtml() output matches expected HTML structure
  • Device preview: Switch between desktop/tablet/mobile and verify responsive behavior
  • WHP integration: Deploy to staging, verify save/load, verify PHP wrapper injection

Development Notes

  • Path alias @/ maps to ./src/ (configured in both tsconfig.json and vite.config.ts)
  • GuidedStyles shows selected component type and delegates to per-component settings panels
  • Text components (Heading, TextBlock) use contentEditable for inline editing when selected
  • Button/link navigation is prevented in the editor via e.preventDefault()
  • Image upload integrates with WHP API; in standalone mode falls back to local blob: URLs
  • Auto-save runs every 30 seconds when connected to WHP API
  • The SettingsTabs UI component provides a reusable General/Style/Advanced tab layout for component settings