Files
site-builder/craft/CLAUDE.md
T
shadowdaoandClaude Opus 5.5 cfeeef37ce Mobile guard: HtmlBlock wrapper never gets a box; phone net is media + per-table/pre scroll only
Review of bb4fb75: overflow-x:auto on [data-wsb-html] forced overflow-y:auto (clipped dropdowns/shadows),
broke position:sticky inside customer HTML, and display:block!important merged multi-root blocks that are
direct flex/grid items. Now the wrapper stays display:contents on every viewport and no rule targets it;
at <=767px only its descendants are touched: media max-width:100% (img/video height:auto), and a table or
<pre> becomes its own sideways scroller (not when already inside an inline overflow wrapper). No
overflow-wrap:anywhere (it shrank min-content); body{overflow-wrap:break-word} inherits through the wrapper.
Deliberately wide customer markup (fixed widths, px grids) is clipped as before; the publish warning is
its guard, and its wording now says the excess is cut off. width= attributes are no longer flagged (they
size only media and tables, which the net handles). stripMediaBlocks lowercases once.

Browser suite: htmlblocks probes at every width (wrapper display:contents, sticky sticks, absolutely
positioned dropdown visible on top, multi-root block = separate flex items / grid cells, bare table
scrolls on its own); new htmlwide doc asserts wide blocks stay clipped and the page never scrolls sideways.
All probes fail on bb4fb75's net and pass now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:09:22 -08:00

27 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 (39 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
│   │   ├── 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
│   │   │   └── HeadCodeModal.tsx       # Edits SiteDesign.headCode (site-wide, not per-page)
│   │   ├── 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          # Single "Styles" tab -- renders GuidedStyles only
│   │   │   ├── GuidedStyles.tsx        # Context-aware dispatcher: picks a StylePanel by selected type
│   │   │   ├── SiteDesignPanel.tsx     # Site-wide design tokens editor (Basic/Advanced tabs)
│   │   │   └── styles/                 # Per-type StylePanels (Text, Button, Image, Container, Nav, Form, etc.)
│   │   └── 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/
│   │   ├── AssetPicker.tsx             # Reusable image/video source picker (upload/browse-uploaded/URL), full+compact variants
│   │   └── Modal.tsx                   # Reusable modal dialog shell
│   │
│   ├── 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. Rendering and HTML export are co-located in one file; style editing is handled by a shared per-type StylePanel in src/panels/right/styles/ rather than a per-component settings panel.

  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. Craft config (displayName, default props, rules)
MyComponent.craft = {
  displayName: 'My Component',
  props: { text: 'Default text', style: {} },
  rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true },
};

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

Style editing for the new type is added separately as a StylePanel under src/panels/right/styles/ (or reuses an existing generic one), and wired into GuidedStyles.tsx's type dispatch -- components no longer carry their own settings UI.

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 (39)

