# Addon-Module Integration: Design Decisions

**Date**: December 26, 2025  
**Status**: 🎯 Decision Document

---

## 1. Dynamic Categories (RECOMMENDED)

### ❌ Problem with Static Categories
```php
// BAD: Empty categories if no modules use them
public static function get_categories() {
    return [
        'shipping' => 'Shipping & Fulfillment', // Empty if no shipping modules!
        'payments' => 'Payments & Checkout', // Empty if no payment modules!
    ];
}
```

### ✅ Solution: Dynamic Category Generation

```php
class ModuleRegistry {
    
    /**
     * Get categories dynamically from registered modules
     */
    public static function get_categories() {
        $all_modules = self::get_all_modules();
        $categories = [];
        
        // Extract unique categories from modules
        foreach ($all_modules as $module) {
            $cat = $module['category'] ?? 'other';
            if (!isset($categories[$cat])) {
                $categories[$cat] = self::get_category_label($cat);
            }
        }
        
        // Sort by predefined order (if exists), then alphabetically
        $order = ['marketing', 'customers', 'products', 'shipping', 'payments', 'analytics', 'other'];
        uksort($categories, function($a, $b) use ($order) {
            $pos_a = array_search($a, $order);
            $pos_b = array_search($b, $order);
            if ($pos_a === false) $pos_a = 999;
            if ($pos_b === false) $pos_b = 999;
            return $pos_a - $pos_b;
        });
        
        return $categories;
    }
    
    /**
     * Get human-readable label for category
     */
    private static function get_category_label($category) {
        $labels = [
            'marketing'  => __('Marketing & Sales', 'woonoow'),
            'customers'  => __('Customer Experience', 'woonoow'),
            'products'   => __('Products & Inventory', 'woonoow'),
            'shipping'   => __('Shipping & Fulfillment', 'woonoow'),
            'payments'   => __('Payments & Checkout', 'woonoow'),
            'analytics'  => __('Analytics & Reports', 'woonoow'),
            'other'      => __('Other Extensions', 'woonoow'),
        ];
        
        return $labels[$category] ?? ucfirst($category);
    }
    
    /**
     * Group modules by category
     */
    public static function get_grouped_modules() {
        $all_modules = self::get_all_modules();
        $grouped = [];
        
        foreach ($all_modules as $module) {
            $cat = $module['category'] ?? 'other';
            if (!isset($grouped[$cat])) {
                $grouped[$cat] = [];
            }
            $grouped[$cat][] = $module;
        }
        
        return $grouped;
    }
}
```

### Benefits
- ✅ No empty categories
- ✅ Addons can define custom categories
- ✅ Single registration point (module only)
- ✅ Auto-sorted by predefined order

---

## 2. Module Settings URL Pattern (RECOMMENDED)

### ❌ Problem with Custom URLs
```php
'settings_url' => '/settings/shipping/biteship', // Conflict risk!
'settings_url' => '/marketing/newsletter', // Inconsistent!
```

### ✅ Solution: Convention-Based Pattern

#### Option A: Standardized Pattern (RECOMMENDED)
```php
// Module registration - NO settings_url needed!
$addons['biteship-shipping'] = [
    'id'       => 'biteship-shipping',
    'name'     => 'Biteship Shipping',
    'has_settings' => true, // Just a flag!
];

// Auto-generated URL pattern:
// /settings/modules/{module_id}
// Example: /settings/modules/biteship-shipping
```

#### Backend: Auto Route Registration
```php
class ModuleRegistry {
    
    /**
     * Register module settings routes automatically
     */
    public static function register_settings_routes() {
        $modules = self::get_all_modules();
        
        foreach ($modules as $module) {
            if (empty($module['has_settings'])) continue;
            
            // Auto-register route: /settings/modules/{module_id}
            add_filter('woonoow/spa_routes', function($routes) use ($module) {
                $routes[] = [
                    'path'          => "/settings/modules/{$module['id']}",
                    'component_url' => $module['settings_component'] ?? null,
                    'title'         => sprintf(__('%s Settings', 'woonoow'), $module['label']),
                ];
                return $routes;
            });
        }
    }
}
```

