33 KiB
WooNooW Page Editor - Enhanced UI/UX Brief (v4)
Overview
Redesign the Page Editor from a card-list-based interface to a live-canvas-based interface that shows actual rendered content in real-time. Leverage JSON-based rendering (no iframe/MutationObserver overhead) for faster, simpler implementation.
Core Philosophy
One Mental Model: What You See Is What You Get (WYSIWYG)
- Canvas shows the actual rendered output merchants will see on the front-end
- No iframe isolation → Direct component rendering → Instant visual feedback
- Edit sections on the canvas → Settings panel updates → Preview updates in real-time
- No context switching between "list view" and "edit mode"
New Layout Structure
Full-Screen Layout (No iframe, Direct React Rendering)
┌──────────────────────────────────────────────────────────────────┐
│ [☰] WooNooW Appearance > Pages > [About Us] [D/M] [Save] │
├─────────────────┬────────────────────────┬──────────────────────┤
│ │ │ │
│ LEFT SIDEBAR │ MAIN CANVAS │ RIGHT INSPECTOR │
│ (180px) │ (Dynamic, ~60-70%) │ (Collapse ◄) │
│ │ │ (200-280px) │
│ 📄 PAGES │ ╔════════════════════╗ │ │
│ ├─ Home │ ║ LIVE PAGE RENDER ║ │ ┌─────────────────┐ │
│ ├─ About ✓ │ ║ ║ │ │ Section Config │ │
│ └─ Contact │ ║ ┌──────────────┐ ║ │ └─────────────────┘ │
│ │ ║ │ HERO │ ║ │ │
│ 📋 TEMPLATES │ ║ │ Welcome │←──╋─┼─→ [Hero section selected]
│ ├─ Post │ ║ │ [Image] │ ║ │ │
│ └─ Portfolio │ ║ │ Join us │ ║ │ Layout: │
│ │ ║ └──────────────┘ ║ │ [Hero-Left ▼] │
│ [+ Create] │ ║ ║ │ │
│ │ ║ ┌──────────────┐ ║ │ Title: │
│ │ ║ │ IMAGE+TEXT │ ║ │ [Welcome______] │
│ │ ║ │ [IMG] [TEXT] │ ║ │ │
│ │ ║ └──────────────┘ ║ │ Image: │
│ │ ║ ║ │ [Choose Image ▼] │
│ │ ║ ┌──────────────┐ ║ │ │
│ │ ║ │ CTA │ ║ │ [Delete Section] │
│ │ ║ │ "Shop Now" │ ║ │ │
│ │ ║ └──────────────┘ ║ │ [Hide Inspector] │
│ │ ║ ║ │ │
│ │ ║ [+ Add section] ║ │ │
│ │ ╚════════════════════╝ │ │
│ │ │ │
│ │ Responsive toggle: │ │
│ │ [Desktop] [Mobile] │ │
│ │ │ │
└─────────────────┴────────────────────────┴──────────────────────┘
│ [Discard Changes] [Save Changes] [Preview in New Tab] │
└──────────────────────────────────────────────────────────────────┘
Key Differences from v3
| Aspect | v3 (Card List) | v4 (Live Canvas) |
|---|---|---|
| Main Column | Card list (read-only) | Live rendered sections |
| Rendering | Screenshot/preview | Actual React components |
| Interactivity | Click card → settings | Click section → settings |
| Visual Feedback | Static cards | Actual page appearance |
| Preview Size | Small box on right | Full mobile/desktop |
| Edit Speed | Click → settings → change | See change instantly |
| Context | List + Settings + Preview | One coherent interface |
Canvas Rendering System (JSON → React Components)
Architecture: Zero-Iframe, Direct Component Rendering
┌──────────────────────────────────────────┐
│ Page Editor State (Redux/Zustand) │
│ { │
│ sections: [ │
│ { type: "hero", props: {...} }, │
│ { type: "content", props: {...} } │
│ ], │
│ selectedSectionId: "sec-1" │
│ } │
└──────────────────────────────────────────┘
↓ (Real-time)
┌──────────────────────────────────────────┐
│ CanvasRenderer Component │
│ (Renders JSON → React components) │
│ │
│ { sections.map((sec) => │
│ <SectionComponent │
│ data={sec} │
│ isSelected={...} │
│ onSelect={...} │
│ /> │
│ )} │
└──────────────────────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Live Canvas Preview │
│ (Shows exact front-end appearance) │
│ │
│ ╔═══════════════════════════════════╗ │
│ ║ HERO SECTION ║ │
│ ║ (Selected, blue border) ║ │
│ ╚═══════════════════════════════════╝ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ IMAGE + TEXT │ │
│ │ (Not selected, gray border) │ │
│ └───────────────────────────────────┘ │
└──────────────────────────────────────────┘
Why This Is Simpler Than Page Builder Plugins
Page Builder Plugins (e.g., Elementor, Beaver):
- Use iframes to sandbox editing environment
- Must observe DOM mutations to track changes
- Heavy JavaScript overhead
- Slow preview updates
WooNooW Approach:
- ✅ JSON state → React components (unidirectional)
- ✅ No iframe = no sandbox overhead
- ✅ No mutation observers = simple state management
- ✅ Change state → re-render → instant preview
- ✅ Single component tree (not isolated)
Canvas Interaction Model
Section States
At Rest (Not Selected):
┌─────────────────────────────────┐
│ Hero Section │ ← Subtle border (gray)
│ [Actual rendered content] │ ← Hover shows subtle highlight
│ "Welcome to our store" │ ← Cursor change on hover
│ [Featured image] │
└─────────────────────────────────┘
On Hover:
╔════════════════════════════════╗
║ Hero Section ║ ← Blue border appears
║ [Actual rendered content] ║ ← Handles show: [↑][↓][◆]
║ "Welcome to our store" ║ ← "Click to edit" hint
║ [Featured image] ║
║ Drag handle: [:::] ║ ← Reorder handle visible
╚════════════════════════════════╝
On Click (Selected):
╔════════════════════════════════╗
║ Hero Section ║ ← Bold blue border
║ [Actual rendered content] ║ ← Selection handles
║ "Welcome to our store" ║ ← Full edit mode
║ [Featured image] ║ ← Right panel populated
║ [≣ Move ↑ ↓] [⊕ Duplicate] [✕]║ ← Context menu
╚════════════════════════════════╝
↓ Right panel updates:
Section Config
├─ Layout: [Hero-Left ▼]
├─ Color: [Primary ▼]
├─ Title: [Welcome__________]
└─ Image: [Choose Image ▼]
Interaction Handlers
// Canvas Section Component
<CanvasSection
section={section}
isSelected={selectedId === section.id}
// Selection
onClick={() => setSelectedSection(section.id)}
onMouseEnter={() => setHoveredId(section.id)}
onMouseLeave={() => setHoveredId(null)}
// Reordering
onDragStart={() => initDragReorder(section.id)}
onDragOver={(e) => e.preventDefault()}
onDrop={() => performDragReorder(section.id)}
// Quick actions
onDuplicate={() => duplicateSection(section.id)}
onDelete={() => deleteSection(section.id)}
onMoveUp={() => moveSection(section.id, 'up')}
onMoveDown={() => moveSection(section.id, 'down')}
/>
Inspector Panel (Right Side)
Inspector Behavior
Collapsible:
┌──────────────────┐
│ [Inspector] [◄] │ ← Click to collapse (inspector slides out)
│ │
│ Section Config │
│ ├─ Layout: [▼] │
│ ├─ Title: [____] │
│ └─ Image: [▼] │
└──────────────────┘
When collapsed:
[Canvas takes 70% width instead of 60%]
Responsive to Selection:
No section selected:
┌──────────────────┐
│ Page Settings │
├─ Title: [About] │
├─ Slug: [/about] │
└─ SEO: [Manage→] │
└──────────────────┘
Hero section selected:
┌──────────────────┐
│ Section Config │
│ Type: Hero │
├─ Layout: [▼] │
├─ Title: [____] │
└─ Image: [▼] │
└──────────────────┘
Content section selected:
┌──────────────────┐
│ Section Config │
│ Type: Content │
├─ Source: [▼] │ ← Dynamic: post_content
│ ○ Static text │
│ ◉ Post Body │
└─ [Dynamic ◆] │
└──────────────────┘
Inspector Field Types
For Static Fields:
Title Field
[Text input____________]
Description
[Large textarea___
_________________
_________________]
Image
[Choose from Media] or [Upload new]
[Preview thumbnail]
For Dynamic Fields (CPT Templates):
Title Source
○ Static text: [__________]
◉ Dynamic: [Post Title ▼]
• Post Title
• Post Excerpt
• Custom Field X
[◆ Dynamic] indicator shown
Layout Controls:
Layout Variant
[Hero-Left ▼]
├─ Hero-Left
├─ Hero-Right
├─ Hero-Center
└─ Hero-Full
Color Scheme
[Primary ▼]
├─ Primary
├─ Secondary
├─ Accent
└─ Dark
Canvas UI Features
Section Card Appearance
In Canvas:
Rendered like the actual front-end:
╔═══════════════════════════════════╗
║ HERO SECTION ║ ← Looks like real page
║ ║
║ Welcome to our store ║ ← Actual text
║ ┌─────────────────────────────┐ ║
║ │ [Featured Image] │ ║ ← Actual image
║ │ (500x300px) │ ║
║ └─────────────────────────────┘ ║
║ ║
║ Join our community today ║ ← Button
║ [SHOP NOW →] ║
║ ║
╚═══════════════════════════════════╝
NOT like current cards:
❌ WRONG (Current):
┌─────────────────────────┐
│ Hero [default] │
│ [↑] [↓] [x] │
│ ─────────────────────── │
Add Section UI
Floating Button Below Sections:
Canvas shows sections:
╔═══════════════════════════════════╗
║ HERO SECTION ║
╚═══════════════════════════════════╝
[+ Add section] ← Click to open section picker
├─ Hero
├─ Image + Text
├─ Feature Grid
├─ Related Items
├─ CTA Banner
└─ Content
Or inline:
╔═══════════════════════════════════╗
║ CTA BANNER ║
╚═══════════════════════════════════╝
[+ Add section below] ← Click here
Responsive Preview Toggle
Canvas shows device toggle:
[Desktop] [Mobile] ← Toggle between previews
Desktop (full width, e.g., 1024px)
╔═══════════════════════════════════╗
║ Page at desktop width ║
╚═══════════════════════════════════╝
Mobile (narrow width, e.g., 375px)
╔════════════╗
║ Page at ║
║ mobile ║
║ width ║
╚════════════╝
Data Flow & State Management
Single Source of Truth
┌─────────────────────────────────┐
│ Editor State (Zustand/Redux) │
│ │
│ { │
│ pageId: "about", │
│ pageType: "page", │
│ title: "About Us", │
│ slug: "about", │
│ sections: [ │
│ { │
│ id: "sec-1", │
│ type: "hero", │
│ layoutVariant: "left", │
│ props: { │
│ title: { │
│ type: "static", │
│ value: "Welcome" │
│ }, │
│ image: { │
│ type: "static", │
│ value: "/img.jpg" │
│ } │
│ } │
│ } │
│ ], │
│ selectedSectionId: "sec-1", │
│ hasUnsavedChanges: true │
│ } │
└─────────────────────────────────┘
↓ (Consumed by):
├─ CanvasRenderer
│ └─ Renders sections to UI
│
├─ InspectorPanel
│ └─ Shows/edits selected section
│
├─ PreviewToggle
│ └─ Desktop/Mobile responsive
│
└─ SaveButton
└─ POST to /wp-json/woonoow/v1/pages/{slug}
Real-Time Updates
User changes field in Inspector:
[Title input] → onChange event
↓
Store update:
sections[0].props.title.value = "New Title"
↓
React re-renders:
<CanvasRenderer sections={sections} />
↓
Canvas shows updated title instantly
(No save needed, live preview)
Component Structure
Main Components
<PageEditor> ← Top container
├─ <Sidebar> ← Left navigation
│ ├─ <PagesList> ← Structural pages
│ └─ <TemplatesList> ← CPT templates
│
├─ <Canvas> ← Main workspace
│ ├─ <ResponsiveToggle> ← Desktop/Mobile
│ └─ <CanvasRenderer> ← Renders sections
│ ├─ <CanvasSection> ← Each section
│ │ ├─ <HeroRenderer> ← Section-specific
│ │ ├─ <ContentRenderer>
│ │ ├─ <FeatureGridRenderer>
│ │ └─ ...
│ └─ <AddSectionButton>
│
├─ <Inspector> ← Right panel
│ ├─ <PageSettings> ← When no section selected
│ ├─ <SectionSettings> ← When section selected
│ └─ <PreviewToggle> ← Collapse button
│
└─ <BottomBar> ← Actions
├─ <DiscardButton>
├─ <SaveButton>
└─ <PreviewButton>
Canvas Section Component Pattern
// Each section type follows same pattern:
function HeroSectionRenderer({ section, isSelected, onSelect }) {
return (
<CanvasSection
isSelected={isSelected}
onClick={onSelect}
className="section-hero"
>
{/* Renders actual content */}
<h1>{section.props.title.value}</h1>
<img src={section.props.image.value} />
<p>{section.props.subtitle.value}</p>
{/* Selection UI overlays */}
{isSelected && (
<SelectionOverlay>
<DragHandle />
<ContextMenu />
</SelectionOverlay>
)}
</CanvasSection>
);
}
// Same for Content, FeatureGrid, CTA, etc.
Selection & Interaction Feedback
Visual Selection Indicators
Unselected:
┌─────────────────────────────┐
│ Hero Section │ ← Gray border (1px)
│ [Content] │ ← Normal opacity
└─────────────────────────────┘
Hovered:
┌─────────────────────────────┐
│ Hero Section │ ← Light blue border (1px)
│ [Content] │ ← Subtle shadow
│ [↑ ↓] │ ← Move handles appear
└─────────────────────────────┘
Selected:
╔═════════════════════════════╗
║ Hero Section ║ ← Bold blue border (2px)
║ [Content] ║ ← Highlight background
║ ║
║ [≣] [↑↓] [⊕] [✕] ║ ← Full action menu
╚═════════════════════════════╝
Drag & Drop Reordering
// User drags section up:
Before:
╔═══════════════════╗
║ HERO (drag me) ║ ← Dragging
║ [↑ ↓] ║
╚═══════════════════╝
╔═══════════════════╗
║ IMAGE+TEXT ║ ← Drop zone appears
║ [↑ ↓] ║
╚═══════════════════╝
After drop:
╔═══════════════════╗
║ HERO ║
╚═══════════════════╝
╔═══════════════════╗
║ IMAGE+TEXT ║
╚═══════════════════╝
Save & Persistence Flow
Auto-Save vs Manual Save
Current approach: Manual Save
User edits → Real-time canvas update (no server call)
[Save Changes] button → POST to API → Save to wp_options/postmeta
[Discard Changes] button → Reset to last saved state
Optional: Auto-Save (Future)
User edits → Real-time canvas update
[Unsaved changes indicator] appears
Auto-save after 3 seconds of inactivity
Or manual save with [Save] button
Save Endpoints
POST /wp-json/woonoow/v1/pages/about
{
"sections": [...]
}
→ Saves to wp_postmeta (_wn_page_structure)
POST /wp-json/woonoow/v1/templates/post
{
"sections": [...]
}
→ Saves to wp_options (wn_template_post)
Responsive Canvas Behavior
Desktop View (Default)
Full-width canvas showing page at 1024px width
┌─────────────────────────────────┐
│ [Desktop] [Mobile] │
│ │
│ Canvas width: 1024px │
│ Shows full page layout │
└─────────────────────────────────┘
Mobile View
Canvas showing page at 375px width
┌──────────┐
│[D][M] │
│ │
│ Canvas │
│ width: │
│ 375px │
│ │
│ Shows │
│ mobile │
│ layout │
│ │
└──────────┘
Scroll Behavior
- Canvas is scrollable (overflow-y: auto)
- Sections stack vertically
- Inspector panel stays fixed (top-right)
- Bottom action bar always visible
Merchant Experience Flow
Typical Edit Session
-
Select page from sidebar
Sidebar: [About Us] ← Click Canvas loads page structure Inspector shows page settings -
Click section to edit
Canvas: [Hero section] ← Click Section highlights (blue border) Inspector updates: Section Config for Hero -
Change fields in Inspector
Inspector: Title = [Welcome_____] ← Type "Welcome to us" Canvas updates instantly No server call needed yet -
Reorder sections (optional)
Canvas: Drag [Hero] above [Image+Text] Sections reorder instantly -
Add new section
Canvas: [+ Add section] ← Click Section picker popup Select "CTA Banner" New section appears at bottom Inspector shows CTA settings -
Save all changes
Bottom bar: [Save Changes] ← Click POST to /wp-json/woonoow/v1/pages/about Sections saved to database "Page saved" toast notification
Implementation Phases (Enhanced)
Phase 1: Canvas Rendering System (Priority)
- Refactor state from Redux/Zustand (unified store)
- Build
CanvasRenderercomponent- Maps sections array to React components
- Zero-iframe approach (direct rendering)
- Responsive device simulation (CSS media queries)
- Build section renderers (Hero, Content, Feature Grid, CTA, Related)
- Render actual content, not placeholder cards
- Show dynamic placeholders with [◆] badges
- Add responsive toggle (Desktop/Mobile)
- Test real-time re-rendering on state changes
Phase 2: Canvas Interaction (Click, Hover, Select)
- Click handler → Set selected section in state
- Hover handler → Show selection handles
- Visual feedback (borders, shadows, highlights)
- Context menu (Move up, Down, Duplicate, Delete)
- Add section button (opens section picker)
Phase 3: Inspector Panel (Settings)
- Collapse/expand button
- Page settings vs Section settings
- Dynamic field type detection
- Static: text input, textarea, image picker
- Dynamic: dropdown with available sources
- Real-time sync (change field → canvas updates)
Phase 4: Drag & Drop Reordering
- Drag start/over/drop handlers
- Visual drag feedback (opacity, shadow)
- Reorder state updates
- Test with multiple sections
Phase 5: Polish & Launch
- Save/Discard buttons
- Unsaved changes warning
- Toast notifications (saved, error)
- Keyboard shortcuts (ESC to deselect, etc.)
- Mobile-friendly inspector (for tablet editing)
- Performance optimization (memoization, lazy rendering)
Code Example: Canvas Component Structure
// pages/editor/PageEditor.tsx
export function PageEditor() {
const [pageState, dispatch] = useReducer(pageReducer, initialState);
const [selectedSectionId, setSelectedSectionId] = useState(null);
const [deviceMode, setDeviceMode] = useState('desktop');
const handleSectionSelect = (sectionId) => {
setSelectedSectionId(sectionId);
};
const handleSectionDelete = (sectionId) => {
dispatch({
type: 'DELETE_SECTION',
payload: { sectionId }
});
};
const handleSectionDuplicate = (sectionId) => {
dispatch({
type: 'DUPLICATE_SECTION',
payload: { sectionId }
});
};
const handleFieldChange = (sectionId, fieldPath, value) => {
dispatch({
type: 'UPDATE_SECTION_PROP',
payload: { sectionId, fieldPath, value }
});
};
const handleSave = async () => {
const response = await fetch(`/wp-json/woonoow/v1/pages/${pageState.slug}`, {
method: 'POST',
body: JSON.stringify({ sections: pageState.sections })
});
// Show toast notification
};
return (
<div className="page-editor">
<Sidebar
pages={pageState.pages}
selectedPageId={pageState.id}
/>
<Canvas
sections={pageState.sections}
selectedSectionId={selectedSectionId}
deviceMode={deviceMode}
onSelectSection={handleSectionSelect}
onDeleteSection={handleSectionDelete}
onDuplicateSection={handleSectionDuplicate}
onFieldChange={handleFieldChange}
/>
<Inspector
pageState={pageState}
selectedSectionId={selectedSectionId}
onFieldChange={handleFieldChange}
onDeviceChange={setDeviceMode}
/>
<BottomBar
onSave={handleSave}
onDiscard={() => location.reload()}
/>
</div>
);
}
// components/Canvas.tsx
export function Canvas({
sections,
selectedSectionId,
deviceMode,
onSelectSection,
onFieldChange,
...handlers
}) {
return (
<div className="canvas-container">
<DeviceToggle mode={deviceMode} onChange={setDeviceMode} />
<div className={`canvas-viewport canvas-${deviceMode}`}>
{sections.map((section) => (
<CanvasSection
key={section.id}
section={section}
isSelected={selectedSectionId === section.id}
onClick={() => onSelectSection(section.id)}
{...handlers}
>
<SectionRenderer
section={section}
onFieldChange={(field, value) =>
onFieldChange(section.id, field, value)
}
/>
</CanvasSection>
))}
<AddSectionButton onClick={openSectionPicker} />
</div>
</div>
);
}
// components/CanvasSection.tsx
export function CanvasSection({ section, isSelected, children, ...handlers }) {
return (
<div
className={`canvas-section ${isSelected ? 'selected' : ''}`}
onClick={handlers.onClick}
onDragStart={handlers.onDragStart}
onDragOver={handlers.onDragOver}
onDrop={handlers.onDrop}
>
{/* Actual rendered content */}
{children}
{/* Selection overlay */}
{isSelected && (
<SelectionOverlay>
<DragHandle />
<ContextMenu
onDuplicate={() => handlers.onDuplicate(section.id)}
onDelete={() => handlers.onDelete(section.id)}
/>
</SelectionOverlay>
)}
</div>
);
}
// components/Inspector.tsx
export function Inspector({ pageState, selectedSectionId, onFieldChange }) {
if (!selectedSectionId) {
return <PageSettings pageState={pageState} />;
}
const section = pageState.sections.find(s => s.id === selectedSectionId);
return (
<div className="inspector-panel">
<div className="section-config">
<h3>{section.type}</h3>
{Object.entries(section.props).map(([fieldName, fieldData]) => (
<InspectorField
key={fieldName}
fieldName={fieldName}
fieldData={fieldData}
onChange={(value) =>
onFieldChange(selectedSectionId, `props.${fieldName}.value`, value)
}
/>
))}
<button onClick={() => handleDelete(selectedSectionId)}>
Delete Section
</button>
</div>
<Canvas preview />
</div>
);
}
// components/InspectorField.tsx
export function InspectorField({ fieldName, fieldData, onChange }) {
if (fieldData.type === 'static') {
return (
<div className="inspector-field">
<label>{fieldName}</label>
<input
type="text"
value={fieldData.value}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
}
if (fieldData.type === 'dynamic') {
return (
<div className="inspector-field">
<label>{fieldName} <span className="dynamic-badge">[◆]</span></label>
<select value={fieldData.source} onChange={(e) => onChange(e.target.value)}>
<option value="post_title">Post Title</option>
<option value="post_content">Post Content</option>
<option value="post_featured_image">Featured Image</option>
{/* More options */}
</select>
</div>
);
}
}
CSS Utilities for Canvas
/* Canvas container */
.canvas-container {
flex: 1;
background: #f5f5f5;
overflow-y: auto;
padding: 20px;
}
/* Device modes */
.canvas-viewport {
background: white;
margin: 0 auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.canvas-desktop {
max-width: 1024px;
}
.canvas-mobile {
max-width: 375px;
}
/* Sections */
.canvas-section {
position: relative;
padding: 0;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 16px;
transition: all 0.2s ease;
}
.canvas-section:hover {
border-color: #90caf9;
box-shadow: 0 2px 8px rgba(144, 202, 249, 0.3);
}
.canvas-section.selected {
border-color: #1976d2;
border-width: 2px;
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
}
/* Selection overlay */
.selection-overlay {
position: absolute;
top: 0;
right: 0;
display: flex;
gap: 8px;
padding: 8px;
background: rgba(25, 118, 210, 0.05);
border-radius: 4px;
opacity: 0;
}
.canvas-section.selected .selection-overlay {
opacity: 1;
}
/* Dynamic badge */
.dynamic-badge {
display: inline-block;
width: 14px;
height: 14px;
background: #ff9800;
color: white;
border-radius: 50%;
text-align: center;
font-size: 10px;
line-height: 14px;
margin-left: 4px;
}
Benefits of This Approach
| Benefit | Explanation |
|---|---|
| WYSIWYG | See actual page appearance while editing |
| No iframe overhead | Direct component rendering = faster updates |
| No MutationObserver | JSON → React = clean state management |
| Instant feedback | Change field → canvas updates in milliseconds |
| Simpler code | No iframe communication, no sandbox complexity |
| Better mobile edit | Responsive canvas shows mobile view directly |
| Easier debugging | Single React component tree, standard dev tools |
| Faster iteration | Less complexity = faster implementation |
| Merchant friendly | See exactly what customers will see |
Next Steps
- ✅ Refactor state management (unified store)
- ✅ Build CanvasRenderer component
- ✅ Implement section click/hover interactions
- ✅ Connect Inspector panel to canvas
- ✅ Add drag-and-drop reordering
- ✅ Test responsive device modes
- ✅ Implement save/discard flow
- ✅ Polish UX (animations, feedback)