# 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 Heading Basic basic/Heading.tsx Inline-editable, h1-h6 level, color, font family/size/weight, text align
6 TextBlock Basic basic/TextBlock.tsx Inline-editable paragraph, color, font family/size/weight, text align, line height
7 ButtonLink Basic basic/ButtonLink.tsx Link text/URL/target, 8 color presets (auto text contrast), radius, padding, font size
8 Logo Basic basic/Logo.tsx Text or image logo, link href, font family/size/weight, image width
9 Menu Basic basic/Menu.tsx Link list with optional CTA styling, horizontal/vertical orientation, alignment, hover colors
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 Icon Basic basic/Icon.tsx Font Awesome icon, size/color, background shape, optional link
15 ImageBlock Media media/ImageBlock.tsx SVG placeholder, URL input, upload, browse assets, alt text, width/height, object-fit, radius
16 VideoBlock Media media/VideoBlock.tsx YouTube, Vimeo, direct files (.mp4/.webm/.ogg), background mode, autoplay, loop
17 MapEmbed Media media/MapEmbed.tsx Embedded map by address, zoom level, height
18 HeroSimple Section sections/HeroSimple.tsx Pre-built hero with heading, subtext, CTA button, gradient/color background
19 FeaturesGrid Section sections/FeaturesGrid.tsx 3-column feature cards with icons, titles, descriptions
20 CTASection Section sections/CTASection.tsx Call-to-action banner with heading, text, button
21 Countdown Section sections/Countdown.tsx Countdown timer to a target date, heading, digit/label colors
22 Testimonials Section sections/Testimonials.tsx Grid or single-layout testimonial cards, star color, card background
23 FormContainer Form forms/FormContainer.tsx Form wrapper with action URL and method
24 InputField Form forms/InputField.tsx Text input with label, placeholder, type (text/email/tel/password/number)
25 TextareaField Form forms/TextareaField.tsx Textarea with label and placeholder
26 FormButton Form forms/FormButton.tsx Submit button with color and style controls
27 ContactForm Form forms/ContactForm.tsx Configurable field list, recipient email, success message or thank-you URL redirect
28 StarRating Basic basic/StarRating.tsx Star rating display, rating/max stars, filled/empty color
29 SocialLinks Basic basic/SocialLinks.tsx Social icon links, size/color/shape, gap, alignment
30 CallToAction Section sections/CallToAction.tsx Heading/description with 1-2 buttons, color/gradient/image background with overlay
31 Accordion Section sections/Accordion.tsx Expand/collapse item list, header/content colors
32 Tabs Section sections/Tabs.tsx Tabbed content panels, active/inactive tab colors
33 PricingTable Section sections/PricingTable.tsx Pricing plan cards, featured plan highlight, bullet list
34 Gallery Section sections/Gallery.tsx Image grid gallery, configurable columns/gap, optional lightbox
35 ContentSlider Section sections/ContentSlider.tsx Auto-playing image/content slider, dots/arrows, configurable interval
36 NumberCounter Section sections/NumberCounter.tsx Animated stat counters, columns, number/label colors
37 SubscribeForm Form forms/SubscribeForm.tsx Inline/stacked email signup form, heading, button color
38 SearchBar Basic basic/SearchBar.tsx Search input with optional button, placeholder text
39 HtmlBlock Basic basic/HtmlBlock.tsx Raw/custom HTML embed block, sanitized on export

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 is site-wide only, on SiteDesignContext/SiteDesign, edited via the TopBar's Head Code modal -- not a per-page field)
  • 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 fields elsewhere in the editor (ImageStylePanel, MediaStylePanel, HeroStylePanel, NavStylePanel, BackgroundSectionStylePanel, and array-editor cards like FeaturesEditor) use the shared AssetPicker (src/ui/AssetPicker.tsx) for upload / browse-uploaded / paste-URL, in a full or compact variant depending on space.

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. Add or extend a StylePanel in src/panels/right/styles/ and wire it into GuidedStyles.tsx's type dispatch so the new component is editable when selected
  5. Implement the toHtml static for HTML export
  6. Build and test: npm run dev, drag the block onto the canvas, verify the style panel, verify HTML export

Checklist for a new component:

  • Props interface with style?: CSSProperties
  • useNode() with connect(drag(ref)) on the root element
  • StylePanel entry (new or reused) wired into GuidedStyles.tsx
  • .craft config with displayName, default props, rules
  • .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, style panels) 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 style 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
  • Mobile guard (real browsers): npm run test:browser:mobile-guard (Chromium + Firefox, headless; jsdom has no layout). Exports representative pages (navbar, columns, card grids, images, long words, wide customer HtmlBlocks) and asserts at 360/390/768/1280: no horizontal overflow (body.scrollWidth AND the right edge of every visible box and text line -- the head's html,body{overflow-x:clip} hides overflow from documentElement.scrollWidth) and no sideways page scroll, columns and card grids stack on phones and keep their columns on tablet, navbar links wrap, tables scroll on their own, sticky/dropdowns/multi-root flex+grid items inside HtmlBlocks still work, deliberately wide customer HTML stays clipped (not reflowed), and desktop is box- and pixel-identical with the hooks removed. Set CHROMIUM_PATH / FIREFOX_PATH if the Playwright-pinned browsers are not installed; record/compare a desktop baseline across commits with MOBILE_GUARD_RECORD=<file> (on the base commit) / MOBILE_GUARD_BASELINE=<file>. Run it whenever a component's toHtml, the exporter, or the head CSS changes.
  • Mobile hooks: the head's <=767px block (RESPONSIVE_CSS in utils/html-export.ts, mirrored byte-for-byte by WHP generateCompiledHTML) targets [data-wsb-grid] (component card grids -> one column), body (long words wrap) and descendants of [data-wsb-html] (the inline display:contents wrapper the exporter puts around HtmlBlock output: media shrink, tables/<pre> scroll on their own). Never give that wrapper a box or overflow: it clipped dropdowns, broke sticky and merged flex/grid items. A new component with an inline multi-column grid should carry data-wsb-grid.
  • 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 the selected component type and dispatches to a shared StylePanel in src/panels/right/styles/ (components no longer carry their own settings UI)
  • 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
  • AssetPicker (src/ui/AssetPicker.tsx) is the shared upload/browse/URL control reused across StylePanels for every image and video field