feat: Page Editor Phase 1 - Core Infrastructure
- Add is_bot() detection in TemplateOverride.php (30+ bot patterns)
- Add PageSSR.php for server-side rendering of page sections
- Add PlaceholderRenderer.php for dynamic content resolution
- Add PagesController.php REST API for pages/templates CRUD
- Register PagesController routes in Routes.php
API Endpoints:
- GET /pages - list all pages/templates
- GET /pages/{slug} - get page structure
- POST /pages/{slug} - save page
- GET /templates/{cpt} - get CPT template
- POST /templates/{cpt} - save template
- GET /content/{type}/{slug} - get content with template applied
This commit is contained in:
781
woonoow-page-editor-brief.md
Normal file
781
woonoow-page-editor-brief.md
Normal file
@@ -0,0 +1,781 @@
|
||||
# WooNooW Page Editor Strategy - Final Decision Brief (v3)
|
||||
|
||||
## Overview
|
||||
Extend WooNooW SPA to support structural pages and CPT templates with:
|
||||
- **Unified Page Editor** for pages (page CPT) and CPT templates (post, portfolio, custom)
|
||||
- **Native WordPress `page` CPT** for structural pages (SEO compatible)
|
||||
- **Dynamic placeholders** for CPT templates (blog posts, portfolio items, etc.)
|
||||
- **Dual rendering strategy** for bots vs humans (SSR + SPA redirect)
|
||||
- **Zero page builder plugin dependencies**
|
||||
- **Integrated admin UI** with collapsible sidebar
|
||||
|
||||
---
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Data Storage
|
||||
|
||||
#### Structural Pages (page CPT)
|
||||
- **Location**: WordPress `wp_posts` table (post_type='page')
|
||||
- **Structure Storage**: `wp_postmeta`, key: `_wn_page_structure`
|
||||
- **Type**: Static content only (no placeholders)
|
||||
- **Example**: Homepage, About, Contact, Terms, Privacy
|
||||
|
||||
#### CPT Templates
|
||||
- **Location**: WordPress `wp_options` table
|
||||
- **Storage Keys**: `wn_template_{cpt_name}`
|
||||
- `wn_template_post` (for blog posts)
|
||||
- `wn_template_portfolio` (for portfolio items)
|
||||
- `wn_template_custom_cpt` (for custom CPTs)
|
||||
- **Type**: Dynamic content with placeholders
|
||||
- **Applies To**: All items of that CPT (one template per CPT)
|
||||
|
||||
### Page Structure Storage Format
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page|template",
|
||||
"sections": [
|
||||
{
|
||||
"id": "section-1",
|
||||
"type": "hero",
|
||||
"layoutVariant": "hero-left-image",
|
||||
"colorScheme": "primary",
|
||||
"props": {
|
||||
"title": {
|
||||
"type": "static",
|
||||
"value": "Welcome"
|
||||
},
|
||||
"subtitle": {
|
||||
"type": "static",
|
||||
"value": "Join us"
|
||||
},
|
||||
"image": {
|
||||
"type": "static",
|
||||
"value": "/uploads/hero.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "section-2",
|
||||
"type": "content",
|
||||
"props": {
|
||||
"content": {
|
||||
"type": "dynamic",
|
||||
"source": "post_content"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "section-3",
|
||||
"type": "feature-grid",
|
||||
"layoutVariant": "grid-3-columns",
|
||||
"props": {
|
||||
"heading": {
|
||||
"type": "static",
|
||||
"value": "Why Choose Us"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Field Types: Static vs Dynamic
|
||||
|
||||
**Static Fields** (Structural Pages):
|
||||
```json
|
||||
{
|
||||
"type": "static",
|
||||
"value": "Hardcoded text or value"
|
||||
}
|
||||
```
|
||||
|
||||
**Dynamic Fields** (CPT Templates):
|
||||
```json
|
||||
{
|
||||
"type": "dynamic",
|
||||
"source": "post_title|post_content|post_excerpt|post_featured_image|related_posts|..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Available Dynamic Sources (per CPT)
|
||||
|
||||
**For `post` CPT:**
|
||||
- `post_title` - Post title
|
||||
- `post_excerpt` - Post excerpt
|
||||
- `post_content` - Post body content
|
||||
- `post_featured_image` - Featured image URL
|
||||
- `post_author` - Author name
|
||||
- `post_date` - Publication date
|
||||
- `post_categories` - Category list
|
||||
- `post_tags` - Tag list
|
||||
- `related_posts` - Query for related posts (with count parameter)
|
||||
|
||||
**For Custom CPTs:**
|
||||
- `{cpt_name}_title` - CPT item title
|
||||
- `{cpt_name}_featured_image` - Featured image
|
||||
- `{cpt_name}_field_{field_name}` - Custom meta fields
|
||||
- Custom sources defined per CPT
|
||||
|
||||
---
|
||||
|
||||
## Unified Page Editor Concept
|
||||
|
||||
### Single Editor, Two Modes
|
||||
|
||||
**Mode 1: Structural Pages (Static)**
|
||||
- Edit individual pages (page CPT)
|
||||
- All content is static/hardcoded
|
||||
- Examples: Home, About, Contact, Terms, Privacy
|
||||
- SEO managed by Yoast/Rank Math
|
||||
|
||||
**Mode 2: CPT Templates (Dynamic)**
|
||||
- Design template for entire CPT
|
||||
- Content has dynamic placeholders
|
||||
- One template applies to all items of that CPT
|
||||
- Examples: Blog Post template, Portfolio template, News template
|
||||
|
||||
### Create New Page Flow
|
||||
|
||||
When user clicks `[+ Create Page]`:
|
||||
|
||||
```
|
||||
Modal Dialog:
|
||||
┌──────────────────────────────────┐
|
||||
│ Create New Page │
|
||||
├──────────────────────────────────┤
|
||||
│ │
|
||||
│ What are you creating? │
|
||||
│ │
|
||||
│ ○ Structural Page │
|
||||
│ Static content (page CPT) │
|
||||
│ Examples: About, Contact │
|
||||
│ Permalink: /page-slug │
|
||||
│ │
|
||||
│ ○ Blog Post Template │
|
||||
│ Dynamic template for posts │
|
||||
│ Applies to: /blog/* │
|
||||
│ Includes placeholders │
|
||||
│ │
|
||||
│ ○ Portfolio Template │
|
||||
│ Dynamic template for portfolio │
|
||||
│ Applies to: /portfolio/* │
|
||||
│ Includes placeholders │
|
||||
│ │
|
||||
│ ○ Other CPT Template │
|
||||
│ [Select CPT ▼] │
|
||||
│ Applies to: /cpt-base/* │
|
||||
│ │
|
||||
│ [Cancel] [Next] │
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Editor Left Panel: Pages List (Unified)
|
||||
|
||||
```
|
||||
Pages Sidebar
|
||||
|
||||
📄 STRUCTURAL PAGES
|
||||
├─ ✓ Homepage (page)
|
||||
├─ ○ About (page)
|
||||
├─ ○ Contact (page)
|
||||
└─ ○ Terms (page)
|
||||
|
||||
📋 TEMPLATES (Dynamic)
|
||||
├─ ○ Blog Post (post)
|
||||
│ └─ Applies to: /blog/*
|
||||
├─ ○ Portfolio (portfolio)
|
||||
│ └─ Applies to: /portfolio/*
|
||||
├─ ○ News (custom_cpt)
|
||||
│ └─ Applies to: /news/*
|
||||
└─ ○ Custom CPT (custom_name)
|
||||
└─ Applies to: /custom-base/*
|
||||
|
||||
Visual indicators:
|
||||
- 📄 = Structural page (static)
|
||||
- 📋 = Template (dynamic)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rendering Strategy: Dual Path
|
||||
|
||||
### Path 1: Bot Detection → Server-Side Rendering (SSR)
|
||||
|
||||
**When**: Bot user agent detected
|
||||
**Action**: Serve pre-rendered HTML (no redirect)
|
||||
**Content**: All sections rendered as static HTML strings
|
||||
**Placeholders**: Replaced with actual post data at render time
|
||||
**Result**: ✅ Full content indexed by search engines
|
||||
|
||||
```
|
||||
Bot visits /blog/my-article
|
||||
↓
|
||||
is_bot() === true
|
||||
↓
|
||||
Get post data + load post template
|
||||
↓
|
||||
Replace placeholders (post_title, post_content, etc.)
|
||||
↓
|
||||
Render sections to static HTML
|
||||
↓
|
||||
Output: <html><h1>Article Title</h1><p>Post content...</p></html>
|
||||
↓
|
||||
✓ Bot crawls full content
|
||||
✓ All text indexed
|
||||
✓ All links followable
|
||||
✓ SEO perfect
|
||||
```
|
||||
|
||||
### Path 2: Human Detection → SPA Redirect
|
||||
|
||||
**When**: Human user agent detected
|
||||
**Action**: HTTP 302 redirect to SPA single-page app
|
||||
**Destination**: Matches WordPress permalink structure
|
||||
- `/about` → `/store/about`
|
||||
- `/blog/slug` → `/store/blog/slug`
|
||||
- `/portfolio/slug` → `/store/portfolio/slug`
|
||||
**Result**: ✅ Beautiful interactive React UI
|
||||
|
||||
```
|
||||
Human visits /blog/my-article
|
||||
↓
|
||||
is_bot() === false
|
||||
↓
|
||||
HTTP 302 → /store/blog/my-article
|
||||
↓
|
||||
SPA loads at /store/blog/my-article
|
||||
↓
|
||||
React detects path base (/blog) → loads post template
|
||||
↓
|
||||
React fetches post data for "my-article"
|
||||
↓
|
||||
React replaces placeholders with post data
|
||||
↓
|
||||
Renders sections with React components
|
||||
↓
|
||||
✓ Interactive UI
|
||||
✓ No page reload
|
||||
✓ Great UX
|
||||
```
|
||||
|
||||
### Bot Detection Logic
|
||||
|
||||
Detect bots by User-Agent string:
|
||||
```php
|
||||
$bot_patterns = [
|
||||
'googlebot', 'bingbot', 'slurp', 'duckduckbot',
|
||||
'baiduspider', 'yandexbot', 'crawler', 'robot',
|
||||
'spider', 'facebookexternalhit', 'twitterbot',
|
||||
'linkedinbot', 'whatsapp', 'slackbot', 'applebot'
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SPA Architecture (Enhanced)
|
||||
|
||||
### SPA Remains Centralized
|
||||
- **Single page location**: WordPress page (configurable, e.g., `/store/`)
|
||||
- **Single React router**: Handles all SPA routes
|
||||
- **Routes**:
|
||||
- `/` - Homepage (products)
|
||||
- `/shop` - Shop listing
|
||||
- `/product/:slug` - Product detail
|
||||
- `/cart` - Shopping cart
|
||||
- `/checkout` - Checkout
|
||||
- `/my-account/*` - User account
|
||||
- **NEW Dynamic Routes**:
|
||||
- `/:slug` - Structural page (About, Contact, etc.)
|
||||
- `/:pathBase/:slug` - CPT item (blog post, portfolio, etc.)
|
||||
|
||||
### Dynamic Route Matching
|
||||
|
||||
```tsx
|
||||
// React Router detects what to render
|
||||
const router = createBrowserRouter([
|
||||
// Existing routes (higher priority)
|
||||
{ path: '/', element: <Shop /> },
|
||||
{ path: '/shop', element: <Shop /> },
|
||||
{ path: '/product/:slug', element: <Product /> },
|
||||
|
||||
// NEW: Dynamic routes (lower priority)
|
||||
{ path: '/:pathBase/:slug', element: <DynamicPageRenderer /> },
|
||||
{ path: '/:slug', element: <DynamicPageRenderer /> },
|
||||
]);
|
||||
|
||||
// DynamicPageRenderer logic
|
||||
function DynamicPageRenderer() {
|
||||
const { pathBase, slug } = useParams();
|
||||
const [pageData, setPageData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Determine what template to use
|
||||
if (!pathBase) {
|
||||
// /:slug → Structural page (About, Contact, etc.)
|
||||
fetchPage(slug);
|
||||
} else {
|
||||
// /:pathBase/:slug → CPT item
|
||||
detectCPTFromPath(pathBase); // /blog → post, /portfolio → portfolio
|
||||
fetchCPTTemplate(cptType);
|
||||
fetchPostData(slug);
|
||||
}
|
||||
}, [pathBase, slug]);
|
||||
|
||||
return <PageRenderer pageData={pageData} />;
|
||||
}
|
||||
```
|
||||
|
||||
### No Multiple "SPA Islands"
|
||||
- Pages route through same SPA instance
|
||||
- Same styling, same theme variables, same JavaScript context
|
||||
|
||||
---
|
||||
|
||||
## Page Editor UI Layout (3-Column Design)
|
||||
|
||||
### Visual Layout
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [☰] WooNooW Appearance > Pages > [Blog Post Template] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────┬──────────────────┬──────────────────────┐ │
|
||||
│ │ Pages │ │ │ │
|
||||
│ │ Sidebar │ Editor Panel │ Settings + Preview │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ 📄 Home │ SECTIONS │ Page Settings │ │
|
||||
│ │ ○ About │ ────────────────│ ├─ Type: Template │ │
|
||||
│ │ ○ Contact │ 1. Hero Section │ ├─ CPT: post │ │
|
||||
│ │ │ [Image ◆] │ └─ Permalink: /blog │ │
|
||||
│ │ 📋 Posts │ 2. Content │ │ │
|
||||
│ │ ○ Portfolio│ [Post Body ◆]│ Section Settings │ │
|
||||
│ │ ○ News │ 3. Related Posts│ ├─ Layout: Grid 3 │ │
|
||||
│ │ │ [Query ◆] │ ├─ Count: [3___] │ │
|
||||
│ │ [+ Create] │ 4. CTA Banner │ └─ [Delete] │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ [+ Add Section] │ Preview │ │
|
||||
│ │ │ │ [Desktop] [Mobile] │ │
|
||||
│ │ │ Legend: │ ┌──────────────────┐│ │
|
||||
│ │ │ [◆] = Dynamic │ │ Live SPA Preview ││ │
|
||||
│ │ │ placeholder │ │ (Real-time) ││ │
|
||||
│ │ │ │ └──────────────────┘│ │
|
||||
│ │ │ │ │ │
|
||||
│ └────────────┴──────────────────┴──────────────────────┘ │
|
||||
│ │
|
||||
│ [Discard Changes] [Save Changes] [Preview] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Settings Panel Behavior
|
||||
|
||||
**For Structural Pages (No Section Selected):**
|
||||
```
|
||||
Page Settings
|
||||
├─ Type: Page (Structural)
|
||||
├─ Title: About Us
|
||||
├─ Slug: about
|
||||
└─ SEO: Manage in Yoast [→]
|
||||
```
|
||||
|
||||
**For Templates (No Section Selected):**
|
||||
```
|
||||
Template Settings
|
||||
├─ Type: Template (Dynamic)
|
||||
├─ CPT: Post
|
||||
├─ Applies to: /blog/*
|
||||
└─ Edit CPT permalink: [→]
|
||||
```
|
||||
|
||||
**For Sections with Dynamic Fields:**
|
||||
```
|
||||
Section Settings: Hero
|
||||
|
||||
Layout Variant
|
||||
[Hero Left Image ▼]
|
||||
|
||||
Title Field
|
||||
○ Static: [____________]
|
||||
◉ Dynamic: [Post Title ◆]
|
||||
|
||||
Image Field
|
||||
○ Static: [Choose Image]
|
||||
◉ Dynamic: [Featured Image ◆]
|
||||
|
||||
[Delete Section]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin SPA Integration
|
||||
|
||||
### Navigation Location
|
||||
- **Menu**: Appearance (existing)
|
||||
- **Submenu**: Pages (NEW)
|
||||
- **Path**: `/admin/appearance/pages`
|
||||
|
||||
### Sidebar Behavior
|
||||
|
||||
**Default (Expanded):**
|
||||
- Admin sidebar shows full menu with labels
|
||||
- Page editor takes remaining width (3-column layout)
|
||||
|
||||
**Collapsed:**
|
||||
- Admin sidebar shows icons only (~60px width)
|
||||
- Page editor expands to use more width
|
||||
- Tooltips show on hover
|
||||
|
||||
**Layout dimensions:**
|
||||
|
||||
| State | Sidebar | Content Area | Pages List | Editor | Settings+Preview |
|
||||
|-------|---------|--------------|-----------|--------|------------------|
|
||||
| Expanded | 240px | ~1160px | 120px | 650px | 390px |
|
||||
| Collapsed | 60px | ~1340px | 120px | 750px | 470px |
|
||||
|
||||
### No Full-Screen Takeover
|
||||
- ✅ Part of admin SPA normal content flow
|
||||
- ✅ User maintains context (can quickly jump to other admin sections)
|
||||
- ✅ Sidebar collapse is user-controlled, not forced
|
||||
- ✅ Can navigate back via breadcrumb or admin menu
|
||||
|
||||
### Escape Routes
|
||||
1. Click WooNooW logo → Dashboard
|
||||
2. Click any menu item in sidebar → Navigate to that section
|
||||
3. Press ESC key → Back (with unsaved changes warning)
|
||||
4. Click browser back button → Back (with unsaved changes warning)
|
||||
5. Click breadcrumb "Appearance" → Back to Appearance settings
|
||||
|
||||
---
|
||||
|
||||
## SEO Preservation
|
||||
|
||||
### Yoast/Rank Math Integration
|
||||
- ✅ Meta title, meta description managed by Yoast/Rank Math
|
||||
- ✅ Canonical URLs managed by WordPress
|
||||
- ✅ Open Graph tags managed by SEO plugins
|
||||
- ✅ Structured data can be added to page template
|
||||
- ✅ No changes needed to existing SEO workflow
|
||||
|
||||
### For CPT Items (Posts, Portfolio, etc.)
|
||||
- Yoast/Rank Math still manages SEO for individual CPT items
|
||||
- Templates don't interfere with SEO workflow
|
||||
- Bot sees pre-rendered content with all placeholders filled
|
||||
|
||||
### What Bots See
|
||||
|
||||
**Example: Blog Post with Template**
|
||||
```html
|
||||
<h1>Article Title</h1>
|
||||
<img src="/uploads/featured.jpg" alt="Article featured image" />
|
||||
<article>
|
||||
<p>Article body content goes here...</p>
|
||||
</article>
|
||||
<section class="related-posts">
|
||||
<h2>Related Articles</h2>
|
||||
<div class="grid">
|
||||
<a href="/blog/related-1">Related Post 1</a>
|
||||
<a href="/blog/related-2">Related Post 2</a>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
- ✅ All text crawlable
|
||||
- ✅ All links followable
|
||||
- ✅ Structure preserved
|
||||
- ✅ Meta tags present
|
||||
- ✅ Images indexed
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Get All Pages & Templates
|
||||
```
|
||||
GET /wp-json/woonoow/v1/pages
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 42,
|
||||
"type": "page",
|
||||
"slug": "home",
|
||||
"title": "Homepage",
|
||||
"icon": "page"
|
||||
},
|
||||
{
|
||||
"type": "template",
|
||||
"cpt": "post",
|
||||
"title": "Blog Post Template",
|
||||
"icon": "template",
|
||||
"permalink_base": "/blog/"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get Page or Template Structure
|
||||
```
|
||||
GET /wp-json/woonoow/v1/pages/about
|
||||
GET /wp-json/woonoow/v1/templates/post
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"type": "page|template",
|
||||
"id": 42,
|
||||
"slug": "about",
|
||||
"title": "About Us",
|
||||
"url": "https://mystore.com/about",
|
||||
"seo": {
|
||||
"meta_title": "About Us | My Store",
|
||||
"meta_description": "Learn about our company...",
|
||||
"canonical": "https://mystore.com/about",
|
||||
"og_title": "About Us",
|
||||
"og_image": "https://mystore.com/og-image.jpg"
|
||||
},
|
||||
"structure": {
|
||||
"sections": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Single Post with Template Applied
|
||||
```
|
||||
GET /wp-json/woonoow/v1/posts/{post_id}/with-template
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"post": {
|
||||
"id": 123,
|
||||
"title": "Article Title",
|
||||
"content": "Article body...",
|
||||
"featured_image": "/uploads/image.jpg",
|
||||
"author": "John Doe",
|
||||
"date": "2026-01-11"
|
||||
},
|
||||
"template": {
|
||||
"sections": [...]
|
||||
},
|
||||
"rendered": {
|
||||
"sections": [
|
||||
{
|
||||
"type": "hero",
|
||||
"props": {
|
||||
"title": "Article Title", // Placeholder filled
|
||||
"image": "/uploads/image.jpg" // Placeholder filled
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Save Page or Template Structure
|
||||
```
|
||||
POST /wp-json/woonoow/v1/pages/about
|
||||
POST /wp-json/woonoow/v1/templates/post
|
||||
```
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"sections": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"page": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Core Infrastructure (Priority)
|
||||
- [ ] Add bot detection logic to `TemplateOverride.php`
|
||||
- [ ] Build `PageSSR` class to render sections as HTML
|
||||
- [ ] Build `PlaceholderRenderer` class to replace dynamic placeholders
|
||||
- [ ] Create REST API endpoints:
|
||||
- `GET /wp-json/woonoow/v1/pages` (list all)
|
||||
- `GET /wp-json/woonoow/v1/pages/{slug}` (get page)
|
||||
- `GET /wp-json/woonoow/v1/templates/{cpt}` (get template)
|
||||
- `GET /wp-json/woonoow/v1/posts/{id}/with-template` (get post with template)
|
||||
- `POST /wp-json/woonoow/v1/pages/{slug}` (save page)
|
||||
- `POST /wp-json/woonoow/v1/templates/{cpt}` (save template)
|
||||
- [ ] Update template_redirect hook:
|
||||
- Detect if structural page or CPT item
|
||||
- Load appropriate template
|
||||
- Replace placeholders (for CPT items)
|
||||
- Serve SSR for bots (exit without redirect)
|
||||
- Redirect humans to SPA with matching permalink structure
|
||||
- [ ] Create React `DynamicPageRenderer` component
|
||||
- [ ] Test bot crawling vs human browsing
|
||||
|
||||
### Phase 2: Admin SPA Integration
|
||||
- [ ] Add "Pages" submenu to Appearance menu
|
||||
- [ ] Create `/admin/appearance/pages` route
|
||||
- [ ] Build `PageEditor` component with 3-column layout
|
||||
- [ ] Implement sidebar collapse/expand functionality
|
||||
- [ ] Add breadcrumb navigation
|
||||
- [ ] Implement create page modal (structural vs template selection)
|
||||
- [ ] Test navigation flow
|
||||
|
||||
### Phase 3: WooNooW Page Editor (UI)
|
||||
- [ ] Build custom admin page editor UI (React)
|
||||
- [ ] Section list with drag-to-reorder
|
||||
- [ ] Section selector popup when clicking "+ Add Section"
|
||||
- [ ] Layout variant dropdown per section
|
||||
- [ ] Color scheme selector per section
|
||||
- [ ] Form fields for section content:
|
||||
- Static field: text input
|
||||
- Dynamic field: dropdown with available sources
|
||||
- [ ] Live SPA preview iframe (real-time updates)
|
||||
- [ ] Desktop/Mobile preview toggle
|
||||
- [ ] Save/Publish button → stores to postmeta or wp_options
|
||||
- [ ] Visual indicator for dynamic placeholders ([◆])
|
||||
|
||||
### Phase 4: Section Library
|
||||
- [ ] Define section types and their props schema
|
||||
- [ ] Hero section renderer (SSR + React)
|
||||
- [ ] Content section renderer (for post body)
|
||||
- [ ] Feature grid renderer (SSR + React)
|
||||
- [ ] Related items section (for related posts/CPT items)
|
||||
- [ ] CTA banner renderer (SSR + React)
|
||||
- [ ] Add more sections as needed
|
||||
|
||||
### Phase 5: Polish & Launch
|
||||
- [ ] Caching for SSR (cache rendered HTML for 1 hour)
|
||||
- [ ] Unsaved changes warning (ESC key, browser back, menu click)
|
||||
- [ ] Test SEO with Google Search Console
|
||||
- [ ] Test with bot simulators (curl with bot user agents)
|
||||
- [ ] Test dynamic placeholder rendering
|
||||
- [ ] Documentation for merchants
|
||||
- [ ] Documentation for developers (extending sections, custom placeholders)
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Structural Pages (page CPT)
|
||||
```sql
|
||||
wp_posts:
|
||||
├─ id: 42
|
||||
├─ post_type: 'page'
|
||||
├─ post_name: 'about'
|
||||
├─ post_title: 'About Us'
|
||||
├─ post_status: 'publish'
|
||||
└─ post_modified: '2026-01-11 20:58:00'
|
||||
|
||||
wp_postmeta:
|
||||
├─ post_id: 42, meta_key: '_wn_page_structure'
|
||||
│ value: {"type": "page", "sections": [...]}
|
||||
├─ post_id: 42, meta_key: '_yoast_wpseo_title'
|
||||
│ value: "About Us | My Store"
|
||||
├─ post_id: 42, meta_key: '_yoast_wpseo_metadesc'
|
||||
│ value: "Learn about our company..."
|
||||
├─ post_id: 42, meta_key: '_yoast_wpseo_canonical'
|
||||
│ value: "https://mystore.com/about"
|
||||
└─ post_id: 42, meta_key: '_yoast_wpseo_opengraph-image'
|
||||
value: "https://mystore.com/og-image.jpg"
|
||||
```
|
||||
|
||||
### CPT Templates
|
||||
```sql
|
||||
wp_options:
|
||||
├─ option_name: 'wn_template_post'
|
||||
│ option_value: {"type": "template", "cpt": "post", "sections": [...]}
|
||||
├─ option_name: 'wn_template_portfolio'
|
||||
│ option_value: {"type": "template", "cpt": "portfolio", "sections": [...]}
|
||||
└─ option_name: 'wn_template_custom_cpt'
|
||||
option_value: {"type": "template", "cpt": "custom_cpt", "sections": [...]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Target Users & Experience
|
||||
|
||||
### Tech-Savvy Users (Developers/Agencies)
|
||||
**Use Case**: Building custom stores with WooNooW
|
||||
**Tools**: WooNooW CSS variables, class documentation, section schema
|
||||
**Value**: Framework for rapid SPA development, design system enforcement
|
||||
|
||||
### Non-Tech Merchants
|
||||
**Use Case**: Quick store setup with predefined pages and templates
|
||||
**Tools**: WooNooW Page Editor (constrained choices, no freestyle design)
|
||||
**Value**: Professional pages in minutes, consistent styling across all CPTs
|
||||
|
||||
### Both
|
||||
**Result**: Consistent visual identity across structural pages + dynamic CPT items
|
||||
|
||||
---
|
||||
|
||||
## Decision Summary
|
||||
|
||||
| Aspect | Decision |
|
||||
|--------|----------|
|
||||
| **Editor Type** | Single unified editor for pages + CPT templates |
|
||||
| **Structural Pages** | Native `page` CPT, stored in postmeta |
|
||||
| **CPT Templates** | Stored in wp_options (one per CPT) |
|
||||
| **Field Types** | Static (hardcoded) or Dynamic (with placeholders) |
|
||||
| **Available Placeholders** | post_title, post_content, featured_image, author, date, related_posts, etc. |
|
||||
| **Page Builder Integration** | None (custom WooNooW editor only) |
|
||||
| **Admin Location** | Appearance > Pages (single submenu) |
|
||||
| **Editor Context** | Part of admin SPA (not full-screen) |
|
||||
| **Sidebar Behavior** | Collapsible (expanded or icon-only) |
|
||||
| **Create Flow** | Modal asks: Structural page or CPT template? |
|
||||
| **SPA Routing** | Matches WordPress permalink structure |
|
||||
| **Permalink Matching** | `/blog/slug` → `/store/blog/slug` |
|
||||
| **SEO** | Yoast/Rank Math for pages, templates don't interfere |
|
||||
| **Bot Handling** | No redirect, serve pre-rendered HTML with placeholders filled |
|
||||
| **Human Handling** | HTTP 302 redirect to SPA with matching permalink structure |
|
||||
| **SPA Location** | Single centralized page (e.g., `/store/`) |
|
||||
| **Performance** | Cache SSR HTML (1 hour TTL) |
|
||||
| **UI Pattern** | 3-column layout (pages list, editor, settings+preview) |
|
||||
| **Template Types** | Hero, Content, Feature Grid, Related Items, CTA Banner |
|
||||
|
||||
---
|
||||
|
||||
## What's NOT Changing
|
||||
|
||||
✅ Existing shop/product/cart/checkout functionality
|
||||
✅ Existing SPA architecture (single page, centralized)
|
||||
✅ Existing template override logic (extended, not replaced)
|
||||
✅ Existing API patterns (new endpoints follow conventions)
|
||||
✅ Existing CSS variable system (reused for all sections)
|
||||
✅ SEO plugin compatibility (Yoast, Rank Math continue to work)
|
||||
✅ Admin menu structure (Pages added to existing Appearance menu)
|
||||
✅ Admin sidebar (enhanced with collapse functionality, not replaced)
|
||||
|
||||
---
|
||||
|
||||
## Key Architectural Benefits
|
||||
|
||||
✅ **One editor, two modes** - Merchants don't learn separate systems
|
||||
✅ **Clean separation** - Static pages vs dynamic templates clear
|
||||
✅ **URL consistency** - SPA routes match WordPress permalinks
|
||||
✅ **SEO perfect** - Bots see full content, humans get SPA experience
|
||||
✅ **Scalable** - Works for current CPTs and future custom CPTs
|
||||
✅ **Flexible placeholders** - Can add any post field or custom meta
|
||||
✅ **No page builder lock-in** - Custom sections, not Gutenberg/Elementor
|
||||
✅ **Maintains design consistency** - Predefined sections, no freestyle design
|
||||
|
||||
---
|
||||
|
||||
## Next Discussion Topics
|
||||
- [ ] Section library detailed specs (Hero, Content, Features, Related, CTA)
|
||||
- [ ] Placeholder source definitions and extensibility
|
||||
- [ ] Section schema validation
|
||||
- [ ] Caching strategy details (Redis vs file-based vs transient)
|
||||
- [ ] Migration path for existing pages/posts
|
||||
- [ ] Developer documentation (extending sections, custom placeholders)
|
||||
- [ ] Component structure and file organization (React + PHP)
|
||||
Reference in New Issue
Block a user