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
- Add POST /preview/page/{slug} and /preview/template/{cpt} endpoints
- Render full HTML using PageSSR for iframe preview
- Templates use sample post for dynamic placeholder resolution
- PageSettings iframe with debounced section updates (500ms)
- Desktop/Mobile toggle with scaled iframe view
- Show/Hide preview toggle button
- Refresh button for manual preview reload
- Preview indicator banner in iframe
- Add horizontal padding to dialog content (px-1)
- Show real site URL from WNW_CONFIG.siteUrl instead of 'yoursite.com'
- Improve error handling to extract message from response.data.message
- Better slug generation regex (removes special chars properly)
- Reset form when modal closes
- Use theme colors (text-muted-foreground, hover:bg-accent/50)
- Implement serve_ssr_content with full PageSSR rendering
- SEO meta tags (title, description, og:*)
- Minimal CSS for bot-friendly presentation
- Yoast/Rank Math SEO data integration
- Add maybe_serve_ssr_for_bots hook (priority 2 on template_redirect)
- Serves SSR for structural pages with WooNooW structure
- Serves SSR for CPT items with templates
- Add use statements for PageSSR and PlaceholderRenderer
- Add Pages link to Appearance submenu in NavigationRegistry
- Bump NAV_VERSION to 1.1.0
- Add DynamicPageRenderer component for structural pages and CPT content
- Add 6 section components:
- HeroSection with multiple layout variants
- ContentSection for rich text/HTML content
- ImageTextSection with image-left/right layouts
- FeatureGridSection with grid-2/3/4 layouts
- CTABannerSection with color schemes
- ContactFormSection with webhook POST and redirect
- Add dynamic routes to App.tsx for /:slug and /:pathBase/:slug
- Build customer-spa successfully
- Add is_bot() detection in TemplateOverride.php (30+ bot patterns)
- Add PageSSR.php for server-side rendering of page sections
- Add PlaceholderRenderer.php for dynamic content resolution
- Add PagesController.php REST API for pages/templates CRUD
- Register PagesController routes in Routes.php
API Endpoints:
- GET /pages - list all pages/templates
- GET /pages/{slug} - get page structure
- POST /pages/{slug} - save page
- GET /templates/{cpt} - get CPT template
- POST /templates/{cpt} - save template
- GET /content/{type}/{slug} - get content with template applied
When SPA is frontpage, basePath is empty string. JavaScript || treats '' as falsy
and falls back to /store. Changed to ?? (nullish coalescing) so empty string works.
- Removed all debug logging (backend and frontend)
- Added filter hook 'woonoow_standard_checkout_field_keys' for extensibility
- Added form-row-wide class support to admin OrderForm
- Tax is automatically handled by WC's calculate_totals()
ROOT CAUSE: The sanitize_payload() method was returning a whitelist of
allowed fields, but shipping_cost, shipping_title, custom_fields, and
customer_note were NOT included. This caused these values to be null
even though the frontend was sending them correctly.
Added:
- shipping_cost (float)
- shipping_title (sanitized text)
- custom_fields (array)
- customer_note (sanitized textarea)
This should fix shipping not being applied to order totals.
- Added isFullWidthField helper to check for form-row-wide in class array
- Added getFieldWrapperClass helper to return md:col-span-2 for full width
- Applied dynamic width to billing_first_name and billing_last_name
- PHP can now control field width via class: ['form-row-wide']
- Added debug logging for shipping to help diagnose shipping not applied issue
Shipping Fix:
- Frontend now sends shipping_cost and shipping_title in order payload
- Backend uses these values as fallback when WC zone-based rate lookup fails
- Fixes issue where Rajaongkir and other API-based shipping wasn't applied
Dynamic Field Rendering:
- Added billingFields/shippingFields filters sorted by priority
- Added getBillingField/getShippingField helpers that return undefined for hidden fields
- All standard fields now conditionally rendered based on API response
- Fields use labels and required flags from API
- Any field can be hidden via PHP snippet (type: 'hidden' or hidden: true)
- Removed unused isFieldHidden function
1. ThankYou page - Added discount row to order summary
- All 3 template variations (receipt-inner, receipt-outer, default)
- Shows discount with negative amount in green when > 0
- Already had shipping/tax rows, now complete breakdown
2. Checkout - Enhanced isFieldHidden helper
- Now checks both type='hidden' AND hidden=true flags
- Any standard field can be hidden via PHP snippet
- Existing city/state/postcode/country checks unchanged
1. Hidden fields now properly hidden in SPA
- Added billing_postcode and shipping_postcode to isFieldHidden checks
- Fields with type='hidden' from PHP now conditionally rendered
2. Coupons now applied to order total
- Added coupons array to order submission payload
- CartController now calls calculate_totals() before reading discounts
- Returns per-coupon discount amounts {code, discount, type}
3. Shipping now applied to order total
- Already handled in submit() via find_shipping_rate_for_order
- Frontend now sends shipping_method in payload
4. Order details now include shipping/tracking info
- checkout/order/{id} API includes shipping_lines, tracking_number, tracking_url
- account/orders/{id} API includes same shipping/tracking fields
- Tracking info read from multiple plugin meta keys
5. Thank you/OrderDetails page shows shipping method and AWB
- Shipping Method section with courier name and cost
- AWB tracking for processing/completed orders with Track Shipment button
1. Hidden fields now respected in SPA
- Added isFieldHidden helper to check if PHP sets type to 'hidden'
- Country/state/city fields conditionally rendered based on API response
- Set default country value for hidden country fields (Indonesia-only stores)
2. Coupon discount now shows correct amount
- Added calculate_totals() before reading discount
- Changed coupons response to include {code, discount, type} per coupon
- Added discount_total at root level for frontend compatibility
3. Order details page now shows shipping info and AWB tracking
- Added shipping_lines, tracking_number, tracking_url to Order interface
- Added Shipping Method section with courier name and cost
- Added AWB tracking section for processing/completed orders
- Track Shipment button with link to tracking URL
RAJAONGKIR_INTEGRATION.md:
- Hide country/state/city when Indonesia is the only allowed country
- Make destination_id required for Indonesia-only stores
- Force country to ID in session bridge
- Added billing_destination_id fallback
SHIPPING_BRIDGE_PATTERN.md:
- New generic template for shipping provider integrations
- Documents architecture, hooks, and field types
- Provides copy-paste template for new providers
- Includes checklist for new integrations
Backend:
- Added /checkout/shipping-rates REST endpoint
- Returns available shipping methods from matching zone
- Triggers woonoow/shipping/before_calculate hook for Rajaongkir
Frontend:
- Added ShippingRate interface and state
- Added fetchShippingRates with 500ms debounce
- Replaced hardcoded shipping options with dynamic rates
- Added loading and empty state handling
- Added shipping_method to order submission payload
This fixes:
- Rajaongkir rates not appearing (now fetched from API)
- Free shipping showing despite disabled (now from WC zones)
- search_endpoint changed from '/woonoow/v1/rajaongkir/destinations'
to '/rajaongkir/destinations' (relative to API base)
- min_chars increased from 2 to 3 to reduce early API hits