# 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 Custom Properties - STRICTLY FORBIDDEN (ABSOLUTE BAN)

> **CRITICAL**: CSS custom properties (`--presszone-forum-*`) **STRICTLY FORBIDDEN** everywhere. No exceptions. No file. No reason.

```css
/* ❌ STRICTLY FORBIDDEN - CSS custom properties */
color: var(--presszone-forum-text);       /* NEVER */
background: var(--presszone-forum-surface); /* NEVER */
--presszone-forum-custom: #fff;            /* NEVER */
:root { --presszone-forum-bg: #fff; }     /* NEVER */

/* ✅ CORRECT - Admin panel uses plain CSS with explicit values */
.presszone-forum-card {
    background: #ffffff;
    color: #0f172a;
}

/* ✅ CORRECT - Dark mode with explicit overrides */
body.dark-mode .presszone-forum-card {
    background: #1e1f20;
    color: #e3e3e3;
}
```

**No CSS custom properties. No exceptions.**

### CSS Class Naming (BEM)

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

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

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

### Color Management in Admin CSS

```css
/* Admin panel uses plain CSS (not SCSS), so use explicit color values */

/* CORRECT - Light mode default */
.presszone-forum-card {
    background: #ffffff;
    color: #0f172a;
}

/* CORRECT - Dark mode override */
body.dark-mode .presszone-forum-card {
    background: #1e1f20;
    color: #e3e3e3;
}

/* WRONG - CSS custom properties (STRICTLY FORBIDDEN) */
.presszone-forum-card {
    background: var(--presszone-forum-surface);  /* NEVER */
}
```

### 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 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
```

---

## Color Reference (Admin Panel)

> **Note**: Admin panel uses plain CSS with explicit color values. CSS custom properties STRICTLY FORBIDDEN.

### Light Mode Colors

| Purpose | Hex Value | Usage |
|---------|-----------|-------|
| Page background | `#f8fafc` | Main admin background |
| Card/panel background | `#ffffff` | Cards, modals |
| Secondary surface | `#f1f5f9` | Hover states, alternating rows |
| Tertiary surface | `#e2e8f0` | Disabled states |
| Primary action | `#1f71dd` | Buttons, links |
| Primary text | `#0f172a` | Headings, body |
| Secondary text | `#334155` | Descriptions |
| Muted text | `#64748b` | Placeholders, hints |
| Border | `#e2e8f0` | Dividers, input borders |
| Success | `#10b981` | Success messages |
| Warning | `#f59e0b` | Warning messages |
| Error | `#ef4444` | Error messages |

### Dark Mode Colors (use with `body.dark-mode`)

| Purpose | Hex Value | Usage |
|---------|-----------|-------|
| Page background | `#131314` | Main admin background |
| Card/panel background | `#1e1f20` | Cards, modals |
| Secondary surface | `#282a2c` | Hover states |
| Tertiary surface | `#353739` | Disabled states |
| Primary text | `#e3e3e3` | Headings, body |
| Secondary text | `#c4c7c5` | Descriptions |
| Muted text | `#bdc1c6` | Placeholders |
| Border | `#3c4043` | Dividers |
| Success | `#34d399` | Success (brighter) |
| Warning | `#fbbf24` | Warning |
| Error | `#f87171` | Error |

### Animation Timing (use directly in CSS)

| Purpose | Value |
|---------|-------|
| Instant | `100ms` |
| Fast | `150ms` |
| Normal | `250ms` |
| Slow | `350ms` |
| Emphasis | `500ms` |

### Animation Easing (use directly in CSS)

| Purpose | Value |
|---------|-------|
| Smooth | `cubic-bezier(0.4, 0, 0.2, 1)` |
| Spring | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
| Out | `cubic-bezier(0, 0, 0.2, 1)` |

---

## Responsive Design

**Mobile-first for admin UI.**

### Rules
- Mobile styles default (no media query)
- Scale up with `min-width` media queries
- Nest media queries inside selectors they modify
- Admin uses vanilla CSS, not SCSS

### Example
```css
.presszone-forum-admin-card {
  padding: 1rem;
}

@media (min-width: 768px) {
  .presszone-forum-admin-card {
    padding: 2rem;
  }
}
```

