# Site Content Admin UX

Audience: AI coding agents first.

- **ID:** IPZ-SITE-CONTENT-ADMIN-UX-2026-08-15
- **Status:** DONE
- **Tasks:** SC-ADMIN-01, SC-ADMIN-TEST-01
- **Source request:** Continue unfinished multilingual Site Content end-to-end. Owner explicitly authorized one parallel `/ask-codex` worker using `gpt-5.6-terra` / `medium`, but required this plan and implementation spec first. Coordinator MUST review, fix, remotely verify, and integrate worker output, then continue main-thread transport/controller tasks without stopping.
- **Execution owner:** One owner-authorized Codex worker implements only disjoint admin paths below. Main coordinator owns plan, direct review, corrections, remote gates, integration, publication, and all PHP transport/controller work.

## Outcome

Build dormant Site Content admin page module plus focused deterministic Vitest coverage. Reuse current admin primitives and existing Site Content SCSS. Match planned current REST contract without activating route, import, navigation tab, bootstrap, PHP endpoint, or runtime consumer.

## Worker ownership

Worker MAY edit only:

- `plugins/international-press-zone/admin/src/pages/site-content.js` (new)
- `plugins/international-press-zone/admin/tests/site-content.test.js` (new)
- this plan only to append worker receipt; MUST NOT change requirements or status

Worker MUST NOT edit:

- `admin/src/pages/translations.js`, `admin/src/main.js`, route tables, bootstrap, manifests, built assets, or SCSS
- any PHP file
- any other test, plan, registry, package, lockfile, configuration, or generated file
- coordinator worktree; worker works in its dedicated isolated worktree

## REST contract

Use `API.request()` from `admin/src/utils/api.js`. Treat payload as `response.data || response` only at API boundary. Never accept alternate field aliases beyond contract below.

1. `GET /site-content?type=<type>&page=<positive-int>&search=<optional>`
   - Supported types: `wp_template`, `wp_template_part`, `navigation`, `pattern`.
   - Response: `{ items, page, total_pages, total }`.
   - Each item: `{ type, catalog_id, title, slug, entity_kind, origin, where_used, source_status, source_language, source_revision, translations }`.
   - `translations` keyed by target language. State: `{ status, translation_revision }` where status is exactly `missing|translating|ready|needs_update|failed`.
2. `GET /site-content/{type}/{catalog_id}`
   - Response includes catalog item fields plus `segments` and `translations`.
   - Source segment: `{ id, text, source_hash }`.
   - Translation state for selected language includes `{ status, translation_revision, segments }`.
   - Translated segment: `{ id, source_hash, translation }`.
3. `POST /site-content/{type}/{catalog_id}/translations`
   - Header: fresh `Idempotency-Key` per logical submission; reuse same key only when retrying same transiently failed logical submission.
   - Body: `{ target_languages: [language], source_revision }`.
   - Prevent concurrent duplicate submit for same exact resource/language.
4. `PUT /site-content/{type}/{catalog_id}/translations/{language}`
   - Body: `{ source_revision, translation_revision, segments }`.
   - Segment: `{ id, source_hash, translation }`.
5. `DELETE /site-content/{type}/{catalog_id}/translations/{language}`
   - Body: `{ source_revision, translation_revision }`.

Use `encodeURIComponent()` for every path identifier. Use `URLSearchParams` for query values. Never interpolate an unencoded identifier.

## Required behavior

1. Export one default `SiteContentPage` class matching current page lifecycle: constructor receives `(container, languages = [])`; `render()` mounts; `destroy()` cancels timers, invalidates pending responses, removes listeners through owned-node teardown, and clears container.
2. Render heading `Site Content`, accessible resource tabs, search, pagination, resource metadata, and per-target-language state/actions.
3. Tabs:
   - Templates → `wp_template`
   - Headers & Footers → `wp_template_part`
   - Navigation → `navigation`
   - Patterns → `pattern`
   - Do not hard-disable Navigation or Patterns. Backend/controller capability activation belongs outside dormant page module.
4. Derive target languages only from constructor data. Include active configured languages except source/default language. Never synthesize or infer language codes.
5. Actions:
   - `missing|failed`: Translate
   - `translating`: disabled state
   - `ready|needs_update`: Review
   - `ready|needs_update|failed`: Regenerate and Delete
6. Review exact current segments. Preserve segment order. Submit exact `id`, `source_hash`, and edited translation. On stale source or stale translation, refetch current resource. Restore only unsaved edits whose exact `(id, source_hash)` still matches; expose incompatible drafts read-only so user can recover text.
7. Handle HTTP 409 codes exactly:
   - `ipz_site_content_stale_source`: refresh current source/catalog and announce stale source.
   - `ipz_site_content_stale_translation`: reopen current review when applicable and announce stale translation.
   - `ipz_idempotency_conflict`: discard local submission key, refresh catalog, and announce conflict.
8. For unsupported backend/controller errors, render clear unavailable state plus Retry. Never infer support from a stale unversioned boolean such as `site_content.segmented_translation`.
9. Ignore stale async responses after tab/search/page changes or `destroy()`. Prevent delayed search callbacks after destroy.
10. Preserve keyboard tab behavior, visible labels, focus after refresh, polite live announcements, and disabled/busy semantics. Never use `innerHTML` with data. Use `el`, `clear`, `__`, and existing component conventions.
11. Reuse existing `_translations.scss` classes where practical. Do not add inline styles or modify styling in this slice.
12. Keep module dormant: no import, route registration, navigation tab, bootstrap construction, generated bundle, or active consumer.

## Tests

Add focused `admin/tests/site-content.test.js` coverage using existing Vitest/JSDOM conventions. MUST prove:

