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
32 KiB
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+PlaceholderRendererto turn the same JSON into HTML
Major subsystems
- Admin editor UI (admin-spa)
Appearance > Pageseditor (sidebar + canvas + inspector)- CanvasRenderer adapts section JSON into props for React section components
- 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)
- Front-end storefront renderer (customer-spa)
- DynamicPage sections like
HeroSection,FeatureGridSection, etc.
- DynamicPage sections like
- 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)
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:
Sectionincludestype,layoutVariant,colorScheme,styles,elementStyles, andpropspropsis 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 POSTssectionsto REST endpoints
- Holds
- Interface (selected):
addSection(type, index?)updateSectionProp(sectionId, propName, value: SectionProp)updateSectionStyles(sectionId, styles)updateElementStyles(sectionId, fieldName, styles)savePage()
- Important behavior:
setSections()markshasUnsavedChanges=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)
- static
- 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
- When a section is selected:
- Key behavior:
- Uses
SECTION_FIELDSdefinitions persection.type - For dynamic field support,
InspectorPanelpassessupportsDynamic={field.dynamic && isTemplate}toInspectorField
- Uses
- 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.tsxcustomer-spa/src/pages/DynamicPage/sections/FeatureGridSection.tsx
- Responsibility:
- Render real UI using props:
layout,colorScheme,styles,elementStyles - Use helper
getSectionBackground(styles)(inHeroSection)
- Render real UI using props:
- Key behavior:
- The components assume props are already in “resolved” form (e.g.,
titleis plain string,imageis a URL,itemsare concrete objects).
- The components assume props are already in “resolved” form (e.g.,
- Consistency implication:
- Admin editor passes
flattenSectionProps()values; dynamic placeholders are not resolved → UI may show placeholder markers rather than real values.
- Admin editor passes
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()callsPageSSR::resolve_props()to resolve section props for the frontend render payload.- Contains a special pre-resolution for
related_postsdynamic arrays.
7) Bot SSR: PageSSR + PlaceholderRenderer
- Files:
includes/Frontend/PageSSR.phpincludes/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):
PageSSRincludes explicit renderers for hero/content/image_text/feature_grid/cta/contact_form; other section types may fall back torender_generic()which only prints string props.render_content()usesapply_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_structureorwn_template_{post_type}sections.
- SSR runs only when
Data Flow (concrete tracing)
A) Editor → persisted structure
- User opens editor:
admin-spa/src/routes/Appearance/Pages/index.tsx - Editor fetches list:
api.get('/pages')(REST) →PagesController::get_pages() - User selects a page/template:
api.get('/pages/{slug}')orapi.get('/templates/{cpt}')
- Editor loads
pageData.structure.sectionsand sets Zustandsections - User edits:
- inspector modifies
sections[n].props[fieldName]andstyles/elementStyles
- inspector modifies
- On Save:
saveMutationPOSTs{ sections }to/pages/{slug}or/templates/{cpt}PagesController::save_page()saves into_wn_page_structurePagesController::save_template()saves intown_template_{cpt}
B) Editor canvas: JSON → React component props
CanvasRenderermapssection.typeto a customer-spa rendererflattenSectionProps()converts each prop:- static: passes
valuedirectly - dynamic: passes placeholder marker string like
[source]
- static: passes
- 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
TemplateOverridedecides whether to serve SPA or SSR.- 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).
- SPA template renders React app; dynamic page fetch uses
- Bots:
maybe_serve_ssr_for_bots()callsserve_ssr_content()
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 forrelated_posts) - returns
rendered.sectionspayload
- fetches post data via
D) Bot SSR: JSON → HTML
TemplateOverride::serve_ssr_content():- gets
_wn_page_structureorwn_template_{cpt} - calls
PageSSR::render($sections, $post_data)
- gets
PageSSR::render_section():- resolves props again with
resolve_props() - calls
render_hero,render_feature_grid, etc.
- resolves props again with
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()insidePagesController::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
HeroSectionexpectsstyles.paddingTop/paddingBottomandstyles.contentWidth, elementStyles per element name. - PHP
PageSSR::render_hero()expectssection_styleskeys likebackgroundType,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 proprelated_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
duplicateSectionusesJSON.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:
InspectorPaneldefinesherofields:title,subtitle,image,cta_text,cta_url(title/subtitle/image can bedynamic: true).DEFAULT_SECTION_PROPS.heroin Zustand initializes static values.
- Canvas:
CanvasRenderermapstype='hero'→customer-spaHeroSectionwithSectionWrapperflattens section.props:- dynamic title becomes marker string
- Frontend runtime:
customer-spa HeroSectionexpects resolvedtitle/subtitle/imagestrings
- 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:
InspectorPanelincludes:SECTION_FIELDSforfeature-grid: onlyheading(static)- repeaters for
featuresinside “Feature Grid Repeater”
- Zustand defaults include
featuresas{type:'static', value:''}(note: this is inconsistent with repeater expecting arrays—possible defect).
- Canvas:
CanvasRenderermapsfeature-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)
- static
- Frontend runtime:
customer-spa FeatureGridSectionusesitems = items.length>0 ? items : features- It supports “post-card” mode by detecting
item.url.
- Bot SSR:
PageSSR::render_feature_grid()rendersitems = $props['items'] ?? []; it does not include afeaturesfallback.- Also it uses
get_icon_svg()only for known Lucide icon names; unknown icons degrade.
Gaps / defects:
- Schema inconsistency between editor and SSR:
- React component uses
itemsandfeaturesprops - PHP SSR renders only
items
- React component uses
- Zustand default for
feature-grid.featuresis''not[], which likely makes the repeater misbehave and/or yields empty rendering on save. - 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:
InspectorPaneldefinesimage-textfields:title,text(textarea),image,cta_text,cta_url
- Canvas:
- maps
image-text→customer-spa ImageTextSection
- maps
- Bot SSR:
PageSSRhasrender_image_text()but it relies on helperrender_universal_row()and expects specific prop shapes (string orprops[...]objects).
Gap:
- If editor stores nested
{type,value}shapes, React HOC wrapper flattens them to plain strings; SSR expects resolved strings afterresolve_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-bannerfields - 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-formincludeswebhook_urlandredirect_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
CanvasRendererincludes 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:
PageSSRhas 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 structureditems/features/hotspotsarrays. - 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
itemsandfeatures, SSR uses$props['items'] - Editor defaults:
feature-grid.featuresdefault isvalue: ''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 dynamicrelated_postsproducing arrays beforePageSSR::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
contentsection; SSR usesapply_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)
admin-spa/src/routes/Appearance/Pages/index.tsx
Start here to see how the editor loads/saves and how the page is assembled.admin-spa/src/routes/Appearance/Pages/components/CanvasRenderer.tsx
Understand how JSON is adapted into customer-spa props (and where WYSIWYG breaks).admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx
Identify exactly which props/styles the inspector can write.customer-spa/src/pages/DynamicPage/sections/HeroSection.tsx
See what “resolved” props look like in React.includes/Api/PagesController.php→get_content_with_template()
This shows the server-side resolution that React runtime likely relies on.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:
- Define a canonical section prop schema and prop names (including
itemsvsfeaturesfor feature-grid). - Implement a template-preview context in the editor for dynamic sources.
- Ensure
PageSSRhas renderers (or at least structured HTML parity) for every editor-supported section type. - 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:
- Align one section schema contract (
props, field names, default values) across editor, React runtime, and PHP SSR. - Add template preview context so
dynamicfields render real sample data instead of[source]markers in the editor. - Complete SSR support for all section types currently available in the editor canvas.
- 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
- Create a single schema source for section types, prop names, and default values.
- Fix
feature-griddefault shape (featuresshould be array-based where required, not''). - Normalize
feature-gridnaming (itemsvsfeatures) and document final contract. - Refactor editor defaults (
usePageEditorStore) to use canonical schema. - Refactor inspector field definitions (
InspectorPanel) to use canonical schema metadata. - Refactor canvas prop flattening to rely on schema-aware transformation rules.
- Update customer section component prop types to the same contract.
- Update PHP SSR renderers to read the same prop names and shapes.
- Add migration handling for legacy saved JSON structures.
Workstream B — Dynamic Preview Consistency in Editor
- Add a template preview context selector (pick sample post/product/CPT item).
- Add API support (or reuse existing preview endpoint) to resolve dynamic props for selected context.
- Render resolved dynamic values in admin canvas preview while preserving source metadata in saved JSON.
- Handle unresolved/missing dynamic source states with explicit placeholder UI (not raw
[source]string). - Add loading/error states for dynamic resolution in editor preview.
- Ensure repeaters with dynamic arrays (
related_posts, etc.) resolve to proper object arrays.
Workstream C — SSR Coverage & Parity
- Audit all editor-supported section types against
PageSSRrenderer coverage. - Implement missing SSR renderers for unsupported types (
bento-category-grid,product-carousel,shoppable-image,marquee-banner, etc.). - Ensure each SSR renderer supports section styles/background/spacing parity where applicable.
- Ensure SSR supports nested/repeater data structures beyond plain strings.
- Reduce dependence on
render_generic()for production-facing section types. - Validate sanitizer/escaping parity between SSR and React rendering expectations.
Workstream D — Dynamic Source Resolution Architecture
- Remove controller-level special-casing for
related_postswhere possible. - Move complex dynamic resolution rules into a shared resolver path (
PlaceholderRenderer+ resolver contract). - Define typed output contracts for scalar dynamic values vs array/object dynamic values.
- Add explicit fallback behavior when a dynamic source returns empty/invalid data.
- Cache dynamic resolution carefully to avoid stale or mismatched preview data.
Workstream E — Testing & Quality Gates
- Add fixture-based tests for section JSON save/load integrity.
- Add frontend tests for section render correctness with static and dynamic props.
- Add PHP tests for
PageSSR::resolve_props()and section HTML render output. - Add parity tests comparing React-rendered content snapshots vs SSR content snapshots.
- Add regression tests for
feature-grid(items/features), dynamic repeater arrays, and style keys. - Add CI checks that fail on schema drift between TS schema and PHP renderer expectations.
Workstream F — Rollout & Backward Compatibility
- Add versioning for section schema payloads (e.g.,
schemaVersion). - Provide migration script/runtime migration for old saved structures.
- Roll out behind feature flag for dynamic preview if needed.
- Verify no breaking behavior on existing live pages/templates before full rollout.
- Prepare release notes and internal support notes for content/admin teams.
Suggested Delivery Sequence
- Workstream A (schema contract)
- Workstream B (editor dynamic preview)
- Workstream C + D (SSR and resolver architecture)
- Workstream E (test parity hardening)
- 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)
- Define canonical section schema contract and publish v1 spec doc.
- Owner:
Tech Lead - Effort:
M
- Owner:
- Fix
feature-gridcontract (itemsvsfeatures) and align defaults ([]instead of empty string where needed).- Owner:
FE-Admin+FE-Storefront+BE-WP - Effort:
M
- Owner:
- Refactor
usePageEditorStoreandInspectorPanelto consume canonical schema metadata.- Owner:
FE-Admin - Effort:
L
- Owner:
- Update
CanvasRenderertransformation to be schema-aware (remove implicit brittle mapping).- Owner:
FE-Admin - Effort:
M
- Owner:
- Audit
PageSSRcoverage against all section types currently available in editor canvas.- Owner:
BE-WP - Effort:
S
- Owner:
- Add test fixtures for section JSON save/load integrity and baseline regression fixtures.
- Owner:
QA+BE-WP - Effort:
M
- Owner:
Next (Following Sprint)
- Implement template preview context selector (sample post/product/CPT picker).
- Owner:
FE-Admin - Effort:
M
- Owner:
- Add/extend API endpoint to resolve dynamic props for selected preview context.
- Owner:
BE-WP - Effort:
M
- Owner:
- Render resolved dynamic values in canvas preview while preserving saved source metadata.
- Owner:
FE-Admin - Effort:
M
- Owner:
- Implement missing SSR renderers for unsupported sections (
bento-category-grid,product-carousel,shoppable-image,marquee-banner, etc.).- Owner:
BE-WP - Effort:
L
- Owner:
- Normalize dynamic array resolution architecture (reduce/remove controller-level
related_postsspecial casing).- Owner:
BE-WP - Effort:
M
- Owner:
- Add parity tests: React snapshots vs SSR snapshots for key templates.
- Owner:
QA+FE-Storefront+BE-WP - Effort:
L
- Owner:
Later (Hardening / Release Readiness)
- Add schema versioning (
schemaVersion) and backward-compat migration strategy.- Owner:
BE-WP+Tech Lead - Effort:
M
- Owner:
- Implement runtime migration path for legacy saved page/template structures.
- Owner:
BE-WP - Effort:
M
- Owner:
- Add CI guardrails for schema drift between TS and PHP contracts.
- Owner:
QA+Tech Lead - Effort:
M
- Owner:
- Validate sanitization parity and security handling between React and SSR outputs.
- Owner:
BE-WP+FE-Storefront - Effort:
M
- Owner:
- Run staged rollout with feature flag for dynamic preview and monitor real-store templates.
- Owner:
Tech Lead+QA - Effort:
M
- Owner:
- Publish release notes + internal support playbook for content/admin teams.
- Owner:
Tech Lead - Effort:
S
- Owner:
Suggested Team Capacity Split
FE-Admin: 40%BE-WP: 35%FE-Storefront: 15%QA: 10%
Definition of Done (Per Work Item)
- Contract/spec updated (if schema-affecting change).
- Implementation completed with tests.
- Backward compatibility validated with legacy fixture coverage.
- Manual verification done on at least: hero, feature-grid, one dynamic template, one repeater-heavy section.