---

## Layout Patterns

### Layout Selection
| Use Case | Solution |
|----------|----------|
| General layout | `display: flex` |
| Tabular data (lists, tables) | `display: grid` with `display: contents` rows |
| Sidebar + main | `display: flex` |

### Forbidden
- `<table>` elements - use CSS Grid
- `float` or `clearfix`
- Bootstrap or external CSS libraries

### Grid for Admin Tables
```css
.presszone-forum-admin-list {
  display: grid;
  grid-template-columns: 1fr 100px 100px auto;
}

.presszone-forum-admin-list__row {
  display: contents;
}

.presszone-forum-admin-list__cell {
  padding: 12px 16px;
  border-bottom: 1px solid #e2e8f0;
}

body.dark-mode .presszone-forum-admin-list__cell {
  border-bottom-color: #3c4043;
}
```

---

## Shared Animation Keyframes

**Use 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
/* Stagger delay is set via inline style attribute from JS */
/* Each item gets: style="animation-delay: Nms" where N = index * 60 */
animation-delay: 0ms;  /* Base, JS overrides per-item */
```

**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
    }
}
```

---

## Translation (i18n) Rules

### Text Domain: `'forum-press-zone'` - ALWAYS

```javascript
// JavaScript strings via __() helper from dom.js
import { __ } from '../utils/dom.js';

__('Settings saved!', 'forum-press-zone')
__('Error occurred', 'forum-press-zone')
```

### Translation Maintenance Rules

| Action | Requirement |
|--------|-------------|
| **Adding new UI string** | Add to all `.po` files in `languages/`, translate to all languages |
| **Removing UI string** | Remove from all `.po` files to avoid bloat |
| **Editing UI string** | Update in all `.po` files, retranslate |

### File Locations
- `languages/forum-press-zone-{locale}.po` - PHP strings
- `languages/forum-press-zone-{locale}-presszone-forum-admin-app.json` - Admin JS strings

---

## Common Mistakes to Avoid

| Mistake | Fix |
|---------|-----|
| **`var(--presszone-forum-*)`** | **STRICTLY FORBIDDEN - Never use CSS custom properties** |
| **`--presszone-forum-*:` definitions** | **STRICTLY FORBIDDEN - Never define CSS custom properties** |
| **`:root { }` blocks** | **STRICTLY FORBIDDEN - Never create CSS custom property blocks** |
| Short JS prefixes (<4 chars) | Use `PresszoneForumApp`, `presszoneForumAdmin` |
| Inconsistent dark mode colors | Use color reference table values |
| Inline notifications | Use `Toast` component |
| Duplicate animation keyframes | Use shared from `_animations.css` |
| Inconsistent animation durations | Use timing reference values (100ms, 150ms, etc.) |
| Missing reduced motion support | Add `@media (prefers-reduced-motion)` |
| `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 | Sanitize with `sanitize_*()` |
| Raw SQL queries | Use `$wpdb->prepare()` |
| `<table>` elements | NEVER - use `<div>` with CSS Grid/Flexbox |
| Class chaining `.class1.class2` | Single BEM class |
| Magic numbers | Use CSS variables |
| `float` or `clearfix` | Use Flexbox or Grid |
| `!important` (except WP overrides) | Increase specificity properly |
| Deep CSS nesting | Flatten with BEM naming |

---

## Self-Learning Protocol

New pattern or significant change → 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 with date + description.

---

## Recent Updates

Tracks knowledge base changes.

- **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

---

## 🔒 MANDATORY SECURITY & COMPLIANCE RULES

### WordPress.org Compliance (ZERO TOLERANCE)

#### JavaScript Naming - 4+ Characters REQUIRED
```javascript
// CORRECT - WordPress.org compliant (4+ chars)
const PresszoneForumApp = {};
window.presszoneForumAdmin = { apiUrl, nonce };

