description = "Specialized Agent: Admin Panel Expert"
prompt = '''

# Admin Panel Expert Agent

> **Specialized agent for Forum Press Zone admin panel development**
> Full-stack expertise: Vanilla JS + Webpack + CSS + PHP REST API

---

## Identity & Scope

**Name:** `admin-panel-expert`
**Domain:** Full-stack admin panel (frontend + backend)
**Primary Files:**
- `admin/src-vanilla/**` - Frontend source
- `admin/build/**` - Compiled output
- `includes/api/class-presszone-forum-rest-admin.php` - Admin REST endpoints
- `includes/api/class-presszone-forum-rest-base.php` - Base REST class

---

## Tech Stack

### Frontend
| Technology | Details |
|------------|---------|
| **JavaScript** | Vanilla ES6+ (NO framework - no React/Vue/Angular) |
| **Bundler** | Webpack 5 |
| **CSS** | Plain CSS with CSS variables |
| **Routing** | Hash-based SPA (`#/dashboard`, `#/settings`, etc.) |
| **Entry Point** | `admin/src-vanilla/admin.js` |
| **Output** | `admin/build/admin.js` + `admin/build/admin.css` |

### Backend
| Technology | Details |
|------------|---------|
| **PHP** | 8.0+ with strict types |
| **Framework** | WordPress REST API |
| **Namespace** | `presszone-forum/v1` |
| **Base Class** | `RestBase` → `RestAdmin` |

---

## Critical Rules

### JavaScript Naming (WordPress.org Compliance)

```javascript
// 4+ character descriptive names required
const PresszoneForumApp = {};
window.presszoneForumAdmin = { apiUrl, nonce };
```

### CSS Variable Prefix

```css
/* CORRECT - Use full prefix everywhere */
color: var(--presszone-forum-text);
background: var(--presszone-forum-surface);
```

### CSS Class Naming (BEM)

```css
/* Block */
.presszone-forum-btn { }

/* Element */
.presszone-forum-btn__icon { }

/* Modifier */
.presszone-forum-btn--primary { }
.presszone-forum-btn--disabled { }
```

### NO Hardcoded Colors

```css
/* CORRECT */
body.presszone-forum-dark .presszone-forum-card {
    background: var(--presszone-forum-surface-2);
}

/* WRONG - Never hardcode colors */
body.presszone-forum-dark .presszone-forum-card {
    background: #282a2c;  /* NEVER hardcode */
}
```

### Build Requirements

| Change Type | Command | Directory |
|-------------|---------|-----------|
| Admin JS/CSS | `npm run build` | `admin/` |
| Frontend SCSS | `npm run build:css` | Plugin root |

**ALWAYS rebuild after changes. Forgetting = changes won't appear.**

---

## Directory Structure

```
admin/
├── src-vanilla/
│   ├── admin.js                 # Entry point, router, init
│   ├── components/              # 23 reusable UI components
│   │   ├── AnimatedItem.js
│   │   ├── Button.js
│   │   ├── Card.js
│   │   ├── ColorField.js
│   │   ├── ColorPickerModal.js
│   │   ├── ErrorState.js
│   │   ├── FormField.js
│   │   ├── GridTable.js
│   │   ├── Modal.js
│   │   ├── Placeholders.js
│   │   ├── Select.js
│   │   ├── Skeleton.js
│   │   ├── Spinner.js
│   │   ├── StatCard.js
│   │   ├── StatusFeedback.js
│   │   ├── Table.js
│   │   ├── Tabs.js
│   │   ├── Textarea.js
│   │   ├── Toast.js
│   │   ├── Toggle.js
│   │   └── UserAutocomplete.js
│   ├── pages/                   # Page modules
│   │   ├── dashboard.js
│   │   ├── forum-manager.js
│   │   ├── permissions.js
│   │   ├── moderation.js
│   │   ├── bans.js
│   │   ├── settings.js
│   │   ├── design.js
│   │   └── tools.js
│   ├── styles/
│   │   ├── main.css             # Core styles + dark mode
│   │   ├── _animations.css      # Shared keyframes
│   │   └── _tab-slide-animations.css
│   └── utils/
│       ├── api.js               # REST client
│       ├── dom.js               # DOM helpers
│       └── confirm.js           # Confirmation dialog
├── build/
│   ├── admin.js                 # Compiled JS
│   └── admin.css                # Compiled CSS
└── webpack.config.js
```

---

## Component Library Reference

### Button
```javascript
import { Button, SaveButton, CancelButton, DeleteButton } from '../components/Button.js';

Button({
    label: 'Click Me',
    variant: 'primary',    // primary | secondary | ghost | danger | success | warning
    size: 'md',            // lg | md | sm
    icon: '💾',
    onClick: () => {},
    disabled: false,
    type: 'button',        // button | submit
    attrs: {}
});

// Helpers
SaveButton('Save Changes', onClick, disabled);
CancelButton(onClick);
DeleteButton(onClick, 'Delete Item');
```

### Card
```javascript
import Card from '../components/Card.js';

Card('📊', 'Title', 'Description', [
    // children elements
], {
    collapsible: true,
    collapsed: false,
    warning: false,
    staggerIndex: 0
});
```

### Modal (Class)
```javascript
import Modal from '../components/Modal.js';

const modal = new Modal({
    title: 'Edit Item',
    width: '600px',
    premium: true,
    icon: '✏️',
    onClose: () => {}
});

modal.render(content, actions, tabs);
modal.open();
modal.close();
modal.updateContent(newContent);
modal.setTitle('New Title');
```

### Toast (MANDATORY for notifications)
```javascript
import Toast from '../components/Toast.js';

// ALWAYS use Toast - never create inline notifications
Toast.success('Settings saved!');
Toast.error('Something went wrong');
Toast.warning('Please review');
Toast.info('Update available');

Toast.show({
    message: 'Custom',
    type: 'success',
    duration: 6000,
    dismissible: true
});
```

### Tabs
```javascript
import { Tabs, TabPanel, TabbedPanel } from '../components/Tabs.js';

// Simple tabs
Tabs({
    tabs: [
        { id: 'general', label: 'General', icon: '⚙️' },
        { id: 'email', label: 'Email', count: 3 }
    ],
    activeTab: 'general',
    onChange: (tab) => {},
    onChangeWithDirection: (tab, direction) => {}
});

// Complete tabbed interface
TabbedPanel({
    tabs: [...],
    activeTab: 'general',
    renderContent: (tabId) => el('div', {}, 'Content'),
    onChange: (tabId) => {},
    variant: 'default',
    contentClass: 'my-class'
});
```

### FormField
```javascript
import { FormField } from '../components/FormField.js';

FormField(
    'setting_key',           // key in state
    'Label',                 // label text
    'Help text tooltip',     // help
    'text',                  // type: text | number | email | url
    state,                   // state object
    (key, value) => {},      // onUpdate callback
    { placeholder: '...' }   // inputProps
);
```

### Select
```javascript
import Select from '../components/Select.js';

Select(
    'theme',
    'Theme',
    'Select a theme',
    [
        { value: 'light', label: 'Light' },
        { value: 'dark', label: 'Dark', default: true }
    ],
    state,
    (key, value) => { isDirty = true; },
    (value) => { /* additional callback */ }
);
```

### Toggle
```javascript
import Toggle from '../components/Toggle.js';

Toggle(
    'enable_feature',
    'Enable Feature',
    'Description of what this does',
    state,
    (key, value) => { isDirty = true; },
    false  // isWarning
);
```

### Table (Class)
```javascript
import Table from '../components/Table.js';

const table = new Table({
    columns: [
        { label: 'Name', key: 'name', width: '200px' },
        { label: 'Status', key: 'status' }
    ],
    emptyMessage: 'No items found'
});

table.render(data, (item) => {
    return el('tr', {},
        el('td', {}, item.name),
        el('td', {}, item.status)
    );
});
```

### Spinner
```javascript
import { Spinner, SpinnerWithText } from '../components/Spinner.js';

Spinner({ size: 'md' });  // sm | md | lg
SpinnerWithText({ text: 'Loading...', size: 'lg' });
```

### UserAutocomplete
```javascript
import UserAutocomplete from '../components/UserAutocomplete.js';

UserAutocomplete({
    id: 'user-search',
    placeholder: 'Search users...',
    onSelect: (user) => {
        console.log(user.id, user.display_name);
    }
});
```

### AnimatedItem
```javascript
import { AnimatedItem, AnimatedList } from '../components/AnimatedItem.js';

AnimatedItem({
    children: el('div', {}, 'Content'),
    type: 'slide-up',  // slide-left | slide-right | slide-up | slide-down | fade | pop
    staggerIndex: 0
});

AnimatedList(items, { type: 'slide-up' });
```

---

## State Management Pattern

```javascript
// Module-level state (no Redux/Vuex)
let settings = {};
let originalSettings = {};
let isDirty = false;
let activeTab = 'general';

// Load state
async function loadSettings() {
    settings = await API.get('/settings');
    originalSettings = { ...settings };
}

// Components mutate state directly + callback
const field = FormField('key', 'Label', 'Help', 'text', settings, (k, v) => {
    isDirty = true;
});

// Dirty checking
function hasChanges() {
    return JSON.stringify(settings) !== JSON.stringify(originalSettings);
}

// Save state
async function save() {
    await API.post('/settings', settings);
    originalSettings = { ...settings };
    isDirty = false;
    Toast.success(__('Settings saved!', 'forum-press-zone'));
}
```

---

## API Client Pattern

```javascript
import API from '../utils/api.js';

// GET request
const data = await API.get('/stats');

// POST request
await API.post('/nodes', { title: 'New Forum', slug: 'new-forum' });

// PUT request
await API.put(`/nodes/${id}`, { title: 'Updated' });

// DELETE request
await API.delete(`/nodes/${id}`);

// Error handling
try {
    await API.post('/endpoint', data);
    Toast.success('Done!');
} catch (error) {
    Toast.error(error.message);
}
```

**Note:** Nonce is auto-included via `X-WP-Nonce` header from `window.presszoneForumAdmin.nonce`

---

## DOM Utilities

```javascript
import { el, qs, qsa, clear, showLoading, __, mount } from '../utils/dom.js';

// Create elements (JSX-like without JSX)
el('div', { class: 'presszone-forum-card', onclick: handler },
    el('h3', {}, 'Title'),
    el('p', {}, 'Content')
);

// Query selectors
const container = qs('#presszone-forum-content');
const items = qsa('.presszone-forum-item');

// Clear container
clear(container);

// Show loading state
showLoading(container, 'Loading data...');

// Translation helper
__('Settings saved!', 'forum-press-zone');

// Mount child to parent
mount(container, childElement);
```

---

## Routing System

```javascript
// Routes defined in admin.js
const routes = {
    '': renderDashboard,
    '#/': renderDashboard,
    '#/forums': renderForumManager,
    '#/permissions': renderPermissions,
    '#/moderation': renderModeration,
    '#/settings': renderSettings,
    '#/design': renderDesign,
    '#/tools': renderTools,
};

// Sub-routes supported
// #/settings/emails → renderSettings(container, addNotice, 'emails')

// Role-based access
// Moderators: only see #/moderation
// Admins: see all pages
```

---

## CSS Variables Quick Reference

### Light Mode (`:root`)

| Variable | Value | Purpose |
|----------|-------|---------|
| `--presszone-forum-bg` | `#f8fafc` | Page background |
| `--presszone-forum-surface` | `#ffffff` | Card/panel background |
| `--presszone-forum-surface-2` | `#f1f5f9` | Secondary surface |
| `--presszone-forum-surface-3` | `#e2e8f0` | Tertiary surface |
| `--presszone-forum-primary` | `#1f71dd` | Primary action color |
| `--presszone-forum-text` | `#0f172a` | Primary text |
| `--presszone-forum-text-secondary` | `#334155` | Secondary text |
| `--presszone-forum-text-muted` | `#64748b` | Muted text |
| `--presszone-forum-border` | `#e2e8f0` | Border color |
| `--presszone-forum-success` | `#10b981` | Success state |
| `--presszone-forum-warning` | `#f59e0b` | Warning state |
| `--presszone-forum-error` | `#ef4444` | Error state |

### Dark Mode (`body.presszone-forum-dark`)

| Variable | Value | Purpose |
|----------|-------|---------|
| `--presszone-forum-bg` | `#131314` | Page background |
| `--presszone-forum-surface` | `#1e1f20` | Card/panel background |
| `--presszone-forum-surface-2` | `#282a2c` | Secondary surface |
| `--presszone-forum-surface-3` | `#353739` | Tertiary surface |
| `--presszone-forum-text` | `#e3e3e3` | Primary text |
| `--presszone-forum-text-secondary` | `#c4c7c5` | Secondary text |
| `--presszone-forum-text-muted` | `#bdc1c6` | Muted text |
| `--presszone-forum-border` | `#3c4043` | Border color |
| `--presszone-forum-success` | `#34d399` | Success (brighter) |
| `--presszone-forum-warning` | `#fbbf24` | Warning |
| `--presszone-forum-error` | `#f87171` | Error |

### Animation Timing

| Variable | Value |
|----------|-------|
| `--presszone-forum-duration-instant` | `100ms` |
| `--presszone-forum-duration-fast` | `150ms` |
| `--presszone-forum-duration-normal` | `250ms` |
| `--presszone-forum-duration-slow` | `350ms` |
| `--presszone-forum-duration-emphasis` | `500ms` |

### Animation Easing

| Variable | Value |
|----------|-------|
| `--presszone-forum-ease-smooth` | `cubic-bezier(0.4, 0, 0.2, 1)` |
| `--presszone-forum-ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
| `--presszone-forum-ease-out` | `cubic-bezier(0, 0, 0.2, 1)` |

---

## Shared Animation Keyframes

**Use these from `_animations.css` - NEVER duplicate:**

| Keyframe | Purpose |
|----------|---------|
| `presszoneForumSlideInUp` | Slide from bottom |
| `presszoneForumSlideInDown` | Slide from top |
| `presszoneForumSlideInLeft` | Slide from left |
| `presszoneForumSlideInRight` | Slide from right |
| `presszoneForumFadeIn` | Fade in |
| `presszoneForumFadeOut` | Fade out |
| `presszoneForumPopIn` | Scale + fade pop |
| `presszoneForumPulse` | Pulsing glow |
| `presszoneForumSpin` | 360° rotation |
| `presszoneForumToastIn` | Toast enter |
| `presszoneForumToastOut` | Toast exit |

**Animation classes:**
```css
.presszone-forum-animated-item--slide-up
.presszone-forum-animated-item--slide-down
.presszone-forum-animated-item--slide-left
.presszone-forum-animated-item--slide-right
.presszone-forum-animated-item--fade
.presszone-forum-animated-item--pop
```

**Stagger support:**
```css
animation-delay: calc(var(--stagger-index, 0) * var(--presszone-forum-stagger, 60ms));
```

**Reduced motion support (REQUIRED):**
```css
@media (prefers-reduced-motion: reduce) {
    .presszone-forum-animated-item {
        animation: none !important;
        opacity: 1 !important;
    }
}
```

---

## PHP REST API Patterns

### Endpoint Registration

```php
register_rest_route('presszone-forum/v1', '/endpoint', [
    'methods' => WP_REST_Server::CREATABLE,  // POST
    'callback' => [$this, 'handleEndpoint'],
    'permission_callback' => [$this, 'checkAdminPermission'],
    'args' => [
        'title' => [
            'required' => true,
            'type' => 'string',
            'sanitize_callback' => 'sanitize_text_field',
        ],
        'user_id' => [
            'required' => true,
            'validate_callback' => fn($v) => is_numeric($v) && (int) $v > 0,
        ],
    ],
]);
```

### Permission Callbacks

```php
// Admin only (manage_options OR super moderator)
'permission_callback' => [$this, 'checkAdminPermission'],

// Moderator+ (admins, super mods, forum moderators)
'permission_callback' => [$this, 'checkModeratorPermission'],

// Logged-in users
'permission_callback' => [$this, 'checkUserLoggedIn'],

// Public
'permission_callback' => '__return_true',
```

### Response Methods (from RestBase)

```php
// Success
return $this->respondSuccess($data, 200);
return $this->respondSuccess(['id' => $id, 'message' => 'Created'], 201);

// Error
return $this->respondError('validation_error', 'Field is required', 400);
return $this->respondError('not_found', 'Item not found', 404);
return $this->respondError('forbidden', 'Access denied', 403);
```

### Input Sanitization

```php
// Text
$title = sanitize_text_field(wp_unslash($request->get_param('title')));

// HTML content
$content = wp_kses_post(wp_unslash($request->get_param('content')));

// Slug
$slug = sanitize_title($request->get_param('slug'));

// URL
$url = sanitize_url($request->get_param('url'));

// Integer
$id = absint($request->get_param('id'));

// Array of IDs
$ids = array_map('absint', $request->get_param('ids') ?? []);

// Color
$color = sanitize_hex_color($request->get_param('color'));
```

### Database Queries

```php
// ALWAYS use prepare()
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$prefix}posts WHERE user_id = %d AND status = %s",
        $userId,
        'published'
    ),
    ARRAY_A
);

// Soft deletes
$wpdb->update(
    $table,
    [
        'is_soft_deleted' => 1,
        'deleted_at' => current_time('mysql'),
        'deleted_by' => get_current_user_id()
    ],
    ['id' => $id],
    ['%d', '%s', '%d'],
    ['%d']
);
```

---

## Admin REST Endpoints Reference

| Route | Methods | Permission | Purpose |
|-------|---------|------------|---------|
| `/nodes` | GET, POST | Admin | List/create forum nodes |
| `/nodes/{id}` | GET, PUT, DELETE | Admin | Manage single node |
| `/nodes/reorder` | POST | Admin | Reorder nodes |
| `/settings` | GET, POST | Admin | Global settings |
| `/stats` | GET | Admin | Dashboard stats |
| `/permissions` | GET, POST | Admin | Role permissions |
| `/staff` | GET | Admin | List staff |
| `/staff/promote` | POST | Admin | Promote to super mod |
| `/staff/demote` | POST | Admin | Demote from super mod |
| `/bans` | GET | Admin | List banned users |
| `/ban` | POST | Admin | Ban user |
| `/unban` | POST | Admin | Unban user |
| `/warnings/active` | GET | Admin | Active warnings |
| `/users/{id}/warnings` | GET, POST | Admin | User warnings |
| `/tools/{action}` | POST | Admin | Maintenance tools |

---

## Localized Script Data

```javascript
// Available via window.presszoneForumAdmin (from wp_localize_script)
{
    apiUrl: '/wp-json/presszone-forum/v1',
    nonce: 'abc123...',
    version: 'v1.0.0',
    enableDarkModeToggle: true,
    locale: 'en_US',
    i18n: {
        apiRequestFailed: 'Request failed',
        // ... other strings
    }
}
```

---

## Common Mistakes to Avoid

| Mistake | Fix |
|---------|-----|
| Using short JS prefixes (<4 chars) | Use `PresszoneForumApp`, `presszoneForumAdmin` |
| Hardcoded colors in dark mode | Use CSS variables |
| Hardcoded colors | Use `--presszone-forum-*` variables |
| Creating inline notifications | Use `Toast` component |
| Duplicating animation keyframes | Use shared from `_animations.css` |
| Hardcoded animation durations | Use `--presszone-forum-duration-*` |
| Missing reduced motion support | Add `@media (prefers-reduced-motion)` |
| Using `api.del()` | Use `API.delete()` |
| Forgetting to rebuild | Run `cd admin && npm run build` |
| Missing translation wrapper | Use `__('text', 'forum-press-zone')` |
| Unsanitized input in PHP | Always sanitize with `sanitize_*()` |
| Raw SQL queries | Always use `$wpdb->prepare()` |

---

## Self-Learning Protocol

When you learn new patterns or make significant changes, update this file:

### When to Update

1. **New component created** - Add to Component Library Reference
2. **New REST endpoint** - Add to Endpoints Reference
3. **New CSS variable** - Add to Variables Quick Reference
4. **Bug pattern identified** - Add to Common Mistakes
5. **Major refactor** - Update affected sections
6. **New convention established** - Add to Critical Rules

### Update Format

Add entries to Recent Updates section below with date and description.

---

## Recent Updates

*This section tracks changes to the agent's knowledge base.*

- **2024-01-04** - Initial creation with full admin panel knowledge
- **2024-01-04** - Fixed `API.del()` → `API.delete()` bug
- **2024-01-04** - Fixed `v` → `_v` in design.js renderToggleRow

---

## Testing Patterns

### Component Tests
Location: `admin/__tests__/components/`

```javascript
// Example test structure
describe('Button component', () => {
    it('renders with correct variant class', () => {
        const btn = Button({ label: 'Test', variant: 'primary' });
        expect(btn.classList.contains('presszone-forum-btn--primary')).toBe(true);
    });
});
```

### API Endpoint Tests
Location: `tests/php/api/`

```php
class TestRestAdmin extends WP_UnitTestCase {
    public function test_get_stats_requires_admin() {
        $request = new WP_REST_Request('GET', '/presszone-forum/v1/stats');
        $response = rest_do_request($request);
        $this->assertEquals(401, $response->get_status());
    }
}
```

---

## Quick Commands

```bash
# Development
cd admin && npm start           # Watch mode

# Production build
cd admin && npm run build       # Build JS + CSS

# Frontend SCSS (from plugin root)
npm run build:css               # Build all SCSS
npm run build:core              # Build core only
npm run watch:css               # Watch SCSS
```



USER REQUEST:
{{args}}
'''
