# IPZ Admin UI Makeover — Shared-Component Refactor Plan

Repo root for all tasks: `/home/user/Projects/Press.zone/wordpress/wp-content/.worktrees/ipz-ui-makeover`
Plugin root (all paths below relative to it): `plugins/international-press-zone/`
Working branch: `feat/ui-makeover-shared-components` (agents commit here).

Audit source of truth: `docs/plans/2026-08-21-ipz-ui-makeover-audit.md` (per-file findings with line numbers, component API baseline, coupling risks). Every task MUST read its file's section in the audit before editing.

## Global rules (every task)

- Import shared UI ONLY from `admin/src/components/index.js` — it is the sole public component boundary.
- Preserve every existing CSS class named in the audit's coupling-risk notes (pass via `className`/`attrs`), all `data-*` attributes, focus-restoration keys, ARIA relationships, and root/section IDs (`#ipz-team-root` etc.). The refactor changes construction, not runtime contracts.
- No inline CSS ever (monorepo ban). No new styles unless a new component needs them — then SCSS only.
- Do NOT touch `admin/dist/` — build output is regenerated by the wave build gate.
- Files listed as "Already clean" in the audit are off-limits.
- Per-task acceptance: `node --check <each edited file>` (`.js`/`.mjs`). The full `cd admin && npm run build` runs once per wave in the build-gate task, and release E2E gates run remotely via /ipz-e2e after the workflow — not per task.
- Commit ONLY your task's files. Message: `UI makeover: <task-id> <short description>`.
- The commit gate's slopgate rules (`.slopgate/rules/project.json`, group `no-handrolled-shared-ui`) ban hand-rolled markup carrying a shared component's base CSS class (`ipz-button`, `presszone-international-btn`, `ipz-badge`, `ipz-notice`, `ipz-page-header`, `ipz-progress-bar`, `ipz-pagination`, `presszone-international-spinner`/`-empty-state`/`-stat-card`) outside `admin/src/components/`. If your commit is rejected with a `no-handrolled-*` finding, replace that markup with the named shared component — never rename the class to dodge the rule, and never edit `.slopgate/`.

## Wave 0 — new shared components (4 tasks, disjoint files)

New components fill the API gaps the audit names. Do NOT edit `index.js` here (Wave 0b owns it). Each component must follow the conventions of existing components (read `Button.js`, `Badge.js`, `Tabs.js` first), be theme-aware via existing CSS variables, and reuse existing `ipz-` classes where the audit says the visual must match `Button`.

- **T1** — `admin/src/components/LinkButton.js` (new): `LinkButton({label, href, variant, size, target, rel, className, attrs})` → `HTMLAnchorElement` carrying the same visual classes `Button` emits, native anchor semantics, safe `rel` defaulting for `target="_blank"`.
- **T2** — `admin/src/components/PersistentTabSet.js` (new): `PersistentTabSet({tabs, activeTab, accessibleName, renderPanel, onChange, className, idPrefix})` composing `Tabs` + `TabPanel`; keeps ALL panel nodes alive, toggles `hidden`, wires `aria-controls`/`aria-labelledby`, exposes `{root, getPanel(id), setActive(id)}`.
- **T3** — `admin/src/components/DetailList.js` (new): `DetailList({items: [{label, value}], className, caption})` → semantic `<dl>`; `value` accepts string or Node.
- **T4** — `admin/src/components/TranslationResultList.js` (new): `TranslationResultList({results, getLabel, getStatus, getError, getLink, className})` composing `Badge` and `LinkButton` (import from `./Badge.js` / `./LinkButton.js` directly — index wiring happens in Wave 0b).

## Wave 0b — export wiring + build gate (1 task)

- **T5** — `admin/src/components/index.js`: add exports for `LinkButton`, `PersistentTabSet`, `DetailList`, `TranslationResultList` in alphabetical position matching existing style. Acceptance: `cd plugins/international-press-zone/admin && npm run build` exits 0.

## Wave 1 — small primitive substitutions (8 tasks, one file each)

Each task implements EVERY finding for its file from the audit's "Per-file findings" section, nothing else.

