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>
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 shelldist/js/editor.js- Single JS bundledist/css/editor.css- All stylesdist/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
-
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.
-
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. -
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. -
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.
-
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. -
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-typeStylePanelinsrc/panels/right/styles/rather than a per-component settings panel. -
Site Design Tokens - A
SiteDesignContextprovides 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:
- Verifies user authentication
- Validates the
site_idparameter - Generates a CSRF token
- Injects a
WHP_CONFIGobject 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(headCodeis site-wide only, onSiteDesignContext/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
- Create
src/components/<category>/<ComponentName>.tsxfollowing the pattern above - Add the component to
src/components/resolver.ts - Add a block entry in
src/panels/left/BlocksPanel.tsxunder the appropriate category - Add or extend a
StylePanelinsrc/panels/right/styles/and wire it intoGuidedStyles.tsx's type dispatch so the new component is editable when selected - Implement the
toHtmlstatic for HTML export - 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()withconnect(drag(ref))on the root elementStylePanelentry (new or reused) wired intoGuidedStyles.tsx.craftconfig withdisplayName, defaultprops,rules.toHtml()static method usingcssPropsToString()- 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 swatchesBG_COLORS- 8 background color swatchesFONT_FAMILIES- 8 Google FontsTEXT_SIZES- XS through 2XLFONT_WEIGHTS- Light through BoldSPACING_PRESETS- None through XLRADIUS_PRESETS- None through Full (9999px)GRADIENTS- 12 gradient presetsDEVICE_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'shtml,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. SetCHROMIUM_PATH/FIREFOX_PATHif the Playwright-pinned browsers are not installed; record/compare a desktop baseline across commits withMOBILE_GUARD_RECORD=<file>(on the base commit) /MOBILE_GUARD_BASELINE=<file>. Run it whenever a component'stoHtml, the exporter, or the head CSS changes. - Mobile hooks: the head's
<=767pxblock (RESPONSIVE_CSS inutils/html-export.ts, mirrored byte-for-byte by WHPgenerateCompiledHTML) targets[data-wsb-grid](component card grids -> one column),body(long words wrap) and descendants of[data-wsb-html](the inlinedisplay:contentswrapper 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 carrydata-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) GuidedStylesshows the selected component type and dispatches to a sharedStylePanelinsrc/panels/right/styles/(components no longer carry their own settings UI)- Text components (Heading, TextBlock) use
contentEditablefor 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