# 🧭 WooNooW β€” Single Source of Truth (S.O.P.) This document defines the **Standard Operating Procedure** for developing, maintaining, and collaborating on the **WooNooW** project β€” ensuring every AI Agent or human collaborator follows the same workflow and conventions. --- ## 1. 🎯 Project Intent WooNooW modernizes WooCommerce **without migration**, delivering a Hybrid + SPA experience for both **storefront** and **admin**, while keeping compatibility with legacy WooCommerce addons. > **Goal:** β€œReimagine WooCommerce for now β€” faster, modern, reversible.” --- ## 1.1 πŸ“ Documentation Standards ### Progress & Testing Documentation **All progress notes and reports MUST be added to:** - `PROGRESS_NOTE.md` - Consolidated progress tracking with timestamps **All test checklists MUST be added to:** - `TESTING_CHECKLIST.md` - Comprehensive testing requirements **Feature-specific documentation:** - Create dedicated `.md` files for major features (e.g., `PAYMENT_GATEWAY_INTEGRATION.md`) - Link to these files from `PROGRESS_NOTE.md` - Include implementation details, code examples, and testing steps **API Routes documentation:** - `API_ROUTES.md` - Complete registry of all REST API routes - **MUST be updated** when adding new API endpoints - Prevents route conflicts between modules - Documents ownership and naming conventions **Metabox & Custom Fields compatibility:** - `METABOX_COMPAT.md` - πŸ”΄ **CRITICAL** compatibility requirement - Documents how to expose WordPress/WooCommerce metaboxes in SPA - **Currently NOT implemented** - blocks production readiness - Required for third-party plugin compatibility (Shipment Tracking, ACF, etc.) **Documentation Rules:** 1. βœ… Update `PROGRESS_NOTE.md` after completing any major feature 2. βœ… Add test cases to `TESTING_CHECKLIST.md` before implementation 3. βœ… Use consistent formatting (emojis, headings, code blocks) 4. βœ… Include "Last synced" timestamp in GMT+7 5. βœ… Reference file paths and line numbers for code changes --- ## 2. 🧱 Core Principles 1. **Zero Data Migration** β€” All data remains in WooCommerce’s database schema. 2. **Safe Activation/Deactivation** β€” Deactivating WooNooW restores vanilla Woo instantly. 3. **HPOS-First Architecture** β€” Mandatory use of WooCommerce HPOS. 4. **Hybrid by Default** β€” SSR + React SPA islands for Cart, Checkout, and My‑Account. 5. **Full SPA Option** β€” Optional React-only mode for performance-critical sites. 6. **Compat Layer** β€” HookBridge & SlotRenderer preserve legacy addon behavior. 7. **Async System** β€” MailQueue & async actions replace blocking PHP tasks. --- ## 3. βš™οΈ Tech Stack Reference | Layer | Technology | |-------|-------------| | Backend | PHP 8.2+, WordPress, WooCommerce (HPOS), Action Scheduler | | Frontend | React 18 + TypeScript, Vite, React Query, Tailwind CSS + Shadcn UI, Recharts | | Architecture | Modular PSR‑4 autoload, REST‑driven logic, SPA hydration islands | | Build | Composer + NPM + ESM scripts | | Packaging | `scripts/package-zip.mjs` | | Deployment | LocalWP for dev, Coolify for staging | --- ## 4. 🧩 Folder Structure ``` woonoow/ β”œβ”€ woonoow.php # main plugin file (WordPress entry) β”œβ”€ includes/ # PSR‑4 classes β”‚ β”œβ”€ Core/ # Bootstrap, Datastores, Mail, Hooks β”‚ β”œβ”€ Api/ # REST endpoints β”‚ β”œβ”€ Admin/ # Menus, asset loaders β”‚ β”œβ”€ Compat/ # Compatibility shims & hook mirrors β”‚ └─ … β”œβ”€ admin-spa/ # React admin interface β”œβ”€ customer-spa/ # React customer interface β”œβ”€ scripts/ # automation scripts β”‚ └─ package-zip.mjs β”œβ”€ dist/ # build output β”œβ”€ composer.json β”œβ”€ package.json β”œβ”€ README.md └─ PROJECT_SOP.md # this file ``` --- ## 5. 🧰 Development Workflow ### 5.1 Environment Setup 1. Use **LocalWP** or **Docker** (PHP 8.2+, MySQL 8, Redis optional). 2. Clone or mount `woonoow` folder into `/wp-content/plugins/`. 3. Ensure WooCommerce is installed and active. 4. Activate WooNooW in wp-admin β†’ β€œPlugins.” ### 5.2 Build & Test Commands ```bash npm run build # build both admin & customer SPAs npm run pack # create woonoow.zip for release composer dump-autoload ``` ### 5.3 Plugin Packaging - The release ZIP must contain only: ``` woonoow.php includes/ admin-spa/dist/ customer-spa/dist/ composer.json package.json phpcs.xml README.md ``` - Build ZIP using: ```bash node scripts/package-zip.mjs ``` ### 5.4 Commit Convention Use conventional commits: ``` feat(api): add checkout quote endpoint fix(core): prevent duplicate email send on async queue refactor(admin): improve SPA routing ``` ### 5.5 Branching - `main` β€” stable, production-ready - `dev` β€” development staging - `feature/*` β€” specific features or fixes ### 5.6 Admin SPA Template Pattern The WooNooW Admin SPA follows a consistent layout structure ensuring a predictable UI across all routes: **Structure** ``` Admin-SPA β”œβ”€β”€ App Bar [Branding | Version | Server Connectivity | Global Buttons (Fullscreen)] β”œβ”€β”€ Menu Bar (Main Menu) [Normal (Tabbed Overflow-X-Auto)] [Fullscreen (Sidebar)] β”œβ”€β”€ Submenu Bar (Tabbed Overflow-X-Auto, context-sensitive) └── Page Template β”œβ”€β”€ Page Tool Bar (Page filters, CRUD buttons, Back button) └── Page Content (Data tables, cards, forms) ``` **Behavioral Notes** - `App Bar`: Persistent across all routes; contains global controls (fullscreen, server, user menu). - `Menu Bar`: Primary navigation for main sections (Dashboard, Orders, Products, etc.); sticky with overflow-x scroll. - `Submenu Bar`: Context-sensitive secondary tabs under the main menu. - `Page Tool Bar`: Contains functional filters and actions relevant to the current page. - `Page Content`: Hosts the page bodyβ€”tables, analytics, and CRUD forms. - In Fullscreen mode, `Menu Bar` becomes a collapsible sidebar while all others remain visible. - Sticky layout rules ensure `App Bar` and `Menu Bar` remain fixed while content scrolls independently. ### 5.7 CRUD Module Pattern (Standard Operating Procedure) WooNooW enforces a **consistent CRUD pattern** for all entity management modules (Orders, Products, Customers, etc.) to ensure predictable UX and maintainability. **Core Principle:** All CRUD modules MUST follow the submenu tab pattern with consistent toolbar structure. #### UI Structure **Submenu Tabs Pattern:** ``` [All {Entity}] [New] [Categories] [Tags] [Other Sections...] ``` **Toolbar Structure:** ``` [Bulk Actions] [Filters...] [Search] ``` **Examples:** - **Products:** `All products | New | Categories | Tags | Attributes` - **Orders:** `All orders | New | Drafts | Recurring` - **Customers:** `All customers | New | Groups | Segments` #### Implementation Rules 1. **βœ… Use Submenu Tabs** for main sections - Primary action (New) is a tab, NOT a toolbar button - Tabs for related entities (Categories, Tags, etc.) - Consistent with WordPress/WooCommerce patterns 2. **βœ… Toolbar for Actions & Filters** - Bulk actions (Delete, Export, etc.) - Filter dropdowns (Status, Type, Date, etc.) - Search input - NO primary CRUD buttons (New, Edit, etc.) 3. **❌ Don't Mix Patterns** - Don't put "New" button in toolbar if using submenu - Don't duplicate actions in both toolbar and submenu - Don't use different patterns for different modules #### Why This Pattern? **Industry Standard:** - Shopify Admin uses submenu tabs - WooCommerce uses submenu tabs - WordPress core uses submenu tabs **Benefits:** - **Scalability:** Easy to add new sections - **Consistency:** Users know where to find actions - **Clarity:** Visual hierarchy between main actions and filters #### Migration Checklist When updating an existing module to follow this pattern: - [ ] Move "New {Entity}" button from toolbar to submenu tab - [ ] Add other relevant tabs (Drafts, Categories, etc.) - [ ] Keep filters and bulk actions in toolbar - [ ] Update navigation tree in `NavigationRegistry.php` - [ ] Test mobile responsiveness (tabs scroll horizontally) #### Code Example **Navigation Tree (PHP):** ```php 'orders' => [ 'label' => __('Orders', 'woonoow'), 'path' => '/orders', 'icon' => 'ShoppingCart', 'children' => [ 'all' => [ 'label' => __('All orders', 'woonoow'), 'path' => '/orders', ], 'new' => [ 'label' => __('New', 'woonoow'), 'path' => '/orders/new', ], 'drafts' => [ 'label' => __('Drafts', 'woonoow'), 'path' => '/orders/drafts', ], ], ], ``` **Submenu Component (React):** ```typescript ``` **Toolbar (React):** ```typescript ``` #### Toolbar Button Standards All CRUD list pages MUST use consistent button styling in the toolbar: **Button Types:** | Button Type | Classes | Use Case | |-------------|---------|----------| | **Delete (Destructive)** | `border rounded-md px-3 py-2 text-sm bg-red-600 text-white hover:bg-red-700 disabled:opacity-50 inline-flex items-center gap-2` | Bulk delete action | | **Refresh (Required)** | `border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50 inline-flex items-center gap-2` | Refresh data (MUST exist in all CRUD lists) | | **Reset Filters** | `text-sm text-muted-foreground hover:text-foreground underline` | Clear all active filters | | **Export/Secondary** | `border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50 inline-flex items-center gap-2` | Other secondary actions | **Button Structure:** ```tsx ``` **Rules:** 1. βœ… **Delete button** - Always use `bg-red-600` (NOT `bg-black`) 2. βœ… **Refresh button** - MUST exist in all CRUD list pages (mandatory) 3. βœ… **Reset filters** - Use text link style (NOT button with background) 4. βœ… **Icon placement** - Use `inline-flex items-center gap-2` (NOT `inline mr-2`) 5. βœ… **Destructive actions** - Only show when items selected (conditional render) 6. βœ… **Non-destructive actions** - Can be always visible (use `disabled` state) 7. βœ… **Consistent spacing** - Use `gap-2` between icon and text 8. βœ… **Hover states** - Destructive: `hover:bg-red-700`, Secondary: `hover:bg-accent` 9. ❌ **Never use `bg-black`** for delete buttons 10. ❌ **Never use `inline mr-2`** - use `inline-flex gap-2` instead 11. ❌ **Never use button style** for reset filters - use text link **Toolbar Layout:** ```tsx
{/* Left: Bulk Actions */}
{/* Delete - Show only when items selected */} {selectedIds.length > 0 && ( )} {/* Refresh - Always visible (REQUIRED) */}
{/* Right: Filters */}
{/* Reset Filters - Text link style */} {activeFiltersCount > 0 && ( )}
``` #### Table/List UI Standards All CRUD list pages MUST follow these consistent UI patterns: **Table Structure:** ```tsx
{/* ... more columns */} {/* ... more cells */}
{/* Checkbox */} {__('Column')}
{/* Cell content */}
``` **Required Classes:** | Element | Classes | Purpose | |---------|---------|---------| | **Container** | `rounded-lg border overflow-hidden` | Rounded corners, border, hide overflow | | **Table** | `w-full` | Full width | | **Header Row** | `bg-muted/50` + `border-b` | Light background, bottom border | | **Header Cell** | `p-3 font-medium text-left` | Padding, bold, left-aligned | | **Body Row** | `border-b hover:bg-muted/30 last:border-0` | Border, hover effect, remove last border | | **Body Cell** | `p-3` | Consistent padding (NOT `px-3 py-2`) | | **Checkbox Column** | `w-12 p-3` | Fixed width for checkbox | | **Actions Column** | `text-right p-3` or `text-center p-3` | Right/center aligned | **Empty State Pattern:** ```tsx {primaryMessage} {helperText &&

{helperText}

} ``` **Mobile Card Pattern:** ```tsx
{items.map(item => ( {/* Card content */} ))}
``` **Rules:** 1. βœ… **Always use `p-3`** for table cells (NOT `px-3 py-2`) 2. βœ… **Always add `hover:bg-muted/30`** to body rows 3. βœ… **Always use `bg-muted/50`** for table headers 4. βœ… **Always use `font-medium`** for header cells 5. βœ… **Always use `last:border-0`** to remove last row border 6. βœ… **Always use `overflow-hidden`** on table container 7. ❌ **Never mix padding styles** between modules 8. ❌ **Never omit hover effects** on interactive rows **Responsive Behavior:** - Desktop: Show table with `hidden md:block` - Mobile: Show cards with `md:hidden` - Both views must support same actions (select, edit, delete) #### Variable Product Handling in Order Forms When adding products to orders, variable products MUST follow the Tokopedia/Shopee pattern: **Responsive Modal Pattern:** - **Desktop:** Use `Dialog` component (centered modal) - **Mobile:** Use `Drawer` component (bottom sheet) - **Detection:** Use `useMediaQuery("(min-width: 768px)")` **Implementation:** ```tsx const isDesktop = useMediaQuery("(min-width: 768px)"); {/* Desktop: Dialog */} {selectedProduct && isDesktop && ( {product.name} {/* Variation list */} )} {/* Mobile: Drawer */} {selectedProduct && !isDesktop && ( {product.name} {/* Variation list */} )} ``` **Desktop Pattern:** ``` [Search Product...] ↓ [Product Name - Variable Product] └─ [Select Variation β–Ό] β†’ Dropdown: Red, Blue, Green [Add to Order] ``` **Mobile Pattern:** ``` [Search Product...] ↓ [Product Card] Product Name [Select Variation β†’] β†’ Opens drawer with variation chips [Add] ``` **Cart Display (Each variation = separate row):** ``` βœ“ Anker Earbuds White Rp296,000 [-] 1 [+] [πŸ—‘οΈ] βœ“ Anker Earbuds Black Rp296,000 [-] 1 [+] [πŸ—‘οΈ] **Rules:** 1. βœ… Each variation is a **separate line item** 2. βœ… Show variation name clearly next to product name 3. βœ… Allow adding same product multiple times with different variations 4. βœ… Mobile: Click variation to open drawer for selection 5. ❌ Don't auto-select first variation 6. ❌ Don't hide variation selector 7. βœ… **Duplicate Handling**: Same product + same variation = increment quantity (NOT new row) 8. βœ… **Empty Attribute Values**: Filter empty attribute values - Use `.filter()` to remove empty strings **Implementation:** - Product search shows variable products - If variable, show variation selector (dropdown/drawer) - User must select variation before adding - Each selected variation becomes separate cart item - Can repeat for different variations ### 5.8 Mobile Responsiveness & UI Controls WooNooW enforces a mobile‑first responsive standard across all SPA interfaces to ensure usability on small screens. **Control Sizing Standard (`.ui-ctrl`)** - All interactive controls β€” input, select, button, and dropdown options β€” must include the `.ui-ctrl` class or equivalent utility for consistent sizing. - Default height: `h-11` (mobile), `md:h-9` (desktop). - This sizing improves tap area accessibility and maintains visual alignment between mobile and desktop. **Responsive Layout Rules** - On mobile view, even in fullscreen mode, the layout uses **Topbar navigation** instead of Sidebar for better reachability. - The Sidebar layout is applied **only** in desktop fullscreen mode. - Sticky top layers (`App Bar`, `Menu Bar`) remain visible while sub‑content scrolls independently. - Tables and grids must support horizontal scroll (`overflow-x-auto`) and collapse to cards when screen width < 640px. **Tokens & Global Styles** - File: `admin-spa/src/ui/tokens.css` defines base CSS variables for control sizing. - File: `admin-spa/src/index.css` imports `./ui/tokens.css` and applies the `.ui-ctrl` rules globally. These rules ensure consistent UX across device classes while maintaining WooNooW's design hierarchy. ### 5.8 Dialog Behavior Pattern WooNooW uses **Radix UI Dialog** with specific patterns for preventing accidental dismissal. **Core Principle:** Prevent outside-click and escape-key dismissal for dialogs with unsaved changes or complex editing. **Dialog Types:** | Type | Outside Click | Escape Key | Use Case | Example | |------|---------------|------------|----------|---------| | **Informational** | βœ… Allow | βœ… Allow | Simple info, confirmations | Alert dialogs | | **Quick Edit** | βœ… Allow | βœ… Allow | Single field edits | Rename, quick settings | | **Heavy Edit** | ❌ Prevent | ❌ Prevent | Multi-field forms, rich content | Email builder, template editor | | **Destructive** | ❌ Prevent | ❌ Prevent | Delete confirmations with input | Delete with confirmation text | **Implementation:** ```typescript // Heavy Edit Dialog - Prevent accidental dismissal e.preventDefault()} onEscapeKeyDown={(e) => e.preventDefault()} > {/* Dialog content */} // Quick Edit Dialog - Allow dismissal {/* Simple content */} ``` **Rules:** 1. βœ… **Prevent dismissal** when: - Dialog contains unsaved form data - User is editing rich content (WYSIWYG, code editor) - Dialog has multiple steps or complex state - Action is destructive and requires confirmation 2. βœ… **Allow dismissal** when: - Dialog is purely informational - Single field with auto-save - No data loss risk - Quick actions (view, select) 3. βœ… **Always provide explicit close buttons**: - Cancel button to close without saving - Save button to commit changes - X button in header (Radix default) **Examples:** - ❌ Prevent: `admin-spa/src/components/EmailBuilder/EmailBuilder.tsx` - Block edit dialog - ❌ Prevent: Template editor dialogs with rich content - βœ… Allow: Simple confirmation dialogs - βœ… Allow: View-only information dialogs **Best Practice:** When in doubt, **prevent dismissal** for editing dialogs. It's better to require explicit Cancel/Save than risk data loss. **Responsive Dialog/Drawer Pattern:** For settings pages and forms, use **ResponsiveDialog** component that automatically switches between Dialog (desktop) and Drawer (mobile): ```typescript import { ResponsiveDialog } from '@/components/ui/responsive-dialog'; } > {/* Form content */} ``` **Behavior:** - **Desktop (β‰₯768px)**: Shows centered Dialog - **Mobile (<768px)**: Shows bottom Drawer for better reachability **Component:** `admin-spa/src/components/ui/responsive-dialog.tsx` ### 5.9 Settings Page Layout Pattern WooNooW enforces a **consistent layout pattern** for all settings pages to ensure predictable UX and maintainability. **Core Principle:** All settings pages MUST use `SettingsLayout` component with contextual header. **Implementation Pattern:** ```typescript import { SettingsLayout } from './components/SettingsLayout'; export default function MySettingsPage() { const [settings, setSettings] = useState({...}); const [isLoading, setIsLoading] = useState(true); const handleSave = async () => { // Save logic }; if (isLoading) { return (
); } return ( {/* Settings content - automatically boxed with max-w-5xl */} {/* Form fields */} ); } ``` **SettingsLayout Props:** | Prop | Type | Required | Description | |------|------|----------|-------------| | `title` | `string \| ReactNode` | Yes | Page title shown in contextual header | | `description` | `string` | No | Subtitle/description below title | | `onSave` | `() => Promise` | No | Save handler - shows Save button in header | | `saveLabel` | `string` | No | Custom label for save button (default: "Save changes") | | `isLoading` | `boolean` | No | Shows loading state | | `action` | `ReactNode` | No | Custom action buttons (e.g., Back button) | **Layout Behavior:** 1. **Contextual Header** (Mobile + Desktop) - Shows page title and description - Shows Save button if `onSave` provided - Shows custom actions if `action` provided - Sticky at top of page 2. **Content Area** - Automatically boxed with `max-w-5xl mx-auto` - Responsive padding and spacing - Consistent with other admin pages 3. **No Inline Header** - When using `onSave` or `action`, inline header is hidden - Title/description only appear in contextual header - Saves vertical space **Rules for Settings Pages:** 1. βœ… **Always use SettingsLayout** - Never create custom layout 2. βœ… **Pass title/description to layout** - Don't render inline headers 3. βœ… **Use onSave for save actions** - Don't render save buttons in content 4. βœ… **Use SettingsCard for sections** - Consistent card styling 5. βœ… **Show loading state** - Use `isLoading` prop during data fetch 6. ❌ **Never use full-width layout** - Content is always boxed 7. ❌ **Never duplicate save buttons** - One save button in header only **Examples:** - βœ… Good: `admin-spa/src/routes/Settings/Customers.tsx` - βœ… Good: `admin-spa/src/routes/Settings/Notifications/Staff.tsx` - βœ… Good: `admin-spa/src/routes/Settings/Notifications/Customer.tsx` **Files:** - Layout component: `admin-spa/src/routes/Settings/components/SettingsLayout.tsx` - Card component: `admin-spa/src/routes/Settings/components/SettingsCard.tsx` ### 5.9 Mobile Contextual Header Pattern WooNooW implements a **dual-header system** for mobile-first UX, ensuring actionable pages have consistent navigation and action buttons. **Concept: Two Headers on Mobile** 1. **Contextual Header** (Mobile + Desktop) - Common actions that work everywhere - Format: `[Back Button] Page Title [Primary Action]` - Always visible (sticky) - Examples: Back, Edit, Save, Create 2. **Page Header / Extra Actions** (Desktop Only) - Additional desktop-specific actions - Hidden on mobile (`hidden md:flex`) - Examples: Print, Invoice, Label, Export **Implementation Pattern** ```typescript import { usePageHeader } from '@/contexts/PageHeaderContext'; import { Button } from '@/components/ui/button'; export default function MyPage() { const { setPageHeader, clearPageHeader } = usePageHeader(); const nav = useNavigate(); // Set contextual header useEffect(() => { const actions = (
); setPageHeader(__('Page Title'), actions); return () => clearPageHeader(); }, [dependencies]); return (
{/* Desktop-only extra actions */}
{/* Page content */}
); } ``` **Rules for CRUD Pages** | Page Type | Contextual Header | Page Header | |-----------|-------------------|-------------| | **List** | None (list page) | Filters, Search | | **Detail** | [Back] Title [Edit] | Print, Invoice, Label | | **New** | [Back] Title [Create] | None | | **Edit** | [Back] Title [Save] | None | **Form Submit Pattern** For New/Edit pages, move submit button to contextual header: ```typescript // Use formRef to trigger submit from header const formRef = useRef(null); const actions = ( ); ``` **Best Practices** 1. **No Duplication** - If action is in contextual header, remove from page body 2. **Mobile First** - Contextual header shows essential actions only 3. **Desktop Enhancement** - Extra actions in page header (desktop only) 4. **Consistent Pattern** - All CRUD pages follow same structure 5. **Loading States** - Buttons show loading state during mutations **Files** - `admin-spa/src/contexts/PageHeaderContext.tsx` - Context provider - `admin-spa/src/hooks/usePageHeader.ts` - Hook for setting headers - `admin-spa/src/components/PageHeader.tsx` - Header component ### 5.8 Error Handling & User Notifications WooNooW implements a centralized, user-friendly error handling system that ensures consistent UX across all features. **Core Principles** 1. **Never expose technical details** to end users (no "API 500", stack traces, or raw error codes) 2. **Use appropriate notification types** based on context 3. **Provide actionable feedback** with clear next steps 4. **Maintain consistency** across all pages and features **Notification Types** | Context | Component | Use Case | Example | |---------|-----------|----------|---------| | **Page Load Errors** | `` | Query failures, data fetch errors | "Failed to load orders" with retry button | | **Action Errors** | `toast.error()` | Mutation failures, form submissions | "Failed to create order. Please check all required fields." | | **Action Success** | `toast.success()` | Successful mutations | "Order created successfully" | | **Inline Validation** | `` | Form field errors | "Email address is required" | **Implementation** ```typescript // For mutations (create, update, delete) import { showErrorToast, showSuccessToast } from '@/lib/errorHandling'; const mutation = useMutation({ mutationFn: OrdersApi.create, onSuccess: (data) => { showSuccessToast('Order created successfully', `Order #${data.number} created`); }, onError: (error) => { showErrorToast(error); // Automatically extracts user-friendly message } }); // For queries (page loads) import { ErrorCard } from '@/components/ErrorCard'; import { getPageLoadErrorMessage } from '@/lib/errorHandling'; if (query.isError) { return query.refetch()} />; } ``` **Error Message Mapping** Backend errors are mapped to user-friendly messages in `lib/errorHandling.ts`: ```typescript const friendlyMessages = { 'no_items': 'Please add at least one product to the order', 'create_failed': 'Failed to create order. Please check all required fields.', 'update_failed': 'Failed to update order. Please check all fields.', 'not_found': 'The requested item was not found', 'forbidden': 'You do not have permission to perform this action', }; ``` **Toast Configuration** - **Position:** Bottom-right - **Duration:** 4s (success), 6s (errors) - **Theme:** Light mode with colored backgrounds - **Colors:** Green (success), Red (error), Amber (warning), Blue (info) **Files** - `admin-spa/src/lib/errorHandling.ts` β€” Centralized error utilities - `admin-spa/src/components/ErrorCard.tsx` β€” Page load error component - `admin-spa/src/components/ui/sonner.tsx` β€” Toast configuration ### 5.9 Data Validation & Required Fields WooNooW enforces strict validation rules to ensure data integrity and provide clear feedback to users. **Order Creation Validation** All orders must include: | Field | Requirement | Error Message | |-------|-------------|---------------| | **Products** | At least 1 product | "At least one product is required" | | **Billing First Name** | Required | "Billing first name is required" | | **Billing Last Name** | Required | "Billing last name is required" | | **Billing Email** | Required & valid format | "Billing email is required" / "Billing email is not valid" | | **Billing Address** | Required | "Billing address is required" | | **Billing City** | Required | "Billing city is required" | | **Billing Postcode** | Required | "Billing postcode is required" | | **Billing Country** | Required | "Billing country is required" | **Backend Validation Response** When validation fails, the API returns: ```json { "error": "validation_failed", "message": "Please complete all required fields", "fields": [ "Billing first name is required", "Billing email is required", "Billing address is required" ] } ``` **Frontend Display** The error handling utility automatically formats field errors as a bulleted list: ``` ❌ Please complete all required fields β€’ Billing first name is required β€’ Billing email is required β€’ Billing address is required β€’ Billing city is required β€’ Billing postcode is required ``` Each field error appears as a bullet point on its own line, making it easy for users to scan and see exactly what needs to be fixed. **Implementation Location** - Backend validation: `includes/Api/OrdersController.php` create() method - Frontend handling: `admin-spa/src/lib/errorHandling.ts` getErrorMessage() ### 5.10 Internationalization (i18n) WooNooW follows WordPress translation standards to ensure all user-facing strings are translatable. **Text Domain:** `woonoow` **Backend (PHP)** Use WordPress translation functions: ```php // Simple translation __( 'Billing first name', 'woonoow' ) // Translation with sprintf sprintf( __( '%s is required', 'woonoow' ), $field_label ) // Translators comment for context /* translators: %s: field label */ sprintf( __( '%s is required', 'woonoow' ), $label ) ``` **Frontend (TypeScript/React)** Use the i18n utility wrapper: ```typescript import { __, sprintf } from '@/lib/i18n'; // Simple translation __('Failed to load data') // Translation with sprintf (placeholders) sprintf(__('Order #%s created'), orderNumber) sprintf(__('Edit Order #%s'), orderId) // In components