#### Frontend: Automatic Navigation
```tsx
// Modules.tsx - Gear icon auto-links
{module.has_settings && module.enabled && (
  <Button
    variant="ghost"
    size="icon"
    onClick={() => navigate(`/settings/modules/${module.id}`)}
  >
    <Settings className="h-4 w-4" />
  </Button>
)}
```

### Benefits
- ✅ No URL conflicts (enforced pattern)
- ✅ Consistent navigation
- ✅ Simpler addon registration
- ✅ Auto-generated breadcrumbs

---

## 3. Form Builder vs Custom HTML (HYBRID APPROACH)

### ✅ Recommended: Provide Both Options

#### Option A: Schema-Based Form Builder (For Simple Settings)
```php
// Addon defines settings schema
add_filter('woonoow/module_settings_schema', function($schemas) {
    $schemas['biteship-shipping'] = [
        'api_key' => [
            'type'        => 'text',
            'label'       => 'API Key',
            'description' => 'Your Biteship API key',
            'required'    => true,
        ],
        'enable_tracking' => [
            'type'    => 'toggle',
            'label'   => 'Enable Tracking',
            'default' => true,
        ],
        'default_courier' => [
            'type'    => 'select',
            'label'   => 'Default Courier',
            'options' => [
                'jne'     => 'JNE',
                'jnt'     => 'J&T Express',
                'sicepat' => 'SiCepat',
            ],
        ],
    ];
    return $schemas;
});
```

**Auto-rendered form** - No React needed!

#### Option B: Custom React Component (For Complex Settings)
```php
// Addon provides custom React component
add_filter('woonoow/addon_registry', function($addons) {
    $addons['biteship-shipping'] = [
        'id'                 => 'biteship-shipping',
        'has_settings'       => true,
        'settings_component' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
    ];
    return $addons;
});
```

**Full control** - Custom React UI

### Implementation

```php
class ModuleSettingsRenderer {
    
    /**
     * Render settings page
     */
    public static function render($module_id) {
        $module = ModuleRegistry::get_module($module_id);
        
        // Option 1: Has custom component
        if (!empty($module['settings_component'])) {
            return self::render_custom_component($module);
        }
        
        // Option 2: Has schema - auto-generate form
        $schema = apply_filters('woonoow/module_settings_schema', []);
        if (isset($schema[$module_id])) {
            return self::render_schema_form($module_id, $schema[$module_id]);
        }
        
        // Option 3: No settings
        return ['error' => 'No settings available'];
    }
}
```

### Benefits
- ✅ Simple addons use schema (no React needed)
- ✅ Complex addons use custom components
- ✅ Consistent data persistence for both
- ✅ Gradual complexity curve

---

## 4. Settings Data Persistence (STANDARDIZED)

### ✅ Recommended: Unified Settings API

#### Backend: Automatic Persistence
```php
class ModuleSettingsController extends WP_REST_Controller {
    
    /**
     * GET /woonoow/v1/modules/{module_id}/settings
     */
    public function get_settings($request) {
        $module_id = $request['module_id'];
        $settings = get_option("woonoow_module_{$module_id}_settings", []);
        
        // Apply defaults from schema
        $schema = apply_filters('woonoow/module_settings_schema', []);
        if (isset($schema[$module_id])) {
            $settings = wp_parse_args($settings, self::get_defaults($schema[$module_id]));
        }
        
        return rest_ensure_response($settings);
    }
    
    /**
     * POST /woonoow/v1/modules/{module_id}/settings
     */
    public function update_settings($request) {
        $module_id = $request['module_id'];
        $new_settings = $request->get_json_params();
        
        // Validate against schema
        $schema = apply_filters('woonoow/module_settings_schema', []);
        if (isset($schema[$module_id])) {
            $validated = self::validate_settings($new_settings, $schema[$module_id]);
            if (is_wp_error($validated)) {
                return $validated;
            }
            $new_settings = $validated;
        }
        
        // Save
        update_option("woonoow_module_{$module_id}_settings", $new_settings);
        
        // Allow addons to react
        do_action("woonoow/module_settings_updated/{$module_id}", $new_settings);
        
        return rest_ensure_response(['success' => true]);
    }
}
```