- exact tab-to-type requests for all four resource types and encoded endpoint identities;
- search debounce and pagination request shapes;
- exact target-language filtering and status actions;
- duplicate-submit suppression and logical idempotency-key reuse only after transient failure;
- stale-source and stale-translation refetch/review behavior;
- review draft reconciliation by exact `(id, source_hash)` and read-only incompatible draft recovery;
- exact PUT and DELETE revision payloads;
- stale response suppression across tab changes and after destroy;
- timer teardown and no DOM mutation after destroy;
- accessible tabs, live status, and keyboard navigation;
- module remains unreferenced by active source entry points.

Tests MUST verify observable behavior and request payloads. Never assert source text or tautologies as substitute behavior.

## Preserved WIP / ref / path

- Coordinator worktree: `/home/user/Projects/Press.zone/wordpress/wp-content/.worktrees/site-content-dispatch-controller`
- Coordinator branch/base: `wt/site-content-dispatch-controller` at `a6febe270f37bb5572dc1deb0f9cb9b7be38a80a`
- Historical evidence only: `e9a2c64f6169d85d76b3606d502c1312b912f919:plugins/international-press-zone/admin/src/pages/site-content.js`. Inspect it, but NEVER transplant wholesale.
- Existing styles: `plugins/international-press-zone/admin/src/styles/pages/_translations.scss`.
- Existing browser contract references `#/translations/site-content`; do not activate it in this slice.
- Worker isolated worktree: `/home/user/Projects/Press.zone/wordpress/wp-content/.worktrees/site-content-admin-codex`
- Worker branch/base: `wt/site-content-admin-codex` at exact `a6febe270f37bb5572dc1deb0f9cb9b7be38a80a`

## Constraints

- Current source is authoritative. Inspect `admin/src/utils/api.js`, `admin/src/utils/dom.js`, current page lifecycle, and current tests before editing.
- Surgical changes only. No new framework, store, abstraction, CSS, dependency, capability store, polling scheduler, or generated asset.
- No arbitrary response normalization, fallback aliases, hidden global state, `Date.now()`-only idempotency authority, or mutable cross-resource state.
- Do not run dependency installs, tests, lint, build, typecheck, browser work, diagnostics, or any gate locally. Worker does not run gates. Coordinator runs all gates later through canonical `/ipz-e2e` infrastructure on registry-selected `debian1`/`debian2`/`debian3` and inspects output for actual execution.
- Worker MUST implement directly. No subagents, skills, external models, workflows, or nested delegation.
- Runtime activation remains forbidden until segmented backend is deployed and PHP transport/controller plus this admin slice are complete and reviewed.

## Execution

1. Coordinator writes this plan/spec and registry entry before worker dispatch.
2. Coordinator creates a fresh isolated worker worktree from exact coordinator base and records path/ref.
3. One Codex worker implements only allowed admin files and appends exact receipt.
4. While worker runs/after synchronous return, coordinator immediately resumes disjoint PHP transport/controller lane; worker output does not replace main-thread work.
5. Coordinator inspects every worker line against this spec, current API client/lifecycle patterns, accessibility, stale-response behavior, and security. Coordinator fixes all defects directly.
6. Coordinator runs focused and broad applicable gates remotely only; inspect proof that commands actually ran and address every signal.
7. Keep dormant. Integrate with coordinated slice only after PHP seams are complete; publish through serialized repository publisher; prove exact ancestry/tree.

## Current receipt

Plan/spec was authored before dispatch. Single owner-authorized worker invocation completed in its isolated worktree. Output rejected: wrong constructor and language source, wrong public type values, stale capability dependency, incomplete list query, wrong translation response shape, wrong queue payload/idempotency transport, and missing pagination, review, delete, stale-conflict, exact draft-reconciliation, keyboard-tab, and comprehensive test behavior. No worker file was copied.

Coordinator inspected current `API.request()`, DOM utilities, page lifecycle, language fields, and Vitest conventions, then created a direct dormant replacement at `admin/src/pages/site-content.js`. Module receives `(container, languages = [])`, uses four exact public types and REST shapes, derives target languages from constructor data, cancels/invalidates stale work, supports catalog/search/pagination/actions/review/delete, persists logical idempotency authority across transient retries only, and reconciles drafts by exact `(id, source_hash)`. Module remains unimported and inactive.

Focused Site Content admin coverage contains 17 passing tests on registry-selected `debian2`. Corrected existing settings-loading assertions now exercise the current tone control, and translation-route assertions now pin the current Pages-first default. Full admin coverage passes remotely: 59 files and 746 tests, with no failures, skipped tests, or pending tests. Changed-source ESLint passes. Production Webpack compilation completes without warnings. Expected error-path fixture logs were inspected and correspond only to tests that intentionally exercise rejected requests and reload failures.

Direct review confirms exact endpoint encoding, request epochs, timer/abort teardown, logical idempotency-key reuse, revision payloads, stale draft reconciliation, safe DOM creation, keyboard behavior, and fail-closed response validation. Active source entry points do not import the module, and the generated admin bundle contains no Site Content page or endpoint reference. The admin module remains dormant.

The coordinated source commit `dd2bd00f9462a20cbaa893a98e9f2eab70568e74` is published in merge `a5e2f9de600a7385e8c83bf7d1d49b3c7fb97780` on `origin/master`. Exact ancestry and fetched `origin/master` tree equality are proven. No active import or generated Site Content bundle was published.

## Next executable action

None for this dormant module. Activation remains forbidden until the segmented backend is deployed through a sanctioned controller and a separate activation plan verifies the complete runtime path.