{sprintf(__('Order #%s'), order.number)}

// In error messages const title = __('Please complete all required fields'); const message = sprintf(__('Order #%s has been created'), data.number); ``` **Translation Files** - Backend strings: Extracted to `languages/woonoow.pot` - Frontend strings: Loaded via `wp.i18n` (WordPress handles this) - Translation utilities: `admin-spa/src/lib/i18n.ts` **Best Practices** 1. **Never hardcode user-facing strings** - Always use translation functions 2. **Use translators comments** for context when using placeholders 3. **Keep strings simple** - Avoid complex concatenation 4. **Test in English first** - Ensure strings make sense before translation --- ## 5.11 Loading States WooNooW provides a **consistent loading UI system** across the application to ensure a polished user experience. **Component:** `admin-spa/src/components/LoadingState.tsx` ### Loading Components **1. LoadingState (Default)**a ```typescript import { LoadingState } from '@/components/LoadingState'; // Default loading // Custom message // Different sizes // default // Full screen overlay ``` **2. PageLoadingState** ```typescript import { PageLoadingState } from '@/components/LoadingState'; // For full page loads if (isLoading) { return ; } ``` **3. InlineLoadingState** ```typescript import { InlineLoadingState } from '@/components/LoadingState'; // For inline loading within components {isLoading && } ``` **4. CardLoadingSkeleton** ```typescript import { CardLoadingSkeleton } from '@/components/LoadingState'; // For loading card content {isLoading && } ``` **5. TableLoadingSkeleton** ```typescript import { TableLoadingSkeleton } from '@/components/LoadingState'; // For loading table rows {isLoading && } ``` ### Usage Guidelines **Page-Level Loading:** ```typescript // βœ… Good - Use PageLoadingState for full page loads if (orderQ.isLoading || countriesQ.isLoading) { return ; } // ❌ Bad - Don't use plain text if (isLoading) { return
Loading...
; } ``` **Inline Loading:** ```typescript // βœ… Good - Use InlineLoadingState for partial loads {q.isLoading && } // ❌ Bad - Don't use custom spinners {q.isLoading &&
Loading...
} ``` **Table Loading:** ```typescript // βœ… Good - Use TableLoadingSkeleton for tables {q.isLoading && } // ❌ Bad - Don't show empty state while loading {q.isLoading &&
Loading data...
} ``` ### Best Practices 1. **Always use i18n** - All loading messages must be translatable ```typescript ``` 2. **Be specific** - Use descriptive messages ```typescript // βœ… Good // ❌ Bad ``` 3. **Choose appropriate size** - Match the context - `sm` - Inline, buttons, small components - `md` - Default, cards, sections - `lg` - Full page, important actions 4. **Use skeletons for lists** - Better UX than spinners ```typescript {isLoading ? : } ``` 5. **Responsive design** - Loading states work on all screen sizes - Mobile: Optimized spacing and sizing - Desktop: Full layout preserved ### Pattern Examples **Order Edit Page:** ```typescript export default function OrdersEdit() { const orderQ = useQuery({ ... }); if (orderQ.isLoading) { return ; } return ; } ``` **Order Detail Page:** ```typescript export default function OrderDetail() { const q = useQuery({ ... }); return (