#### Frontend: Unified Hook
```tsx
// useModuleSettings.ts
export function useModuleSettings(moduleId: string) {
  const queryClient = useQueryClient();
  
  const { data: settings, isLoading } = useQuery({
    queryKey: ['module-settings', moduleId],
    queryFn: async () => {
      const response = await api.get(`/modules/${moduleId}/settings`);
      return response;
    },
  });
  
  const updateSettings = useMutation({
    mutationFn: async (newSettings: any) => {
      return api.post(`/modules/${moduleId}/settings`, newSettings);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['module-settings', moduleId] });
      toast.success('Settings saved');
    },
  });
  
  return { settings, isLoading, updateSettings };
}
```

#### Addon Usage
```tsx
// Custom settings component
export default function BiteshipSettings() {
  const { settings, updateSettings } = useModuleSettings('biteship-shipping');
  
  return (
    <SettingsLayout title="Biteship Settings">
      <SettingsCard>
        <Input
          label="API Key"
          value={settings?.api_key || ''}
          onChange={(e) => updateSettings.mutate({ api_key: e.target.value })}
        />
      </SettingsCard>
    </SettingsLayout>
  );
}
```

### Benefits
- ✅ Consistent storage pattern: `woonoow_module_{id}_settings`
- ✅ Automatic validation (if schema provided)
- ✅ React hook for easy access
- ✅ Action hooks for addon logic

---

## 5. React Extension Pattern (DOCUMENTED)

### ✅ Solution: Window API + Build Externals

#### WooNooW Core Exposes React
```typescript
// admin-spa/src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { useQuery, useMutation } from '@tanstack/react-query';

// Expose for addons
window.WooNooW = {
  React,
  ReactDOM,
  hooks: {
    useQuery,
    useMutation,
    useModuleSettings, // Our custom hook!
  },
  components: {
    SettingsLayout,
    SettingsCard,
    Button,
    Input,
    Select,
    Switch,
    // ... all shadcn components
  },
  utils: {
    api,
    toast,
  },
};
```

#### Addon Development
```typescript
// addon/src/Settings.tsx
const { React, hooks, components, utils } = window.WooNooW;
const { useModuleSettings } = hooks;
const { SettingsLayout, SettingsCard, Input, Button } = components;
const { toast } = utils;

export default function BiteshipSettings() {
  const { settings, updateSettings } = useModuleSettings('biteship-shipping');
  const [apiKey, setApiKey] = React.useState(settings?.api_key || '');
  
  const handleSave = () => {
    updateSettings.mutate({ api_key: apiKey });
  };
  
  return React.createElement(SettingsLayout, { title: 'Biteship Settings' },
    React.createElement(SettingsCard, null,
      React.createElement(Input, {
        label: 'API Key',
        value: apiKey,
        onChange: (e) => setApiKey(e.target.value),
      }),
      React.createElement(Button, { onClick: handleSave }, 'Save')
    )
  );
}
```

#### With JSX (Build Required)
```tsx
// addon/src/Settings.tsx
const { React, hooks, components } = window.WooNooW;
const { useModuleSettings } = hooks;
const { SettingsLayout, SettingsCard, Input, Button } = components;

export default function BiteshipSettings() {
  const { settings, updateSettings } = useModuleSettings('biteship-shipping');
  
  return (
    <SettingsLayout title="Biteship Settings">
      <SettingsCard>
        <Input
          label="API Key"
          value={settings?.api_key || ''}
          onChange={(e) => updateSettings.mutate({ api_key: e.target.value })}
        />
      </SettingsCard>
    </SettingsLayout>
  );
}
```

```javascript
// vite.config.js
export default {
  build: {
    lib: {
      entry: 'src/Settings.tsx',
      formats: ['es'],
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'window.WooNooW.React',
          'react-dom': 'window.WooNooW.ReactDOM',
        },
      },
    },
  },
};
```

### Benefits
- ✅ Addons don't bundle React (use ours)
- ✅ Access to all WooNooW components
- ✅ Consistent UI automatically
- ✅ Type safety with TypeScript

