# 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:

Article Title

Post content...

↓ ✓ 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: }, { path: '/shop', element: }, { path: '/product/:slug', element: }, // NEW: Dynamic routes (lower priority) { path: '/:pathBase/:slug', element: }, { path: '/:slug', element: }, ]); // 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 ; } ``` ### 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

Article Title

Article featured image

Article body content goes here...

``` - ✅ 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)