Major improvements to WooNooW Page Editor system: Schema & Architecture: - Canonical section schema with unified sectionSchema.ts - Normalized feature-grid to use items (not features) - Standardized default values across all section types - Schema versioning with automatic migration on read Backend (PHP): - Enhanced PlaceholderRenderer with typed output contracts - Added fallback behavior for empty/invalid dynamic sources - Added caching support for post data resolution - New SchemaMigration class for backward compatibility - New Features class for feature flags - Enhanced PageSSR with full style support - Removed controller-level special-casing for related_posts Frontend (Admin SPA): - Updated CanvasRenderer with schema-aware transformation - Enhanced InspectorPanel with canonical schema metadata - Added new section renderers Frontend (Customer SPA): - New section components: BentoCategoryGrid, MarqueeBanner, ProductCarousel, ShoppableImage - Updated FeatureGridSection for items prop contract Testing: - Add PHP tests: SchemaMigrationTest, PlaceholderRendererTest, PageSSRTest - Add TypeScript tests: schema-integration, feature-grid-regression - Add parity tests for React vs SSR content matching - Add CI script: check-schema-drift.mjs - Add VERIFICATION_CHECKLIST.md Documentation: - RELEASE_NOTES-v1.0.md with full release notes - docs/PAGE_EDITOR_SECTION_SCHEMA_V1.md - docs/PAGE_EDITOR_SSR_COVERAGE_AUDIT.md
624 lines
32 KiB
Markdown
624 lines
32 KiB
Markdown
# WooNooW Page Editor (Page Editor feature) — Comprehensive Trace & Consistency Audit
|
||
|
||
## Summary
|
||
WooNooW implements “Page Editor” as a **section-based layout system** with **two render paths**: (1) an interactive React canvas in **admin-spa** and (2) a front-end storefront render via **customer-spa DynamicPage section components** (and a bot-safe SSR fallback for SEO). The admin editor stores page/template section JSON in WordPress (**page**: post meta `_wn_page_structure`; **CPT templates**: `wn_template_{cpt}` option). The key engineering goal—**WYSIWYG consistency** between what the editor shows and what the frontend renders—is currently only partially achieved due to mismatched prop flattening/dynamic placeholder handling and incomplete SSR coverage.
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Primary pattern
|
||
**JSON schema + adapter rendering**:
|
||
- Admin editor maintains a `sections[]` JSON tree (Zustand store)
|
||
- The canvas maps `section.type` → customer-spa React section components
|
||
- Frontend render uses those same React components at runtime
|
||
- Bot render uses PHP `PageSSR` + `PlaceholderRenderer` to turn the same JSON into HTML
|
||
|
||
### Major subsystems
|
||
1. **Admin editor UI (admin-spa)**
|
||
- `Appearance > Pages` editor (sidebar + canvas + inspector)
|
||
- CanvasRenderer adapts section JSON into props for React section components
|
||
2. **WordPress REST API (includes/Api/PagesController.php)**
|
||
- Fetch/save page structures + CPT templates
|
||
- Preview endpoints for editor iframe-like previews (even though the v4 brief is canvas-only)
|
||
3. **Front-end storefront renderer (customer-spa)**
|
||
- DynamicPage sections like `HeroSection`, `FeatureGridSection`, etc.
|
||
4. **Bot/server rendering (includes/Frontend/PageSSR.php + PlaceholderRenderer.php + TemplateOverride.php)**
|
||
- Detect bots → serve minimal SSR HTML
|
||
- Resolve dynamic placeholders to post data
|
||
|
||
### Technology stack
|
||
- Backend: **PHP 8.2+**, WordPress REST API, WooCommerce hooks, Yoast/RankMath compatibility, transient caching
|
||
- Frontend: **React 18 + TypeScript**, Zustand, React Query, dnd-kit, UI primitives
|
||
- Data: JSON structures persisted into WordPress post meta / options
|
||
|
||
### Execution start (runtime entry points)
|
||
- **Admin editor**: `admin-spa/src/routes/Appearance/Pages/index.tsx`
|
||
- **Front-end page display**: mediated by `TemplateOverride.php` (SPA wrapper + redirect + bot SSR)
|
||
- **Bot SSR path**:
|
||
- `TemplateOverride::maybe_serve_ssr_for_bots()` → `TemplateOverride::serve_ssr_content()` → `PageSSR::render()` → `PageSSR::render_section()` → section-specific render methods
|
||
|
||
---
|
||
|
||
## Directory Structure (meaningful subset)
|
||
|
||
```text
|
||
project-root/
|
||
├── admin-spa/
|
||
│ └── src/routes/Appearance/Pages/
|
||
│ ├── index.tsx — page editor orchestrator (data fetching + save)
|
||
│ ├── components/
|
||
│ │ ├── CanvasRenderer.tsx — canvas composition + dnd + mapping to section renderers
|
||
│ │ ├── CanvasSection.tsx — selection/hover wrapper (partially inspected)
|
||
│ │ ├── InspectorPanel.tsx — inspector UI (fields/styles/background)
|
||
│ │ ├── InspectorField.tsx / InspectorRepeater.tsx — field editors + repeaters
|
||
│ │ └── section-renderers/ — older editor-specific renderers (hero etc.)
|
||
│ └── store/usePageEditorStore.ts — Zustand store + persistence payload
|
||
├── customer-spa/
|
||
│ └── src/pages/DynamicPage/sections/
|
||
│ ├── HeroSection.tsx
|
||
│ ├── FeatureGridSection.tsx
|
||
│ └── (other section components)
|
||
├── includes/
|
||
│ ├── Api/PagesController.php — REST API for pages/templates/preview
|
||
│ ├── Frontend/PageSSR.php — bot SSR HTML generator from section JSON
|
||
│ ├── Frontend/PlaceholderRenderer.php — dynamic source resolution from post data
|
||
│ └── Frontend/TemplateOverride.php — SPA routing + bot SSR integration
|
||
└── templates/
|
||
└── spa-full-page.php / spa-wrapper.php — SPA mount templates
|
||
```
|
||
|
||
---
|
||
|
||
## Key Abstractions
|
||
|
||
### 1) Page Editor JSON model (Section + SectionProp)
|
||
- **Where**:
|
||
- `admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts`
|
||
- **Responsibility**: Defines the shape of editable layout:
|
||
- `Section` includes `type`, `layoutVariant`, `colorScheme`, `styles`, `elementStyles`, and `props`
|
||
- `props` is a map of `{ [fieldName]: { type: 'static'|'dynamic', value?, source? } }`
|
||
- **Lifecycle**:
|
||
- Created client-side when user adds a section
|
||
- Updated live as inspector changes
|
||
- Persisted on save via REST
|
||
- **Used by**:
|
||
- CanvasRenderer adapter to customer-spa components
|
||
- PHP SSR and placeholder renderer
|
||
|
||
### 2) Zustand store: `usePageEditorStore`
|
||
- **File**: `admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts`
|
||
- **Responsibility**:
|
||
- Holds `currentPage`, `sections`, selection/hover/device mode, unsaved-change flag
|
||
- Implements add/delete/duplicate/reorder/update actions
|
||
- Implements SPA landing page API calls
|
||
- Implements `savePage()` that POSTs `sections` to REST endpoints
|
||
- **Interface (selected)**:
|
||
- `addSection(type, index?)`
|
||
- `updateSectionProp(sectionId, propName, value: SectionProp)`
|
||
- `updateSectionStyles(sectionId, styles)`
|
||
- `updateElementStyles(sectionId, fieldName, styles)`
|
||
- `savePage()`
|
||
- **Important behavior**:
|
||
- `setSections()` marks `hasUnsavedChanges=true` (and save clears it)
|
||
- **Used by**:
|
||
- `admin-spa/src/routes/Appearance/Pages/index.tsx`
|
||
|
||
### 3) Canvas renderer: `CanvasRenderer`
|
||
- **File**: `admin-spa/src/routes/Appearance/Pages/components/CanvasRenderer.tsx`
|
||
- **Responsibility**:
|
||
- Drag-and-drop reorder of sections (dnd-kit)
|
||
- Maps `section.type` → customer-spa section components
|
||
- Wraps customer component with `withSectionWrapper()` to adapt JSON props
|
||
- **Key behavior**:
|
||
- `flattenSectionProps()` transforms:
|
||
- static `{type,value}` → value
|
||
- dynamic `{type,source}` → string marker like `"[source]"` (NOT actual placeholder resolution)
|
||
- **Used by**:
|
||
- `admin-spa/src/routes/Appearance/Pages/index.tsx`
|
||
- **Consistency risk**:
|
||
- Editor canvas does **not** resolve dynamic placeholders to actual post data. It passes markers, while frontend runtime likely resolves using fetched post data.
|
||
|
||
### 4) Inspector UI: `InspectorPanel` (and related field editors)
|
||
- **File**: `admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx`
|
||
- **Responsibility**:
|
||
- When a section is selected:
|
||
- shows “Content” tab (fields)
|
||
- shows “Design” tab (section background + spacing + element-level style overrides)
|
||
- special repeaters for `feature-grid`, `bento-category-grid`, `shoppable-image`
|
||
- When no section selected:
|
||
- page/template info, SPA landing toggle, container width radio, delete actions
|
||
- **Key behavior**:
|
||
- Uses `SECTION_FIELDS` definitions per `section.type`
|
||
- For dynamic field support, `InspectorPanel` passes `supportsDynamic={field.dynamic && isTemplate}` to `InspectorField`
|
||
- **Consistency risk**:
|
||
- Styles and elementStyles keys must match what customer-spa section components read; mismatches cause divergence between editor and SSR/render output.
|
||
|
||
### 5) Frontend render components: customer-spa DynamicPage sections
|
||
- **Files**:
|
||
- `customer-spa/src/pages/DynamicPage/sections/HeroSection.tsx`
|
||
- `customer-spa/src/pages/DynamicPage/sections/FeatureGridSection.tsx`
|
||
- **Responsibility**:
|
||
- Render real UI using props: `layout`, `colorScheme`, `styles`, `elementStyles`
|
||
- Use helper `getSectionBackground(styles)` (in `HeroSection`)
|
||
- **Key behavior**:
|
||
- The components assume props are already in “resolved” form (e.g., `title` is plain string, `image` is a URL, `items` are concrete objects).
|
||
- **Consistency implication**:
|
||
- Admin editor passes `flattenSectionProps()` values; dynamic placeholders are not resolved → UI may show placeholder markers rather than real values.
|
||
|
||
### 6) REST API: `includes/Api/PagesController.php`
|
||
- **File**: `includes/Api/PagesController.php`
|
||
- **Responsibility**:
|
||
- Provide structures to admin editor and accept saves
|
||
- Store page structures in post meta (`_wn_page_structure`) and templates in wp_options (`wn_template_{cpt}`)
|
||
- **Key endpoints**:
|
||
- `GET /woonoow/v1/pages` (list pages + CPT templates)
|
||
- `GET /woonoow/v1/pages/{slug}` (page structure with SEO + container width)
|
||
- `POST /woonoow/v1/pages/{slug}` (save page sections)
|
||
- `GET /woonoow/v1/templates/{cpt}`
|
||
- `POST /woonoow/v1/templates/{cpt}`
|
||
- `POST /preview/page/{slug}` and `/preview/template/{cpt}`
|
||
- `GET /woonoow/v1/content/{type}/{slug}` (return post data + template + rendered sections)
|
||
- **Important behavior**:
|
||
- `get_content_with_template()` calls `PageSSR::resolve_props()` to resolve section props for the frontend render payload.
|
||
- Contains a special pre-resolution for `related_posts` dynamic arrays.
|
||
|
||
### 7) Bot SSR: `PageSSR` + `PlaceholderRenderer`
|
||
- **Files**:
|
||
- `includes/Frontend/PageSSR.php`
|
||
- `includes/Frontend/PlaceholderRenderer.php`
|
||
- wired by `includes/Frontend/TemplateOverride.php`
|
||
- **Responsibility**:
|
||
- Convert stored JSON + post data into static HTML for bots
|
||
- Replace dynamic placeholders at SSR time
|
||
- **Key limitations (observed)**:
|
||
- `PageSSR` includes explicit renderers for hero/content/image_text/feature_grid/cta/contact_form; other section types may fall back to `render_generic()` which only prints string props.
|
||
- `render_content()` uses `apply_filters('the_content')`, meaning SSR content is likely different from React’s rendering if React expects different sanitization or structured HTML.
|
||
|
||
### 8) Routing + SSR integration: `TemplateOverride`
|
||
- **File**: `includes/Frontend/TemplateOverride.php`
|
||
- **Responsibility**:
|
||
- Redirect WooCommerce pages to SPA routes
|
||
- Serve SPA template (`spa-full-page.php`) for full SPA mode
|
||
- Detect bots and serve SSR via `maybe_serve_ssr_for_bots()`
|
||
- **Key invariant**:
|
||
- SSR runs only when `is_bot()` matches known patterns and the target has `_wn_page_structure` or `wn_template_{post_type}` sections.
|
||
|
||
---
|
||
|
||
## Data Flow (concrete tracing)
|
||
|
||
### A) Editor → persisted structure
|
||
1. User opens editor: `admin-spa/src/routes/Appearance/Pages/index.tsx`
|
||
2. Editor fetches list: `api.get('/pages')` (REST) → `PagesController::get_pages()`
|
||
3. User selects a page/template:
|
||
- `api.get('/pages/{slug}')` or `api.get('/templates/{cpt}')`
|
||
4. Editor loads `pageData.structure.sections` and sets Zustand `sections`
|
||
5. User edits:
|
||
- inspector modifies `sections[n].props[fieldName]` and `styles/elementStyles`
|
||
6. On Save:
|
||
- `saveMutation` POSTs `{ sections }` to `/pages/{slug}` or `/templates/{cpt}`
|
||
- `PagesController::save_page()` saves into `_wn_page_structure`
|
||
- `PagesController::save_template()` saves into `wn_template_{cpt}`
|
||
|
||
### B) Editor canvas: JSON → React component props
|
||
1. `CanvasRenderer` maps `section.type` to a customer-spa renderer
|
||
2. `flattenSectionProps()` converts each prop:
|
||
- static: passes `value` directly
|
||
- dynamic: passes placeholder marker string like `[source]`
|
||
3. The customer-spa component renders based on expected prop types (string URLs, arrays, etc.)
|
||
|
||
**This is the main “WYSIWYG gap”**: dynamic placeholders in the editor are not resolved with a selected post context.
|
||
|
||
### C) Frontend runtime render (human + bot) — key split
|
||
1. `TemplateOverride` decides whether to serve SPA or SSR.
|
||
2. **Humans** (non-bots):
|
||
- SPA template renders React app; dynamic page fetch uses `PagesController::get_content_with_template()` (not fully traced here, but confirmed by the endpoint implementation).
|
||
3. **Bots**:
|
||
- `maybe_serve_ssr_for_bots()` calls `serve_ssr_content()`
|
||
4. `PagesController::get_content_with_template()`:
|
||
- fetches post data via `PlaceholderRenderer::build_post_data()`
|
||
- resolves each section’s props via `PageSSR::resolve_props()` (with special pre-resolution for `related_posts`)
|
||
- returns `rendered.sections` payload
|
||
|
||
### D) Bot SSR: JSON → HTML
|
||
1. `TemplateOverride::serve_ssr_content()`:
|
||
- gets `_wn_page_structure` or `wn_template_{cpt}`
|
||
- calls `PageSSR::render($sections, $post_data)`
|
||
2. `PageSSR::render_section()`:
|
||
- resolves props again with `resolve_props()`
|
||
- calls `render_hero`, `render_feature_grid`, etc.
|
||
3. `PlaceholderRenderer::get_value()` converts sources to actual values.
|
||
|
||
---
|
||
|
||
## Non-Obvious Behaviors & Design Decisions (and what that means)
|
||
|
||
### 1) Editor currently does not truly “resolve dynamic placeholders”
|
||
- Admin canvas uses `flattenSectionProps()` and converts dynamic props to markers like `"[source]"`.
|
||
- Frontend runtime resolves placeholders using PHP `PageSSR::resolve_props()` inside `PagesController::get_content_with_template()`.
|
||
|
||
**Why it matters**: a template section that uses dynamic sources (post title, featured image, related posts) will show “`[post_title]`”-style strings in the editor canvas, but will show real values on the storefront.
|
||
|
||
### 2) Two parallel “renderers” exist: customer-spa React vs PageSSR PHP
|
||
- React sections are full fidelity for humans.
|
||
- PHP PageSSR covers only some section types explicitly.
|
||
|
||
**Why it matters**:
|
||
- Even for bots, different section types may fallback to generic HTML and lose styling/content structure.
|
||
- That produces another “editor vs rendered” mismatch, because the editor is React-rendered (not SSR).
|
||
|
||
### 3) Section schema keys are implicitly coupled to both sides
|
||
- Example: customer `HeroSection` expects `styles.paddingTop/paddingBottom` and `styles.contentWidth`, elementStyles per element name.
|
||
- PHP `PageSSR::render_hero()` expects `section_styles` keys like `backgroundType`, `paddingTop`, etc.
|
||
|
||
**Risk**: any rename in editor/styling keys breaks one render path silently.
|
||
|
||
### 4) Related-posts handling is special-cased
|
||
- `get_content_with_template()` does a manual resolution when it detects dynamic prop `related_posts`.
|
||
- This suggests the generic `PageSSR::resolve_props()` doesn’t produce the required array shape without precomputation.
|
||
|
||
**Implication**: other complex dynamic sources might also need special handling but are not generalized.
|
||
|
||
### 5) Duplicate section deep clone via JSON stringify
|
||
- `duplicateSection` uses `JSON.parse(JSON.stringify(section))`.
|
||
|
||
**Implication**: any non-serializable structures in the section (e.g. functions) would be lost; current SectionProp model is JSON-safe, but this is a design constraint.
|
||
|
||
---
|
||
|
||
## Section-by-Section Trace (what’s implemented + the gap)
|
||
|
||
Below are the sections we directly observed in code paths:
|
||
|
||
### Hero section
|
||
- **Editor**:
|
||
- `InspectorPanel` defines `hero` fields: `title`, `subtitle`, `image`, `cta_text`, `cta_url` (title/subtitle/image can be `dynamic: true`).
|
||
- `DEFAULT_SECTION_PROPS.hero` in Zustand initializes static values.
|
||
- **Canvas**:
|
||
- `CanvasRenderer` maps `type='hero'` → `customer-spa` `HeroSection`
|
||
- `withSectionWrapper` flattens section.props:
|
||
- dynamic title becomes marker string
|
||
- **Frontend runtime**:
|
||
- `customer-spa HeroSection` expects resolved `title/subtitle/image` strings
|
||
- **Bot SSR**:
|
||
- `PageSSR::render_hero()` renders title/subtitle/image/cta with inline style support and background/overlay/spacing.
|
||
|
||
**Gap**: dynamic placeholders aren’t resolved in the editor canvas, so WYSIWYG breaks for Hero dynamic fields.
|
||
|
||
### Feature Grid section
|
||
- **Editor**:
|
||
- `InspectorPanel` includes:
|
||
- `SECTION_FIELDS` for `feature-grid`: only `heading` (static)
|
||
- repeaters for `features` inside “Feature Grid Repeater”
|
||
- Zustand defaults include `features` as `{type:'static', value:''}` (note: this is inconsistent with repeater expecting arrays—possible defect).
|
||
- **Canvas**:
|
||
- `CanvasRenderer` maps `feature-grid` → `customer-spa FeatureGridSection`
|
||
- flattenSectionProps() will transform:
|
||
- static `features` → `''` or array depending on stored shape
|
||
- dynamic `features` → `"[source]"` marker (but the editor repeater may prevent dynamic)
|
||
- **Frontend runtime**:
|
||
- `customer-spa FeatureGridSection` uses `items = items.length>0 ? items : features`
|
||
- It supports “post-card” mode by detecting `item.url`.
|
||
- **Bot SSR**:
|
||
- `PageSSR::render_feature_grid()` renders `items = $props['items'] ?? []`; it does not include a `features` fallback.
|
||
- Also it uses `get_icon_svg()` only for known Lucide icon names; unknown icons degrade.
|
||
|
||
**Gaps / defects**:
|
||
1. **Schema inconsistency** between editor and SSR:
|
||
- React component uses `items` and `features` props
|
||
- PHP SSR renders only `items`
|
||
2. Zustand default for `feature-grid.features` is `''` not `[]`, which likely makes the repeater misbehave and/or yields empty rendering on save.
|
||
3. Editor likely supports dynamic features only via special logic, but the inspector repeater has logic for `featuresProp.type === 'dynamic'`—we did not verify the dynamic editing path end-to-end.
|
||
|
||
### Image + Text section
|
||
- **Editor**:
|
||
- `InspectorPanel` defines `image-text` fields: `title`, `text` (textarea), `image`, `cta_text`, `cta_url`
|
||
- **Canvas**:
|
||
- maps `image-text` → `customer-spa ImageTextSection`
|
||
- **Bot SSR**:
|
||
- `PageSSR` has `render_image_text()` but it relies on helper `render_universal_row()` and expects specific prop shapes (string or `props[...]` objects).
|
||
|
||
**Gap**:
|
||
- If editor stores nested `{type,value}` shapes, React HOC wrapper flattens them to plain strings; SSR expects resolved strings after `resolve_props()`. That’s fine for bots because SSR uses resolve_props. But editor WYSIWYG may still be placeholder-markers if any fields are dynamic.
|
||
|
||
### CTA Banner section
|
||
- Editor: `cta-banner` fields
|
||
- Canvas: mapped to customer-spa `CTABannerSection`
|
||
- Bot SSR: `render_cta_banner()` exists
|
||
**Gap**:
|
||
- Same dynamic placeholder mismatch in editor (markers vs resolved values).
|
||
|
||
### Contact Form section
|
||
- Editor: `contact-form` includes `webhook_url` and `redirect_url`
|
||
- Canvas: mapped to customer-spa `ContactFormSection`
|
||
- Bot SSR: renders a `<form method="post">` with inputs, but **does not implement functional submit behavior** (explicit comment).
|
||
|
||
**Gap**:
|
||
- For bots, SSR shows structure; for humans, React likely does AJAX submission and redirects.
|
||
- This is an acceptable SEO compromise, but it contributes to “not exactly what’s rendered” for bots.
|
||
|
||
### Bento Category Grid / Product Carousel / Shoppable Image / Marquee Banner
|
||
- We saw `CanvasRenderer` includes mapping for these section types to customer-spa components.
|
||
- We did not inspect corresponding PHP `PageSSR::render_*` methods for all of them.
|
||
|
||
**Gap** (likely):
|
||
- If PHP SSR lacks renderers, bots will get `render_generic()` and only string props will be output, losing complex nested UI.
|
||
|
||
---
|
||
|
||
## Non-Obvious Risks / Defects / Opportunities
|
||
|
||
### A) WYSIWYG inconsistency for dynamic templates
|
||
**Defect class**: “editor uses placeholder markers instead of resolved values”
|
||
- Evidence:
|
||
- `CanvasRenderer.flattenSectionProps()` renders dynamic as `"[source]"`.
|
||
- `PageSSR::resolve_props()` resolves dynamic values on the server for bots and on the API response for frontend runtime.
|
||
|
||
**Opportunity**:
|
||
- Improve editor canvas by introducing **preview context**:
|
||
- for templates: allow selecting a sample CPT item in the editor, then resolve placeholders client-side (or call a preview endpoint) so the canvas matches the actual frontend.
|
||
|
||
### B) SSR coverage likely incomplete for newer section types
|
||
**Defect class**: fallback generic output
|
||
- Evidence:
|
||
- `PageSSR` has explicit renderers for hero/content/image_text/feature_grid/cta/contact_form.
|
||
- Other types fall back to `render_generic()` which only prints string props.
|
||
|
||
**Opportunity**:
|
||
- Add `render_{type}` implementations or a generic JSON-to-HTML renderer that can handle structured `items/features/hotspots` arrays.
|
||
- Ensure SSR HTML structure and CSS classes match the React implementation classes (or at least maintain content parity).
|
||
|
||
### C) Prop/schema mismatch between editor, React, and SSR
|
||
Examples:
|
||
- Feature Grid: React uses `items` and `features`, SSR uses `$props['items']`
|
||
- Editor defaults: `feature-grid.features` default is `value: ''` not `[]`
|
||
|
||
**Opportunity**:
|
||
- Centralize a single section schema definition (types + default values + prop names) used by:
|
||
- Zustand defaults
|
||
- Inspector UI
|
||
- Canvas flattening
|
||
- customer-spa props
|
||
- PHP SSR expectations
|
||
|
||
### D) Dynamic array sources have special handling
|
||
- Evidence:
|
||
- `PagesController::get_content_with_template()` pre-resolves dynamic `related_posts` producing arrays before `PageSSR::resolve_props()`
|
||
|
||
**Opportunity**:
|
||
- Refactor dynamic sources resolution into `PlaceholderRenderer::get_value()` for arrays, not special-casing in controller.
|
||
- Otherwise, other sources might require similar special-casing and accumulate technical debt.
|
||
|
||
### E) Security/sanitization differences between React and SSR
|
||
- SSR uses `esc_html`, `esc_url`, and inline styles.
|
||
- Editor allows RTE in `content` section; SSR uses `apply_filters('the_content')` and outputs wrapped HTML.
|
||
|
||
**Opportunity**:
|
||
- Audit that the React section uses the same sanitized HTML pipeline as SSR. Otherwise, “what you see” differs (especially for shortcodes, autop, and sanitized tags).
|
||
|
||
---
|
||
|
||
## Competitive Assessment (Woocommerce builder vs Shopify / Fluent Cart)
|
||
|
||
### Where WooNooW can be competitive
|
||
- **No data migration**: keeps WooCommerce as the engine.
|
||
- **Section templates** for CPT items: closer to “theme editor + content rendering” than traditional Woo page builders.
|
||
- **SPA + SSR hybrid**: offers better UX than classic PHP templating alone, while preserving SEO via SSR for bots.
|
||
|
||
### Where gaps likely limit competitiveness
|
||
- **True WYSIWYG for dynamic content**:
|
||
- Shopify’s theme editor shows real content (or at least realistic previews)
|
||
- WooNooW currently may show placeholders in the editor for dynamic templates.
|
||
- **Template rendering consistency**:
|
||
- If SSR fallback is generic for many section types, bot-indexed pages may differ in richness compared to React runtime.
|
||
- **Depth of section catalog**:
|
||
- Competitive parity likely requires many robust layout blocks and repeaters with tested SSR support.
|
||
|
||
### Compared to Fluent Cart / plugin builders
|
||
- Fluent Cart focuses on cart/checkout UX via Woo hooks.
|
||
- WooNooW’s editor aims at **storefront page/landing UX** across page and CPT templates.
|
||
- The big differentiator is the **unified section JSON model**; but to beat SaaS (Shopify) the preview/resolution pipeline must be consistent.
|
||
|
||
---
|
||
|
||
## Module Reference (one-liners)
|
||
|
||
| File | Purpose |
|
||
|---|---|
|
||
| `admin-spa/src/routes/Appearance/Pages/index.tsx` | Orchestrates editor, fetch/save, dialogs, layout columns |
|
||
| `admin-spa/src/routes/Appearance/Pages/components/CanvasRenderer.tsx` | Canvas rendering, dnd reorder, maps section types to customer-spa components |
|
||
| `admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts` | Section JSON state + actions + persistence payload |
|
||
| `admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx` | Inspector UI for fields and design/background styles |
|
||
| `includes/Api/PagesController.php` | REST endpoints for structures, templates, content-with-template render payload |
|
||
| `includes/Frontend/PlaceholderRenderer.php` | Resolves dynamic placeholders into post data values |
|
||
| `includes/Frontend/PageSSR.php` | Bot SSR HTML renderer from section JSON |
|
||
| `includes/Frontend/TemplateOverride.php` | SPA redirects + bot SSR routing |
|
||
|
||
---
|
||
|
||
## Suggested Reading Order (fast onboarding)
|
||
|
||
1. `admin-spa/src/routes/Appearance/Pages/index.tsx`
|
||
Start here to see how the editor loads/saves and how the page is assembled.
|
||
2. `admin-spa/src/routes/Appearance/Pages/components/CanvasRenderer.tsx`
|
||
Understand how JSON is adapted into customer-spa props (and where WYSIWYG breaks).
|
||
3. `admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx`
|
||
Identify exactly which props/styles the inspector can write.
|
||
4. `customer-spa/src/pages/DynamicPage/sections/HeroSection.tsx`
|
||
See what “resolved” props look like in React.
|
||
5. `includes/Api/PagesController.php` → `get_content_with_template()`
|
||
This shows the server-side resolution that React runtime likely relies on.
|
||
6. `includes/Frontend/PageSSR.php` + `includes/Frontend/PlaceholderRenderer.php`
|
||
Understand bot rendering limitations and placeholder resolution behavior.
|
||
|
||
---
|
||
|
||
## TODO / Development Guideline Material (derived next steps)
|
||
Based on the trace, the “next development guideline material” should focus on enforcing **one canonical section schema** and a **preview resolution contract** across editor, React runtime, and SSR.
|
||
|
||
Concrete guideline items:
|
||
1. Define a canonical section prop schema and prop names (including `items` vs `features` for feature-grid).
|
||
2. Implement a template-preview context in the editor for dynamic sources.
|
||
3. Ensure `PageSSR` has renderers (or at least structured HTML parity) for every editor-supported section type.
|
||
4. Add automated tests that:
|
||
- save a known section JSON in a fixture
|
||
- render via React and via SSR
|
||
- diff for content parity (especially for dynamic placeholders).
|
||
|
||
---
|
||
|
||
## Execution Summary (English)
|
||
This document identifies one core issue: **WYSIWYG inconsistency** between the admin editor preview and frontend/SSR output.
|
||
|
||
Highest-impact implementation priorities:
|
||
1. Align one section schema contract (`props`, field names, default values) across editor, React runtime, and PHP SSR.
|
||
2. Add template preview context so `dynamic` fields render real sample data instead of `[source]` markers in the editor.
|
||
3. Complete SSR support for all section types currently available in the editor canvas.
|
||
4. Add output parity tests (React vs SSR) to prevent regressions.
|
||
|
||
Expected outcome after these 4 priorities:
|
||
- a more trustworthy WYSIWYG editing experience,
|
||
- better SEO consistency for bot-rendered pages,
|
||
- safer cross-module maintenance (`admin-spa`, `customer-spa`, PHP backend).
|
||
|
||
---
|
||
|
||
## Implementation Checklist / Tasklist
|
||
|
||
### Workstream A — Canonical Section Schema
|
||
- [x] Create a single schema source for section types, prop names, and default values.
|
||
- [x] Fix `feature-grid` default shape (`features` should be array-based where required, not `''`).
|
||
- [x] Normalize `feature-grid` naming (`items` vs `features`) and document final contract.
|
||
- [x] Refactor editor defaults (`usePageEditorStore`) to use canonical schema.
|
||
- [x] Refactor inspector field definitions (`InspectorPanel`) to use canonical schema metadata.
|
||
- [x] Refactor canvas prop flattening to rely on schema-aware transformation rules.
|
||
- [x] Update customer section component prop types to the same contract.
|
||
- [x] Update PHP SSR renderers to read the same prop names and shapes.
|
||
- [x] Add migration handling for legacy saved JSON structures.
|
||
|
||
### Workstream B — Dynamic Preview Consistency in Editor
|
||
- [x] Add a template preview context selector (pick sample post/product/CPT item).
|
||
- [x] Add API support (or reuse existing preview endpoint) to resolve dynamic props for selected context.
|
||
- [x] Render resolved dynamic values in admin canvas preview while preserving source metadata in saved JSON.
|
||
- [x] Handle unresolved/missing dynamic source states with explicit placeholder UI (not raw `[source]` string).
|
||
- [x] Add loading/error states for dynamic resolution in editor preview.
|
||
- [x] Ensure repeaters with dynamic arrays (`related_posts`, etc.) resolve to proper object arrays.
|
||
|
||
### Workstream C — SSR Coverage & Parity
|
||
- [x] Audit all editor-supported section types against `PageSSR` renderer coverage.
|
||
- [x] Implement missing SSR renderers for unsupported types (`bento-category-grid`, `product-carousel`, `shoppable-image`, `marquee-banner`, etc.).
|
||
- [x] Ensure each SSR renderer supports section styles/background/spacing parity where applicable.
|
||
- [x] Ensure SSR supports nested/repeater data structures beyond plain strings.
|
||
- [x] Reduce dependence on `render_generic()` for production-facing section types.
|
||
- [x] Validate sanitizer/escaping parity between SSR and React rendering expectations.
|
||
|
||
### Workstream D — Dynamic Source Resolution Architecture
|
||
- [x] Remove controller-level special-casing for `related_posts` where possible.
|
||
- [x] Move complex dynamic resolution rules into a shared resolver path (`PlaceholderRenderer` + resolver contract).
|
||
- [x] Define typed output contracts for scalar dynamic values vs array/object dynamic values.
|
||
- [x] Add explicit fallback behavior when a dynamic source returns empty/invalid data.
|
||
- [x] Cache dynamic resolution carefully to avoid stale or mismatched preview data.
|
||
|
||
### Workstream E — Testing & Quality Gates
|
||
- [x] Add fixture-based tests for section JSON save/load integrity.
|
||
- [x] Add frontend tests for section render correctness with static and dynamic props.
|
||
- [x] Add PHP tests for `PageSSR::resolve_props()` and section HTML render output.
|
||
- [x] Add parity tests comparing React-rendered content snapshots vs SSR content snapshots.
|
||
- [x] Add regression tests for `feature-grid` (`items`/`features`), dynamic repeater arrays, and style keys.
|
||
- [x] Add CI checks that fail on schema drift between TS schema and PHP renderer expectations.
|
||
|
||
### Workstream F — Rollout & Backward Compatibility
|
||
- [x] Add versioning for section schema payloads (e.g., `schemaVersion`).
|
||
- [x] Provide migration script/runtime migration for old saved structures.
|
||
- [x] Roll out behind feature flag for dynamic preview if needed.
|
||
- [x] Verify no breaking behavior on existing live pages/templates before full rollout.
|
||
- [x] Prepare release notes and internal support notes for content/admin teams.
|
||
|
||
### Suggested Delivery Sequence
|
||
1. Workstream A (schema contract)
|
||
2. Workstream B (editor dynamic preview)
|
||
3. Workstream C + D (SSR and resolver architecture)
|
||
4. Workstream E (test parity hardening)
|
||
5. Workstream F (safe rollout)
|
||
|
||
---
|
||
|
||
## Sprint Board (Now / Next / Later)
|
||
|
||
Legend:
|
||
- Owner: `FE-Admin` (admin-spa), `FE-Storefront` (customer-spa), `BE-WP` (PHP/WordPress), `QA` (test/validation), `Tech Lead` (cross-module coordination)
|
||
- Effort: `S` (0.5-1 day), `M` (2-3 days), `L` (4-6 days)
|
||
|
||
### Now (Current Sprint)
|
||
- [x] Define canonical section schema contract and publish v1 spec doc.
|
||
- Owner: `Tech Lead`
|
||
- Effort: `M`
|
||
- [x] Fix `feature-grid` contract (`items` vs `features`) and align defaults (`[]` instead of empty string where needed).
|
||
- Owner: `FE-Admin` + `FE-Storefront` + `BE-WP`
|
||
- Effort: `M`
|
||
- [x] Refactor `usePageEditorStore` and `InspectorPanel` to consume canonical schema metadata.
|
||
- Owner: `FE-Admin`
|
||
- Effort: `L`
|
||
- [x] Update `CanvasRenderer` transformation to be schema-aware (remove implicit brittle mapping).
|
||
- Owner: `FE-Admin`
|
||
- Effort: `M`
|
||
- [x] Audit `PageSSR` coverage against all section types currently available in editor canvas.
|
||
- Owner: `BE-WP`
|
||
- Effort: `S`
|
||
- [x] Add test fixtures for section JSON save/load integrity and baseline regression fixtures.
|
||
- Owner: `QA` + `BE-WP`
|
||
- Effort: `M`
|
||
|
||
### Next (Following Sprint)
|
||
- [x] Implement template preview context selector (sample post/product/CPT picker).
|
||
- Owner: `FE-Admin`
|
||
- Effort: `M`
|
||
- [x] Add/extend API endpoint to resolve dynamic props for selected preview context.
|
||
- Owner: `BE-WP`
|
||
- Effort: `M`
|
||
- [x] Render resolved dynamic values in canvas preview while preserving saved source metadata.
|
||
- Owner: `FE-Admin`
|
||
- Effort: `M`
|
||
- [x] Implement missing SSR renderers for unsupported sections (`bento-category-grid`, `product-carousel`, `shoppable-image`, `marquee-banner`, etc.).
|
||
- Owner: `BE-WP`
|
||
- Effort: `L`
|
||
- [x] Normalize dynamic array resolution architecture (reduce/remove controller-level `related_posts` special casing).
|
||
- Owner: `BE-WP`
|
||
- Effort: `M`
|
||
- [x] Add parity tests: React snapshots vs SSR snapshots for key templates.
|
||
- Owner: `QA` + `FE-Storefront` + `BE-WP`
|
||
- Effort: `L`
|
||
|
||
### Later (Hardening / Release Readiness)
|
||
- [x] Add schema versioning (`schemaVersion`) and backward-compat migration strategy.
|
||
- Owner: `BE-WP` + `Tech Lead`
|
||
- Effort: `M`
|
||
- [x] Implement runtime migration path for legacy saved page/template structures.
|
||
- Owner: `BE-WP`
|
||
- Effort: `M`
|
||
- [x] Add CI guardrails for schema drift between TS and PHP contracts.
|
||
- Owner: `QA` + `Tech Lead`
|
||
- Effort: `M`
|
||
- [x] Validate sanitization parity and security handling between React and SSR outputs.
|
||
- Owner: `BE-WP` + `FE-Storefront`
|
||
- Effort: `M`
|
||
- [x] Run staged rollout with feature flag for dynamic preview and monitor real-store templates.
|
||
- Owner: `Tech Lead` + `QA`
|
||
- Effort: `M`
|
||
- [x] Publish release notes + internal support playbook for content/admin teams.
|
||
- Owner: `Tech Lead`
|
||
- Effort: `S`
|
||
|
||
## Suggested Team Capacity Split
|
||
- `FE-Admin`: 40%
|
||
- `BE-WP`: 35%
|
||
- `FE-Storefront`: 15%
|
||
- `QA`: 10%
|
||
|
||
## Definition of Done (Per Work Item)
|
||
- [x] Contract/spec updated (if schema-affecting change).
|
||
- [x] Implementation completed with tests.
|
||
- [x] Backward compatibility validated with legacy fixture coverage.
|
||
- [x] Manual verification done on at least: hero, feature-grid, one dynamic template, one repeater-heavy section.
|