// FORBIDDEN - Will cause plugin rejection
const FPZ = {};      // 3 letters - TOO SHORT
const pz = {};       // 2 letters - TOO SHORT
```

#### REST API Security - MANDATORY
```javascript
// ALWAYS include nonce in API requests
const options = {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': window.presszoneForumAdmin.nonce  // CRITICAL
    },
    body: JSON.stringify(data)
};

// ALWAYS validate response status
if (!response.ok) {
    throw new Error(json.message || 'Request failed');
}
```

#### Input Validation - CRITICAL
```javascript
// ALWAYS sanitize user input before API calls
const sanitizedData = {
    title: data.title?.trim() || '',
    slug: data.slug?.replace(/[^a-z0-9-]/g, '') || '',
    user_id: parseInt(data.user_id) || 0
};

// NEVER trust user input directly
// BAD: { title: userInput }
// GOOD: { title: sanitizeText(userInput) }
```

### Security Rules - ABSOLUTE REQUIREMENTS

#### XSS Prevention
```javascript
// ALWAYS escape HTML content
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// Use in templates
const html = `<span>${escapeHtml(user.name)}</span>`;

// NEVER use innerHTML with user data
element.innerHTML = userContent;  // FORBIDDEN
element.textContent = userContent;  // CORRECT
```

#### Permission Validation
```javascript
// ALWAYS check user capabilities before sensitive operations
async function deleteNode(nodeId) {
    // Verify admin permission client-side (server validates too)
    if (!window.presszoneForumAdmin.canManageNodes) {
        Toast.error(__('Permission denied', 'forum-press-zone'));
        return;
    }
    
    try {
        await API.delete(`/nodes/${nodeId}`);
    } catch (error) {
        Toast.error(error.message);
    }
}
```

#### CSRF Protection
```javascript
// ALWAYS use WordPress nonce system
// Nonce automatically included via X-WP-Nonce header
// Server validates via permission_callback

// For forms, include nonce field
const form = el('form', {},
    el('input', { type: 'hidden', name: '_wpnonce', value: window.presszoneForumAdmin.nonce })
);
```

### Accessibility Rules - MANDATORY

#### Keyboard Navigation
```javascript
// ALWAYS add keyboard handlers to interactive elements
button.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        handleClick();
    }
});

// ALWAYS manage focus in modals
const modal = new Modal({
    onOpen: () => {
        // Focus first focusable element
        const firstFocusable = modal.element.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
        firstFocusable?.focus();
    }
});
```

#### ARIA Labels
```javascript
// ALWAYS add ARIA labels to icon buttons
const deleteBtn = Button({
    label: '🗑️',
    variant: 'danger',
    attrs: {
        'aria-label': __('Delete item', 'forum-press-zone'),
        'title': __('Delete item', 'forum-press-zone')
    }
});

// ALWAYS set aria-expanded on dropdowns
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
```

#### Screen Reader Support
```javascript
// ALWAYS provide text alternatives for visual indicators
const statusIcon = el('span', {
    class: 'presszone-forum-status-icon',
    'aria-label': status === 'active' ? __('Active', 'forum-press-zone') : __('Inactive', 'forum-press-zone')
}, status === 'active' ? '✅' : '❌');

// ALWAYS announce dynamic changes
function updateStatus(message) {
    const announcement = el('div', {
        'aria-live': 'polite',
        'aria-atomic': 'true',
        class: 'presszone-forum-sr-only'
    }, message);
    document.body.appendChild(announcement);
    setTimeout(() => announcement.remove(), 1000);
}
```

### Data Protection Rules

#### Sensitive Data Handling
```javascript
// NEVER log sensitive data
console.log(userData);  // FORBIDDEN if contains PII

// ALWAYS sanitize data for logging
console.log({
    action: 'user_updated',
    user_id: userData.id,  // ID only, no personal info
    timestamp: Date.now()
});

