Files
WooNooW/project_info__1.md
Dwindi Ramadhana 396ca25be4 feat: Page Editor v1.0 - canonical schema, SSR parity, and migration
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
2026-05-30 13:02:08 +07:00

624 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 Reacts 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 sections 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()` doesnt 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 (whats 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 arent 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()`. Thats 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 whats 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**:
- Shopifys 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.
- WooNooWs 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.