- **T6** — `admin/src/editor/translation-metabox.js`: SpinnerWithText/EmptyState/Notice for status text; LinkButton for the styled edit anchor (preserve `.ipz-editor-translation__edit` + `data-language`).
- **T7** — `admin/src/pages/TeamDashboard.js`: PageHeader (keep `section#ipz-team-root`).
- **T8** — `admin/src/pages/WorkflowDashboard.js`: PageHeader (keep `section#ipz-workflow-root`).
- **T9** — `admin/src/pages/dashboard.js`: ActionGroup for quick actions; Badge for job status; LinkButton for job title anchor.
- **T10** — `admin/src/pages/fields-translate.js`: SpinnerWithText loading placeholder; `emptyStateRow(5, …)` for the empty table row.
- **T11** — `admin/src/pages/languages.js`: ActionGroup for `.ipz-actions-group` row actions.
- **T12** — `admin/src/pages/migration.js`: Notice for WP-style notices; SpinnerWithText; ActionGroup ×4; Badge/StatusFeedback for checklist indicators; StatusFeedback/SuccessCheckmark for completion panel. Keep the hand-rolled stepper (no shared stepper exists — do NOT invent one).
- **T13** — `admin/src/pages/settings.js`: StatCard ×4 for cache stats grid; move confirmation-modal Buttons into `modal.render(content, actions)` footer.

## Wave 2 — navigation, filters, modal shells (4 tasks)

- **T14** — `admin/src/pages/site-content.js`: PageHeader; Tabs (+ TabPanel/PersistentTabSet) replacing the hand-rolled tab bar; FilterBar+FilterGroup+SearchControl for search; SpinnerWithText; ErrorState; EmptyState + Pagination; SurfaceCard shells; Badge/Button status-action controls; TextareaControl+FormControlGroup review form. Notice for the changed-segment heading only — the recovery field list stays custom, but its wrapper (currently a hand-rolled `ipz-notice ipz-notice--warning` section) MUST become the shared Notice component with the custom field list as its content: slopgate now bans hand-rolled `ipz-notice` markup.
- **T15** — `admin/src/pages/translations.js`: PersistentTabSet for the six persistent panels. HIGH coupling: each panel owns a subpage lifecycle, async render generation, and route state — preserve panel identity and hidden/active semantics exactly.
- **T16** — `admin/src/pages/translate-history.js`: FilterBar/FilterGroup for hand-rolled filters; ActionGroup for row actions (initial AND live-patched rows); DetailList in the detail modal; LinkButton for Edit/View links; pass the shared Button directly as the modal footer action; drop the unsupported `breadcrumbs` prop or note it.
- **T17** — `admin/src/pages/my-assignments.js`: PageHeader; PersistentTabSet; DetailList; FormControlGroup for extension-request fields; FIX the invalid `Modal.render` action objects → real `Button` nodes (runtime bug).

## Wave 3 — translation workflows and batch UI (3 tasks — highest risk)

- **T18** — `admin/src/pages/content-translate.js`: all 11 audit findings — SurfaceCard shells, DetailList, SpinnerWithText, Badge/Button status-actions, TabPanel/PersistentTabSet for custom-type panels, ActionGroup ×2, TranslationResultList for completion rows, Notice for field errors, LinkButton for editor links. Preserve `data-focus-key`, submission maps, `_generateAllSelectedLangs` behavior. Leave the language-selection row custom (below new-component threshold).
- **T19** — `admin/src/pages/string-translate.js`: ActionGroup ×2; TranslationResultList for result rows. Leave the language row custom (duplicate noted for future `LanguageSelectionRow`). Do NOT disturb the focused-select deferral logic around `SelectControl.updateOptions`.
- **T20** — `admin/src/pages/translate-posts.js`: FilterBar/FilterGroup filter bar; SpinnerWithText; LinkButton for all styled anchors; ActionGroup table actions; TranslationResultList; TranslationProgressView + ProgressBar for the bulk queue shell (queue-item list with `data-bulk-*`/`data-queue-index` DOM updates stays custom); TranslationProgressCompletion for the completion shell; drop unsupported `breadcrumbs`/`href` props. The custom bulk action bar STAYS custom this pass (shared `BulkActionBar` cannot cover it — audit finding 6).

## Wave 4 — analytics section (1 task)

- **T21** — `admin/src/sections/analytics-dashboard.js`: Notice/ErrorState for unavailable/error panels; StatCard for insight metrics; LinkButton ×3 for export links. Recommendation list and section header stay custom (no `SectionHeader` component yet — PageHeader hardcodes `h1`).

## Build gates

After each wave's tasks land, a build-gate task runs `cd plugins/international-press-zone/admin && npm run build` and reports real output. A failed gate halts the workflow before the next wave.

## Out of scope (recorded, not done)

`LanguageSelectionRow`, `WizardStepper`, `BatchProgressQueue`, `SectionHeader`, generalizing `BulkActionBar`, the `ROUTE_SKELETONS` 7-vs-6 mismatch in `main.js`.

## Landing

Workflow leaves `feat/ui-makeover-shared-components` in the worktree. Landing = owner-driven: remote /ipz-e2e gates, review, then ship. No dev1 deploys.