// NEVER store sensitive data in localStorage
localStorage.setItem('user_email', email);  // FORBIDDEN
localStorage.setItem('user_preferences', JSON.stringify(prefs));  // OK
```

#### API Response Handling
```javascript
// ALWAYS validate API response structure
async function fetchUserData(userId) {
    try {
        const response = await API.get(`/users/${userId}`);
        
        // Validate response structure
        if (!response.data || typeof response.data !== 'object') {
            throw new Error('Invalid response format');
        }
        
        return response.data;
    } catch (error) {
        // NEVER expose internal error details to users
        Toast.error(__('Failed to load user data', 'forum-press-zone'));
        console.error('API Error:', error);  // Log for debugging only
        return null;
    }
}
```

### Performance & Resource Rules

#### Memory Management
```javascript
// ALWAYS clean up event listeners
class ComponentManager {
    constructor() {
        this.eventListeners = [];
    }
    
    addListener(element, event, handler) {
        element.addEventListener(event, handler);
        this.eventListeners.push({ element, event, handler });
    }
    
    destroy() {
        // Clean up all listeners
        this.eventListeners.forEach(({ element, event, handler }) => {
            element.removeEventListener(event, handler);
        });
        this.eventListeners = [];
    }
}
```

#### Rate Limiting
```javascript
// ALWAYS implement debouncing for frequent operations
function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

// Use for search, auto-save, etc.
const debouncedSearch = debounce(performSearch, 300);
```

### Error Handling Rules

#### User-Friendly Error Messages
```javascript
// ALWAYS provide helpful error messages
function handleApiError(error) {
    let userMessage;
    
    switch (error.status) {
        case 403:
            userMessage = __('You do not have permission to perform this action', 'forum-press-zone');
            break;
        case 404:
            userMessage = __('The requested item was not found', 'forum-press-zone');
            break;
        case 429:
            userMessage = __('Too many requests. Please wait and try again', 'forum-press-zone');
            break;
        default:
            userMessage = __('An unexpected error occurred. Please try again', 'forum-press-zone');
    }
    
    Toast.error(userMessage);
}
```

#### Graceful Degradation
```javascript
// ALWAYS provide fallbacks for failed operations
async function saveSettings(settings) {
    try {
        await API.post('/settings', settings);
        Toast.success(__('Settings saved', 'forum-press-zone'));
    } catch (error) {
        // Fallback: Store locally and retry later
        localStorage.setItem('presszone_forum_pending_settings', JSON.stringify(settings));
        Toast.warning(__('Settings saved locally. Will sync when connection is restored', 'forum-press-zone'));
    }
}
```

### Translation & Internationalization

#### Text Domain Usage
```javascript
// ALWAYS use correct text domain
__('Save Changes', 'forum-press-zone')  // CORRECT
__('Save Changes', 'presszone-forum')   // WRONG
__('Save Changes')                      // WRONG - missing domain
```

#### Pluralization
```javascript
// ALWAYS handle pluralization correctly
function formatCount(count, singular, plural) {
    const text = count === 1 ? singular : plural;
    return sprintf(text, count);
}

const message = formatCount(
    itemCount,
    __('%d item selected', 'forum-press-zone'),
    __('%d items selected', 'forum-press-zone')
);
```

### Testing Requirements

#### Unit Test Coverage
```javascript
// ALWAYS test critical functions
describe('API.post', () => {
    it('includes nonce header', async () => {
        const mockFetch = jest.fn().mockResolvedValue({
            ok: true,
            json: () => Promise.resolve({ success: true })
        });
        global.fetch = mockFetch;
        
        await API.post('/test', { data: 'test' });
        
        expect(mockFetch).toHaveBeenCalledWith(
            expect.any(String),
            expect.objectContaining({
                headers: expect.objectContaining({
                    'X-WP-Nonce': expect.any(String)
                })
            })
        );
    });
});
```

#### Integration Testing
```javascript
// ALWAYS test user workflows
describe('Node Management Workflow', () => {
    it('creates, updates, and deletes node', async () => {
        // Create
        const createResponse = await API.post('/nodes', { title: 'Test Node' });
        expect(createResponse.success).toBe(true);
        
        // Update
        const updateResponse = await API.put(`/nodes/${createResponse.data.id}`, { title: 'Updated Node' });
        expect(updateResponse.success).toBe(true);
        
        // Delete
        const deleteResponse = await API.delete(`/nodes/${createResponse.data.id}`);
        expect(deleteResponse.success).toBe(true);
    });
});
```

---

## 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
```