```yaml
id: UJ-001
title: Add a configured language
actors: [administrator]
surface: admin-spa
goal: Administrator adds one active language and fresh language state includes its exact configuration.
trigger: Activate Add Language at /wp-admin/admin.php?page=international-press-zone#/languages.
fixtures: [authenticated-admin, unique-language-code-and-locale]
success_state:
  visible: "Language added successfully" renders and the new language row appears.
  durable: wp_ipz_languages contains the submitted code, locale, names, flag, direction, and active state.
  persistence: Fresh GET /wp-json/international-press-zone/v1/languages returns 200 with the created language.
source_specs: [admin/src/pages/languages.js, includes/API/LanguagesRestController.php, includes/Core/LanguageManager.php]
readiness: ready
canonical: true
blockers: []
```

## Path

| Hop | Evidence |
|---|---|
| UI trigger | `admin/src/pages/languages.js:59` |
| Request construction | `admin/src/pages/languages.js:650` |
| Endpoint auth and parse | `includes/API/LanguagesRestController.php:104` |
| Authoritative write | `includes/Core/LanguageManager.php:355` |
| Response | `includes/API/LanguagesRestController.php:426` |
| Terminal render | `admin/src/pages/languages.js:653` |

## Happy path

### H1
- Setup: Administrator authenticated; submitted code and locale do not exist in `wp_ipz_languages`.
- Action: Activate `Add Language`, select a valid unused preset, then activate modal `Add Language`.
- Request: `POST /wp-json/international-press-zone/v1/languages`.
- Response: `201` with `{success:true,message:"Language created successfully.",data:<created-language>}`.
- Visible: `Language added successfully` renders; refreshed language table contains submitted name, code, locale, direction, and active state.
- Durable: `wp_ipz_languages` contains one row with submitted `code`, `locale`, `name`, `native_name`, `flag_code`, `text_direction`, and `is_active=1`.
- Fresh read: `GET /wp-json/international-press-zone/v1/languages` returns `200` with the exact created language in `data`.
- Forbidden: No existing language row changes; no duplicate code or locale row exists.
- Evidence:
  - Setup: `includes/API/LanguagesRestController.php:414`
  - Action: `admin/src/pages/languages.js:398`
  - Request: `admin/src/pages/languages.js:650`
  - Response: `includes/API/LanguagesRestController.php:426`
  - Visible: `admin/src/pages/languages.js:653`
  - Durable: `includes/Core/LanguageManager.php:355`
  - Fresh read: `admin/src/pages/languages.js:85`
  - Forbidden: `includes/API/LanguagesRestController.php:414`

## Alternate and failure paths

### A1
- Setup: Administrator authenticated; `wp_ipz_languages` already contains submitted language code.
- Action: Submit `Add Language` with that existing code.
- Request: `POST /wp-json/international-press-zone/v1/languages`.
- Response: `409` with `{code:"language_code_exists",message:"Language code already exists.",data:{status:409}}`.
- Visible: `Language code already exists.` renders in an error toast; modal remains available for correction.
- Durable: `wp_ipz_languages` remains unchanged.
- Fresh read: `GET /wp-json/international-press-zone/v1/languages` returns `200` with exactly one row for the duplicate code.
- Forbidden: No language row is inserted; no existing language fields change.
- Evidence:
  - Setup: `includes/API/LanguagesRestController.php:414`
  - Action: `admin/src/pages/languages.js:398`
  - Request: `admin/src/pages/languages.js:650`
  - Response: `includes/API/LanguagesRestController.php:415`
  - Visible: `admin/src/pages/languages.js:661`
  - Durable: `includes/API/LanguagesRestController.php:415`
  - Fresh read: `admin/src/pages/languages.js:85`
  - Forbidden: `includes/API/LanguagesRestController.php:415`

## Permissions and boundaries

### P1
- Setup: No authenticated WordPress session.
- Action: Submit a valid language payload directly.
- Request: `POST /wp-json/international-press-zone/v1/languages`.
- Response: `401` with `{code:"rest_forbidden",message:"Sorry, you are not allowed to do that.",data:{status:401}}`.
- Visible: WordPress REST denial body exposes no language data.
- Durable: `wp_ipz_languages` remains unchanged.
- Fresh read: `GET /wp-json/international-press-zone/v1/languages` returns `401` for an anonymous actor without `read`.
- Forbidden: No language row is inserted; no administrator nonce or identity is disclosed.
- Evidence:
  - Setup: `includes/API/LanguagesRestController.php:736`
  - Action: `includes/API/LanguagesRestController.php:104`
  - Request: `includes/API/LanguagesRestController.php:104`
  - Response: `includes/API/LanguagesRestController.php:736`
  - Visible: `includes/API/LanguagesRestController.php:736`
  - Durable: `includes/API/LanguagesRestController.php:736`
  - Fresh read: `includes/API/LanguagesRestController.php:728`
  - Forbidden: `includes/API/LanguagesRestController.php:736`

## Source specs

- `admin/src/pages/languages.js`
- `includes/API/LanguagesRestController.php`
- `includes/Core/LanguageManager.php`