{__('Order Details')}

{q.isLoading && } {q.data && }
); } ``` **Orders List:** ```typescript export default function OrdersList() { const q = useQuery({ ... }); return (
... {q.isLoading && } {q.data?.map(order => )}
); } ``` --- ## 6. πŸ”Œ Addon Development Standards ### 6.1 Addon Injection System WooNooW provides a **filter-based addon injection system** that allows third-party plugins to integrate seamlessly with the SPA without modifying core files. **Core Principle:** All modules that can accept external injection MUST provide filter hooks following the standard naming convention. ### 6.2 Hook Naming Convention All WooNooW hooks follow this structure: ``` woonoow/{category}/{action}[/{subcategory}] ``` **Examples:** - `woonoow/addon_registry` - Register addon metadata - `woonoow/spa_routes` - Register SPA routes - `woonoow/nav_tree` - Modify navigation tree - `woonoow/nav_tree/products/children` - Inject into Products submenu - `woonoow/dashboard/widgets` - Add dashboard widgets (future) - `woonoow/order/detail/panels` - Add order detail panels (future) **Rules:** 1. Always prefix with `woonoow/` 2. Use lowercase with underscores 3. Use singular nouns for registries (`addon_registry`, not `addons_registry`) 4. Use hierarchical structure for nested items 5. Use descriptive names that indicate purpose ### 6.3 Filter Template Pattern When creating a new module that accepts external injection, follow this template: #### **Backend (PHP)** ```php 'my-item', * 'label' => 'My Item', * 'value' => 'something', * ]; * return $data; * }); */ $data = apply_filters('woonoow/my_module/items', $data); // Validate and store $validated = self::validate_items($data); update_option(self::OPTION_KEY, [ 'version' => self::VERSION, 'items' => $validated, 'updated' => time(), ], false); } private static function validate_items(array $items): array { // Validation logic return $items; } public static function get_items(): array { $data = get_option(self::OPTION_KEY, []); return $data['items'] ?? []; } public static function flush() { delete_option(self::OPTION_KEY); } public static function get_frontend_data(): array { // Return sanitized data for frontend return self::get_items(); } } ``` #### **Expose to Frontend (Assets.php)** ```php // In localize_runtime() method wp_localize_script($handle, 'WNW_MY_MODULE', MyModuleRegistry::get_frontend_data()); wp_add_inline_script($handle, 'window.WNW_MY_MODULE = window.WNW_MY_MODULE || WNW_MY_MODULE;', 'after'); ``` #### **Frontend (TypeScript)** ```typescript // Read from window const moduleData = (window as any).WNW_MY_MODULE || []; // Use in component function MyComponent() { const items = (window as any).WNW_MY_MODULE || []; return (
{items.map(item => (
{item.label}
))}
); } ``` ### 6.4 Documentation Requirements When adding a new filter hook, you MUST: 1. **Add to Hook Registry** (see section 6.5) 2. **Document in code** with PHPDoc 3. **Add example** in ADDON_INJECTION_GUIDE.md 4. **Update** ADDONS_ADMIN_UI_REQUIREMENTS.md ### 6.5 Hook Registry See `HOOKS_REGISTRY.md` for complete list of available hooks and filters. ### 6.6 Non-React Addon Development **Question:** Can developers build addons without React? **Answer:** **YES!** WooNooW supports multiple addon approaches: #### **Approach 1: PHP + HTML/CSS/JS (No React)** Traditional WordPress addon development works perfectly: ```php 'my-addon', 'name' => 'My Addon', 'version' => '1.0.0', ]; return $addons; }); // Add navigation item that links to classic admin page add_filter('woonoow/nav_tree', function($tree) { $tree[] = [ 'key' => 'my-addon', 'label' => 'My Addon', 'path' => '/my-addon-classic', // Will redirect to admin page 'icon' => 'puzzle', 'children' => [], ]; return $tree; }); // Register classic admin page add_action('admin_menu', function() { add_menu_page( 'My Addon', 'My Addon', 'manage_options', 'my-addon-page', 'my_addon_render_page', 'dashicons-admin-generic', 30 ); }); function my_addon_render_page() { ?>

