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
This commit is contained in:
Dwindi Ramadhana
2026-05-30 13:02:08 +07:00
parent e70aa1f554
commit 396ca25be4
118 changed files with 10162 additions and 3726 deletions

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { PanelLeft, PanelLeftClose, Package } from 'lucide-react';
import { useActiveSection } from '@/hooks/useActiveSection';
import { __ } from '@/lib/i18n';
import { iconMap } from '@/lib/nav-icons';
interface SidebarProps {
collapsed: boolean;
onToggle: () => void;
}
export function Sidebar({ collapsed, onToggle }: SidebarProps) {
const link = "flex items-center gap-2 rounded-md px-3 py-2 hover:bg-accent hover:text-accent-foreground shadow-none hover:shadow-none focus:shadow-none focus:outline-none focus:ring-0 transition-all";
const linkCollapsed = "flex items-center justify-center rounded-md p-2 hover:bg-accent hover:text-accent-foreground shadow-none hover:shadow-none focus:shadow-none focus:outline-none focus:ring-0 transition-all";
const active = "bg-secondary";
const { main } = useActiveSection();
// Get navigation tree from backend
const navTree = window.WNW_NAV_TREE || [];
return (
<aside className={`flex-shrink-0 border-r border-border sticky top-16 h-[calc(100vh-64px)] overflow-y-auto bg-background flex flex-col transition-all duration-200 ${collapsed ? 'w-14' : 'w-56'}`}>
{/* Toggle button */}
<div className={`p-2 border-b border-border ${collapsed ? 'flex justify-center' : 'flex justify-end'}`}>
<button
onClick={onToggle}
className="p-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors"
title={collapsed ? __('Expand sidebar') : __('Collapse sidebar')}
>
{collapsed ? <PanelLeft className="w-4 h-4" /> : <PanelLeftClose className="w-4 h-4" />}
</button>
</div>
<nav className={`flex flex-col gap-1 flex-1 ${collapsed ? 'p-1' : 'p-3'}`}>
{navTree.map((item: any) => {
const IconComponent = iconMap[item.icon] || Package;
const isActive = main.key === item.key;
return (
<Link
key={item.key}
to={item.path}
className={`${collapsed ? linkCollapsed : link} ${isActive ? active : ''}`}
title={collapsed ? item.label : undefined}
>
<IconComponent className="w-4 h-4 flex-shrink-0" />
{!collapsed && <span>{item.label}</span>}
</Link>
);
})}
</nav>
</aside>
);
}