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

@@ -40,6 +40,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { setQuery, getQuery } from '@/lib/query-params';
import { ProductCard } from './components/ProductCard';
import { FilterBottomSheet } from './components/FilterBottomSheet';
import { Pagination } from '@/components/Pagination';
import { SearchBar } from './components/SearchBar';
const stockStatusStyle: Record<string, string> = {
@@ -144,7 +145,7 @@ export default function Products() {
const deleteMutation = useMutation({
mutationFn: async (ids: number[]) => {
const results = await Promise.allSettled(
ids.map(id => api.del(`/products/${id}/edit`))
ids.map(id => api.del(`/products/${id}`))
);
const failed = results.filter(r => r.status === 'rejected').length;
return { total: ids.length, failed };
@@ -481,30 +482,13 @@ export default function Products() {
)}
{/* Pagination */}
{data && data.total > perPage && (
<div className="flex justify-between items-center pt-4">
<div className="text-sm text-muted-foreground">
{__('Showing')} {((page - 1) * perPage) + 1} - {Math.min(page * perPage, data.total)} {__('of')} {data.total}
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setPage(p => Math.max(1, p - 1))}
disabled={page === 1}
>
{__('Previous')}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setPage(p => p + 1)}
disabled={!data || page * perPage >= data.total}
>
{__('Next')}
</Button>
</div>
</div>
{data && (
<Pagination
page={page}
perPage={perPage}
total={data.total}
onPageChange={setPage}
/>
)}
{/* Delete Dialog */}