991 lines
31 KiB
Markdown
991 lines
31 KiB
Markdown
# 🧭 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
|
||
|
||
**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 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 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** | `<ErrorCard>` | 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** | `<ErrorMessage>` | 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 <ErrorCard
|
||
title="Failed to load data"
|
||
message={getPageLoadErrorMessage(query.error)}
|
||
onRetry={() => 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
|
||
<button>{__('Try again')}</button>
|
||
<h2>{sprintf(__('Order #%s'), order.number)}</h2>
|
||
|
||
// 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
|
||
<LoadingState />
|
||
|
||
// Custom message
|
||
<LoadingState message={__('Loading order...')} />
|
||
|
||
// Different sizes
|
||
<LoadingState size="sm" message={__('Saving...')} />
|
||
<LoadingState size="md" message={__('Loading...')} /> // default
|
||
<LoadingState size="lg" message={__('Processing...')} />
|
||
|
||
// Full screen overlay
|
||
<LoadingState fullScreen message={__('Loading...')} />
|
||
```
|
||
|
||
**2. PageLoadingState**
|
||
```typescript
|
||
import { PageLoadingState } from '@/components/LoadingState';
|
||
|
||
// For full page loads
|
||
if (isLoading) {
|
||
return <PageLoadingState message={__('Loading order...')} />;
|
||
}
|
||
```
|
||
|
||
**3. InlineLoadingState**
|
||
```typescript
|
||
import { InlineLoadingState } from '@/components/LoadingState';
|
||
|
||
// For inline loading within components
|
||
{isLoading && <InlineLoadingState message={__('Loading...')} />}
|
||
```
|
||
|
||
**4. CardLoadingSkeleton**
|
||
```typescript
|
||
import { CardLoadingSkeleton } from '@/components/LoadingState';
|
||
|
||
// For loading card content
|
||
{isLoading && <CardLoadingSkeleton />}
|
||
```
|
||
|
||
**5. TableLoadingSkeleton**
|
||
```typescript
|
||
import { TableLoadingSkeleton } from '@/components/LoadingState';
|
||
|
||
// For loading table rows
|
||
{isLoading && <TableLoadingSkeleton rows={10} />}
|
||
```
|
||
|
||
### Usage Guidelines
|
||
|
||
**Page-Level Loading:**
|
||
```typescript
|
||
// ✅ Good - Use PageLoadingState for full page loads
|
||
if (orderQ.isLoading || countriesQ.isLoading) {
|
||
return <PageLoadingState message={sprintf(__('Loading order #%s...'), orderId)} />;
|
||
}
|
||
|
||
// ❌ Bad - Don't use plain text
|
||
if (isLoading) {
|
||
return <div>Loading...</div>;
|
||
}
|
||
```
|
||
|
||
**Inline Loading:**
|
||
```typescript
|
||
// ✅ Good - Use InlineLoadingState for partial loads
|
||
{q.isLoading && <InlineLoadingState message={__('Loading order...')} />}
|
||
|
||
// ❌ Bad - Don't use custom spinners
|
||
{q.isLoading && <div><Loader2 className="animate-spin" /> Loading...</div>}
|
||
```
|
||
|
||
**Table Loading:**
|
||
```typescript
|
||
// ✅ Good - Use TableLoadingSkeleton for tables
|
||
{q.isLoading && <TableLoadingSkeleton rows={10} />}
|
||
|
||
// ❌ Bad - Don't show empty state while loading
|
||
{q.isLoading && <div>Loading data...</div>}
|
||
```
|
||
|
||
### Best Practices
|
||
|
||
1. **Always use i18n** - All loading messages must be translatable
|
||
```typescript
|
||
<LoadingState message={__('Loading...')} />
|
||
```
|
||
|
||
2. **Be specific** - Use descriptive messages
|
||
```typescript
|
||
// ✅ Good
|
||
<LoadingState message={sprintf(__('Loading order #%s...'), orderId)} />
|
||
|
||
// ❌ Bad
|
||
<LoadingState message="Loading..." />
|
||
```
|
||
|
||
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 ? <TableLoadingSkeleton rows={5} /> : <Table data={data} />}
|
||
```
|
||
|
||
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 <LoadingState message={sprintf(__('Loading order #%s...'), orderId)} />;
|
||
}
|
||
|
||
return <OrderForm ... />;
|
||
}
|
||
```
|
||
|
||
**Order Detail Page:**
|
||
```typescript
|
||
export default function OrderDetail() {
|
||
const q = useQuery({ ... });
|
||
|
||
return (
|
||
<div>
|
||
<h1>{__('Order Details')}</h1>
|
||
{q.isLoading && <InlineLoadingState message={__('Loading order...')} />}
|
||
{q.data && <OrderContent order={q.data} />}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Orders List:**
|
||
```typescript
|
||
export default function OrdersList() {
|
||
const q = useQuery({ ... });
|
||
|
||
return (
|
||
<table>
|
||
<thead>...</thead>
|
||
<tbody>
|
||
{q.isLoading && <TableLoadingSkeleton rows={10} />}
|
||
{q.data?.map(order => <OrderRow key={order.id} order={order} />)}
|
||
</tbody>
|
||
</table>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 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
|
||
<?php
|
||
namespace WooNooW\Compat;
|
||
|
||
class MyModuleRegistry {
|
||
const OPTION_KEY = 'wnw_my_module_data';
|
||
const VERSION = '1.0.0';
|
||
|
||
public static function init() {
|
||
add_action('plugins_loaded', [__CLASS__, 'collect_data'], 30);
|
||
add_action('activated_plugin', [__CLASS__, 'flush']);
|
||
add_action('deactivated_plugin', [__CLASS__, 'flush']);
|
||
}
|
||
|
||
public static function collect_data() {
|
||
$data = [];
|
||
|
||
/**
|
||
* Filter: woonoow/my_module/items
|
||
*
|
||
* Allows addons to register items with this module.
|
||
*
|
||
* @param array $data Array of item configurations
|
||
*
|
||
* Example:
|
||
* add_filter('woonoow/my_module/items', function($data) {
|
||
* $data['my-item'] = [
|
||
* 'id' => '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 (
|
||
<div>
|
||
{items.map(item => (
|
||
<div key={item.id}>{item.label}</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 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
|
||
<?php
|
||
/**
|
||
* Plugin Name: My Traditional Addon
|
||
*/
|
||
|
||
// Register addon
|
||
add_filter('woonoow/addon_registry', function($addons) {
|
||
$addons['my-addon'] = [
|
||
'id' => '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() {
|
||
?>
|
||
<div class="wrap">
|
||
<h1>My Traditional Addon</h1>
|
||
<p>Built with PHP, HTML, CSS, and vanilla JS!</p>
|
||
|
||
<script>
|
||
// Vanilla JavaScript works fine
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
console.log('My addon loaded!');
|
||
});
|
||
</script>
|
||
</div>
|
||
<?php
|
||
}
|
||
```
|
||
|
||
**This approach:**
|
||
- ✅ Works with WooNooW navigation
|
||
- ✅ No React knowledge required
|
||
- ✅ Uses standard WordPress admin pages
|
||
- ✅ Can use WordPress admin styles
|
||
- ✅ Can enqueue own CSS/JS
|
||
- ⚠️ Opens in separate page (not SPA)
|
||
|
||
#### **Approach 2: Vanilla JS Component (No React)**
|
||
|
||
For developers who want SPA integration without React:
|
||
|
||
```javascript
|
||
// dist/MyAddon.js - Vanilla JS module
|
||
export default function MyAddonPage(props) {
|
||
const container = document.createElement('div');
|
||
container.className = 'p-6';
|
||
container.innerHTML = `
|
||
<div class="rounded-lg border border-border p-6 bg-card">
|
||
<h2 class="text-xl font-semibold mb-2">My Addon</h2>
|
||
<p class="text-sm opacity-70">Built with vanilla JavaScript!</p>
|
||
<button id="my-button" class="px-4 py-2 bg-blue-500 text-white rounded">
|
||
Click Me
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
// 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 (
|
||
<div className="p-6">
|
||
<div className="rounded-lg border border-border p-6 bg-card">
|
||
<h2 className="text-xl font-semibold mb-2">My Addon</h2>
|
||
<p className="text-sm opacity-70">Built with React!</p>
|
||
<button
|
||
onClick={() => setCount(count + 1)}
|
||
className="px-4 py-2 bg-blue-500 text-white rounded"
|
||
>
|
||
Clicked {count} times
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
**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.
|
||
|
||
---
|
||
|
||
## 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: `<button onClick={() => nav('/parent/path')}><ArrowLeft /> Back</button> <h2>Page Title</h2>`** |
|
||
| 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.
|
||
|
||
--- |