My Traditional Addon

Built with PHP, HTML, CSS, and vanilla JS!

My Addon

Built with vanilla JavaScript!

`; // Add event listeners setTimeout(() => { const button = container.querySelector('#my-button'); button.addEventListener('click', () => { alert('Vanilla JS works!'); }); }, 0); return container; } ``` **This approach:** - βœ… Integrates with SPA - βœ… No React required - βœ… Can use Tailwind classes - βœ… Can fetch from REST API - ⚠️ Must return DOM element - ⚠️ Manual state management #### **Approach 3: React Component (Full SPA)** For developers comfortable with React: ```typescript // dist/MyAddon.tsx - React component import React from 'react'; export default function MyAddonPage() { const [count, setCount] = React.useState(0); return (

My Addon

Built with React!

); } ``` **This approach:** - βœ… Full SPA integration - βœ… React state management - βœ… Can use React Query - βœ… Can use WooNooW components - βœ… Best UX - ⚠️ Requires React knowledge ### 6.7 Addon Development Checklist When creating a module that accepts addons: - [ ] Create Registry class (e.g., `MyModuleRegistry.php`) - [ ] Add filter hook with `woonoow/` prefix - [ ] Document filter in PHPDoc with example - [ ] Expose data to frontend via `Assets.php` - [ ] Add to `HOOKS_REGISTRY.md` - [ ] Add example to `ADDON_INJECTION_GUIDE.md` - [ ] Test with example addon - [ ] Update `ADDONS_ADMIN_UI_REQUIREMENTS.md` ### 6.8 Orders Module as Reference The **Orders module** is the reference implementation: - No external injection (by design) - Clean route structure - Type-safe components - Proper error handling - Mobile responsive - i18n complete Use Orders as the template for building new core modules. --- ## 6.9 CRUD Module Pattern (Standard Template) **All CRUD modules (Orders, Products, Customers, Coupons, etc.) MUST follow this exact pattern for consistency.** ### πŸ“ File Structure ``` admin-spa/src/routes/{Module}/ β”œβ”€β”€ index.tsx # List view (table + filters) β”œβ”€β”€ New.tsx # Create new item β”œβ”€β”€ Edit.tsx # Edit existing item β”œβ”€β”€ Detail.tsx # View item details (optional) β”œβ”€β”€ components/ # Module-specific components β”‚ β”œβ”€β”€ {Module}Card.tsx # Mobile card view β”‚ β”œβ”€β”€ FilterBottomSheet.tsx # Mobile filters β”‚ └── SearchBar.tsx # Search component └── partials/ # Shared form components └── {Module}Form.tsx # Reusable form for create/edit ``` ### 🎯 Backend API Pattern **File:** `includes/Api/{Module}Controller.php` ```php 'GET', 'callback' => [__CLASS__, 'get_{module}'], 'permission_callback' => [Permissions::class, 'check_admin'], ]); // Single register_rest_route('woonoow/v1', '/{module}/(?P\d+)', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_{item}'], 'permission_callback' => [Permissions::class, 'check_admin'], ]); // Create register_rest_route('woonoow/v1', '/{module}', [ 'methods' => 'POST', 'callback' => [__CLASS__, 'create_{item}'], 'permission_callback' => [Permissions::class, 'check_admin'], ]); // Update register_rest_route('woonoow/v1', '/{module}/(?P\d+)', [ 'methods' => 'PUT', 'callback' => [__CLASS__, 'update_{item}'], 'permission_callback' => [Permissions::class, 'check_admin'], ]); // Delete register_rest_route('woonoow/v1', '/{module}/(?P\d+)', [ 'methods' => 'DELETE', 'callback' => [__CLASS__, 'delete_{item}'], 'permission_callback' => [Permissions::class, 'check_admin'], ]); } // List with pagination & filters public static function get_{module}(WP_REST_Request $request) { $page = max(1, (int) $request->get_param('page')); $per_page = min(100, max(1, (int) ($request->get_param('per_page') ?: 20))); $search = $request->get_param('search'); $status = $request->get_param('status'); $orderby = $request->get_param('orderby') ?: 'date'; $order = $request->get_param('order') ?: 'DESC'; // Query logic here return new WP_REST_Response([ 'rows' => $items, 'total' => $total, 'page' => $page, 'per_page' => $per_page, 'pages' => $max_pages, ], 200); } } ``` **Register in Routes.php:** ```php use WooNooW\Api\{Module}Controller; // In rest_api_init: {Module}Controller::register_routes(); ``` ### 🎨 Frontend Index Page Pattern **File:** `admin-spa/src/routes/{Module}/index.tsx` ```typescript import React, { useState, useCallback } from 'react'; import { useQuery, useMutation, keepPreviousData } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { useFABConfig } from '@/hooks/useFABConfig'; import { setQuery, getQuery } from '@/lib/query-params'; import { __ } from '@/lib/i18n'; export default function {Module}Index() { useFABConfig('{module}'); // Enable FAB for create const initial = getQuery(); const [page, setPage] = useState(Number(initial.page ?? 1) || 1); const [status, setStatus] = useState(initial.status || undefined); const [searchQuery, setSearchQuery] = useState(''); const [selectedIds, setSelectedIds] = useState([]); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const perPage = 20; // Sync URL params React.useEffect(() => { setQuery({ page, status }); }, [page, status]); // Fetch data const q = useQuery({ queryKey: ['{module}', { page, perPage, status }], queryFn: () => api.get('/{module}', { page, per_page: perPage, status }), placeholderData: keepPreviousData, }); const data = q.data as undefined | { rows: any[]; total: number }; // Filter by search const filteredItems = React.useMemo(() => { const rows = data?.rows; if (!rows) return []; if (!searchQuery.trim()) return rows; const query = searchQuery.toLowerCase(); return rows.filter((item: any) => item.name?.toLowerCase().includes(query) || item.id?.toString().includes(query) ); }, [data, searchQuery]); // Bulk delete const deleteMutation = useMutation({ mutationFn: async (ids: number[]) => { const results = await Promise.allSettled( ids.map(id => api.del(`/{module}/${id}`)) ); const failed = results.filter(r => r.status === 'rejected').length; return { total: ids.length, failed }; }, onSuccess: (result) => { const { total, failed } = result; if (failed === 0) { toast.success(__('Items deleted successfully')); } else if (failed < total) { toast.warning(__(`${total - failed} deleted, ${failed} failed`)); } else { toast.error(__('Failed to delete items')); } setSelectedIds([]); setShowDeleteDialog(false); q.refetch(); }, }); // Checkbox handlers const allIds = filteredItems.map(r => r.id) || []; const allSelected = allIds.length > 0 && selectedIds.length === allIds.length; const toggleAll = () => { setSelectedIds(allSelected ? [] : allIds); }; const toggleRow = (id: number) => { setSelectedIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id] ); }; return (
{/* Desktop: Filters */}
{/* Filter controls */}
{/* Mobile: Search + Filter */}
setFilterSheetOpen(true)} />
{/* Desktop: Table */}
{filteredItems.map(item => ( ))}
{__('Name')} {__('Status')} {__('Actions')}
toggleRow(item.id)} /> {item.name} {__('View')}
{/* Mobile: Cards */}
{filteredItems.map(item => ( <{Module}Card key={item.id} item={item} /> ))}
{/* Delete Dialog */} {/* Dialog content */}
); } ``` ### πŸ“ Frontend Create Page Pattern **File:** `admin-spa/src/routes/{Module}/New.tsx` ```typescript import React, { useEffect, useRef } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { useNavigate } from 'react-router-dom'; import { usePageHeader } from '@/contexts/PageHeaderContext'; import { Button } from '@/components/ui/button'; import { useFABConfig } from '@/hooks/useFABConfig'; import { __ } from '@/lib/i18n'; import {Module}Form from './partials/{Module}Form'; export default function {Module}New() { const nav = useNavigate(); const qc = useQueryClient(); const { setPageHeader, clearPageHeader } = usePageHeader(); const formRef = useRef(null); useFABConfig('none'); // Hide FAB on create page const mutate = useMutation({ mutationFn: (data: any) => api.post('/{module}', data), onSuccess: (data) => { qc.invalidateQueries({ queryKey: ['{module}'] }); showSuccessToast(__('Item created successfully')); nav('/{module}'); }, onError: (error: any) => { showErrorToast(error); }, }); // Set page header useEffect(() => { const actions = (
); setPageHeader(__('New {Item}'), actions); return () => clearPageHeader(); }, [mutate.isPending, setPageHeader, clearPageHeader, nav]); return (
<{Module}Form mode="create" formRef={formRef} hideSubmitButton={true} onSubmit={(form) => mutate.mutate(form)} />
); } ``` ### ✏️ Frontend Edit Page Pattern **File:** `admin-spa/src/routes/{Module}/Edit.tsx` ```typescript import React, { useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { usePageHeader } from '@/contexts/PageHeaderContext'; import { Button } from '@/components/ui/button'; import { useFABConfig } from '@/hooks/useFABConfig'; import { __ } from '@/lib/i18n'; import {Module}Form from './partials/{Module}Form'; export default function {Module}Edit() { const { id } = useParams(); const itemId = Number(id); const nav = useNavigate(); const qc = useQueryClient(); const { setPageHeader, clearPageHeader } = usePageHeader(); const formRef = useRef(null); useFABConfig('none'); const itemQ = useQuery({ queryKey: ['{item}', itemId], enabled: Number.isFinite(itemId), queryFn: () => api.get(`/{module}/${itemId}`) }); const upd = useMutation({ mutationFn: (payload: any) => api.put(`/{module}/${itemId}`, payload), onSuccess: () => { qc.invalidateQueries({ queryKey: ['{module}'] }); qc.invalidateQueries({ queryKey: ['{item}', itemId] }); showSuccessToast(__('Item updated successfully')); nav(`/{module}/${itemId}`); }, onError: (error: any) => { showErrorToast(error); } }); const item = itemQ.data || {}; // Set page header useEffect(() => { const actions = (
); setPageHeader(__('Edit {Item}'), actions); return () => clearPageHeader(); }, [itemId, upd.isPending, setPageHeader, clearPageHeader, nav]); if (!Number.isFinite(itemId)) { return
{__('Invalid ID')}
; } if (itemQ.isLoading) { return ; } if (itemQ.isError) { return itemQ.refetch()} />; } return (
<{Module}Form mode="edit" initial={item} formRef={formRef} hideSubmitButton={true} onSubmit={(form) => upd.mutate(form)} />
); } ``` ### πŸ“‹ Checklist for New CRUD Module **Backend:** - [ ] Create `{Module}Controller.php` with all CRUD endpoints - [ ] Register routes in `Routes.php` - [ ] Add permission checks (`Permissions::check_admin`) - [ ] Implement pagination, filters, search - [ ] Return consistent response format - [ ] Add i18n for all error messages **Frontend:** - [ ] Create `routes/{Module}/index.tsx` (list view) - [ ] Create `routes/{Module}/New.tsx` (create) - [ ] Create `routes/{Module}/Edit.tsx` (edit) - [ ] Create `routes/{Module}/Detail.tsx` (optional view) - [ ] Create `components/{Module}Card.tsx` (mobile) - [ ] Create `partials/{Module}Form.tsx` (reusable form) - [ ] Add to navigation tree (`nav/tree.ts`) - [ ] Configure FAB (`useFABConfig`) - [ ] Add all i18n strings - [ ] Implement bulk delete - [ ] Add filters (status, date, search) - [ ] Add pagination - [ ] Test mobile responsive - [ ] Test error states - [ ] Test loading states **Testing:** - [ ] Create item - [ ] Edit item - [ ] Delete item - [ ] Bulk delete - [ ] Search - [ ] Filter by status - [ ] Pagination - [ ] Mobile view - [ ] Error handling - [ ] Permission checks --- ## 7. 🎨 Admin Interface Modes WooNooW provides **three distinct admin interface modes** to accommodate different workflows and user preferences: ### **1. Normal Mode (wp-admin)** - **Access:** `/wp-admin/admin.php?page=woonoow` - **Layout:** Traditional WordPress admin with WooNooW SPA in content area - **Use Case:** Standard WordPress admin workflow - **Features:** - WordPress admin bar and sidebar visible - Full WordPress admin functionality - WooNooW SPA integrated seamlessly - Settings submenu hidden (use WooCommerce settings) - **When to use:** When you need access to other WordPress admin features alongside WooNooW ### **2. Fullscreen Mode** - **Access:** Toggle button in WooNooW header - **Layout:** WooNooW SPA only (no WordPress chrome) - **Use Case:** Focused work sessions, order processing - **Features:** - Maximized workspace - Distraction-free interface - All WooNooW features accessible - Settings submenu hidden - **When to use:** When you want to focus exclusively on WooNooW tasks ### **3. Standalone Mode** ✨ - **Access:** `https://yoursite.com/admin` - **Layout:** Complete standalone application with custom login - **Use Case:** Quick daily access, mobile-friendly, bookmark-able - **Features:** - Custom login page (`/admin#/login`) - WordPress authentication integration - Settings submenu visible (SPA settings pages) - "WordPress" button to access wp-admin - "Logout" button in header - Admin bar link in wp-admin to standalone - Session persistence across modes - **When to use:** As your primary WooNooW interface, especially on mobile or for quick access ### **Mode Switching** - **From wp-admin to Standalone:** Click "WooNooW" in admin bar - **From Standalone to wp-admin:** Click "WordPress" button in header - **To Fullscreen:** Click fullscreen toggle in any mode - **Session persistence:** Login state is shared across all modes ### **Settings Submenu Behavior** - **Normal Mode:** No settings submenu (use WooCommerce settings in wp-admin) - **Fullscreen Mode:** No settings submenu - **Standalone Mode:** Full settings submenu visible with SPA pages **Implementation:** Settings submenu uses dynamic getter in `admin-spa/src/nav/tree.ts`: ```typescript get children() { const isStandalone = (window as any).WNW_CONFIG?.standaloneMode; if (!isStandalone) return []; return [ /* settings items */ ]; } ``` --- ## 8. πŸ€– AI Agent Collaboration Rules When using an AI IDE agent (ChatGPT, Claude, etc.): ### Step 1: Context Injection Always load: - `README.md` - `PROJECT_SOP.md` - The specific file(s) being edited ### Step 2: Editing Rules 1. All AI edits must be **idempotent** β€” never break structure or naming conventions. 2. Always follow PSR‑12 PHP standard and React code conventions. 3. When unsure about a design decision, **refer back to this S.O.P.** before guessing. 4. New files must be registered in the correct namespace path. 5. When editing React components, ensure build compatibility with Vite. ### Step 3: Communication AI agents must: - Explain each patch clearly. - Never auto‑remove code without reason. - Maintain English for all code comments, Markdown for docs. --- ## 7. πŸ“¦ Release Steps 1. Run all builds: ```bash npm run build && npm run pack ``` 2. Test in LocalWP with a sample Woo store. 3. Validate HPOS compatibility and order creation flow. 4. Push final `woonoow.zip` to release channel (Sejoli, member.dwindi.com, or manual upload). 5. Tag version using semantic versioning (e.g. `v0.2.0-beta`). --- ## 8. 🧭 Decision Hierarchy | Category | Decision Reference | |-----------|--------------------| | Code Style | Follow PSR‑12 (PHP) & Airbnb/React rules | | Architecture | PSR‑4 + modular single responsibility | | UI/UX | Modern minimal style, standardized using Tailwind + Shadcn UI. Recharts for data visualization. | | Icons | Use lucide-react via npm i lucide-react. Icons should match Shadcn UI guidelines. Always import directly (e.g. import { Package } from 'lucide-react'). Maintain consistent size (16–20px) and stroke width (1.5px). Use Tailwind classes for color states. | | **Navigation Pattern** | **CRUD pages MUST follow consistent back button navigation: New Order: Index ← New. Edit Order: Index ← Detail ← Edit. Back button always goes to parent page, not index. Use ArrowLeft icon from lucide-react. Toolbar format: `

Page Title

`** | | Compatibility | Must preserve Woo hooks unless explicitly replaced | | Performance | Async-first, no blocking mail or sync jobs | | **Email Policy** | **ALL `wp_mail()` calls MUST be delayed by 15+ seconds using Action Scheduler or wp-cron. Never send emails synchronously during API requests (create, update, status change). Use `OrdersController::schedule_order_email()` pattern.** | | Deployment | LocalWP β†’ Coolify β†’ Production | --- ## 9. 🧩 Future Extensions - **Addon Manager** (JSON feed + licensing integration). - **Admin Insights** (charts, sales analytics with React). - **Storefront SPA Theme Override** (optional full React mode). - **Developer SDK** for 3rd-party addon compatibility. --- ## 10. πŸ“œ License & Ownership All rights reserved to **Dwindi (dewe.dev)**. The WooNooW project may include GPL-compatible code portions for WordPress compliance. Redistribution without written consent is prohibited outside official licensing channels. ---