---

## 6. Newsletter as Addon Example (RECOMMENDED)

### ✅ Yes, Refactor Newsletter as Built-in Addon

#### Why This is Valuable

1. **Dogfooding** - We use our own addon system
2. **Example** - Best reference for addon developers
3. **Consistency** - Newsletter follows same pattern as external addons
4. **Testing** - Proves the system works

#### Proposed Structure

```
includes/
  Modules/
    Newsletter/
      NewsletterModule.php        # Module registration
      NewsletterController.php    # API endpoints (moved from Api/)
      NewsletterSettings.php      # Settings schema
      
admin-spa/src/modules/
  Newsletter/
    Settings.tsx                  # Settings page
    Subscribers.tsx               # Subscribers page
    index.ts                      # Module exports
```

#### Registration Pattern
```php
// includes/Modules/Newsletter/NewsletterModule.php
class NewsletterModule {
    
    public static function register() {
        // Register as module
        add_filter('woonoow/builtin_modules', function($modules) {
            $modules['newsletter'] = [
                'id'                 => 'newsletter',
                'label'              => __('Newsletter', 'woonoow'),
                'description'        => __('Email newsletter subscriptions', 'woonoow'),
                'category'           => 'marketing',
                'icon'               => 'mail',
                'default_enabled'    => true,
                'has_settings'       => true,
                'settings_component' => self::get_settings_url(),
            ];
            return $modules;
        });
        
        // Register routes (only if enabled)
        if (ModuleRegistry::is_enabled('newsletter')) {
            self::register_routes();
        }
    }
    
    private static function register_routes() {
        // Settings route
        add_filter('woonoow/spa_routes', function($routes) {
            $routes[] = [
                'path'          => '/settings/modules/newsletter',
                'component_url' => plugins_url('admin-spa/dist/modules/Newsletter/Settings.js', WOONOOW_FILE),
            ];
            return $routes;
        });
        
        // Subscribers route
        add_filter('woonoow/spa_routes', function($routes) {
            $routes[] = [
                'path'          => '/marketing/newsletter',
                'component_url' => plugins_url('admin-spa/dist/modules/Newsletter/Subscribers.js', WOONOOW_FILE),
            ];
            return $routes;
        });
    }
}
```

### Benefits
- ✅ Newsletter becomes reference implementation
- ✅ Proves addon system works for complex modules
- ✅ Shows best practices
- ✅ Easier to maintain (follows pattern)

---

## Summary of Decisions

| # | Question | Decision | Rationale |
|---|----------|----------|-----------|
| 1 | Categories | **Dynamic from modules** | No empty categories, single registration |
| 2 | Settings URL | **Pattern: `/settings/modules/{id}`** | No conflicts, consistent, auto-generated |
| 3 | Form Builder | **Hybrid: Schema + Custom** | Simple for basic, flexible for complex |
| 4 | Data Persistence | **Unified API + Hook** | Consistent storage, easy access |
| 5 | React Extension | **Window API + Externals** | No bundling, access to components |
| 6 | Newsletter Refactor | **Yes, as example** | Dogfooding, reference implementation |

---

## Implementation Order

### Phase 1: Foundation
1. ✅ Dynamic category generation
2. ✅ Standardized settings URL pattern
3. ✅ Module settings API endpoints
4. ✅ `useModuleSettings` hook

### Phase 2: Form System
1. ✅ Schema-based form renderer
2. ✅ Custom component loader
3. ✅ Settings validation

### Phase 3: UI Enhancement
1. ✅ Search input on Modules page
2. ✅ Category filter pills
3. ✅ Gear icon with auto-routing

### Phase 4: Example
1. ✅ Refactor Newsletter as built-in addon
2. ✅ Document pattern
3. ✅ Create external addon example (Biteship)

---

## Next Steps

**Ready to implement?** We have clear decisions on all 6 questions. Should we:

1. Start with Phase 1 (Foundation)?
2. Create the schema-based form system first?
3. Refactor Newsletter as proof-of-concept?

**Your call!** All design decisions are documented and justified.
