# Mirror Original Custom-Field Controls

**Date:** 2026-08-16
**Plan slug:** `mirror-original-custom-fields`
**Scope:** Edit Translation modal in `plugins/international-press-zone`

## Problem

The Custom Fields disclosure renders each original-language value as a bespoke display `<div>` while rendering its translated counterpart through the shared `TextareaControl()` component. The two sides consequently use different browser elements, style systems, padding, line heights, border treatments, minimum-height rules, and resize behavior.

The mismatch is amplified by the universal modal selector `#ipz-modal-container textarea`, whose `min-height: 100px` and `resize: vertical` rules outrank the ACF page-level sizing classes. A nominally 72px translated control therefore cannot match its 72px original display box. This produces the visibly different shapes and sizes shown in the supplied screenshot.

## Desired Behavior

For every row in the Custom Fields disclosure:

- The original and translated controls use the same native control type and shared component path.
- Corresponding controls have identical computed width, height, box sizing, padding, border width, border radius, line height, and resize behavior at the same viewport.
- Both sides use the same field-type sizing rule.
- The current translated-control default geometry and `resize: vertical` behavior remain canonical; the original control adopts them rather than normalizing or shrinking both sides. Default paired geometry matches before interaction, while deliberate user resizing of the editable translated control may subsequently diverge.
- The original control is always read-only, remains keyboard-focusable for selection/copying and scrolling, and never participates in save serialization.
- The translated control preserves its current editability rules, token/mode metadata, directionality, tab behavior, generation behavior, and save serialization.
- Read-only semantics alone do not create a different visual shape or surface.
- Desktop two-column and narrow stacked layouts remain symmetric.
- Empty, long, wrapped, LTR, RTL, copy-mode, and processing-state values do not make paired geometry diverge.

## Non-Goals

- Changing the REST ACF field contract or backend field-mode mapping.
- Changing which ACF field types are translatable.
- Introducing field-specific editors beyond the existing textarea-based model.
- Redesigning the disclosure, modal, labels, or surrounding translation workflow.
- Editing generated `admin/dist/**` files directly.

## Existing Contracts

### ACF modal row

The frontend continues to consume the existing row shape produced by `ACFIntegration` and attached by `TranslationsController`:

```text
{
  token,
  label,
  type,
  mode,
  source_value,
  target_value
}
```

No field is added, removed, or reinterpreted.

### Shared control

`TextareaControl(options): HTMLElement` remains the common native-control factory. The mirrored ACF renderer supplies the value, rows, sizing class, direction, read-only state, and side-specific attributes.

## Design

### Shared labeled ACF group construction

In `admin/src/pages/content-translate.js`, replace the independent original display-box and translated textarea construction paths with one local labeled-group seam used by both sides:

```text
buildAcfFieldGroup({ side: "source" | "translation", field, value, direction, status }): HTMLElement
```

The seam returns the complete labeled field group rather than a bare control. Its closed input contract prevents callers from passing an unrestricted attribute bag.

The shared seam is responsible for:

- Mapping `textarea` and `wysiwyg` to the multiline sizing variant and all currently supported other types to the single-line sizing variant.
- Applying the same `rows`, neutral shared ACF classes, common control classes, value normalization, and side-provided language direction to either side.
- Producing a `TextareaControl()` instance for both sides.
- Applying `readOnly: true` unconditionally to the source side while preserving keyboard focusability.
- Applying the existing copy/processing read-only rule to the translation side.
- Preserving the translated ID `ipz-ct-acf-${field.token}` exactly and assigning the source ID `ipz-ct-acf-${field.token}-source`.
- Producing a real associated label and a distinct accessible name that includes source/translation context; each visible copy-mode description has a unique ID and is associated with its corresponding source or translated control.
- Guaranteeing internally that `data-acf-token` and `data-acf-mode` are emitted only when `side === "translation"`, so save collection cannot mistake source controls for payload fields.

The existing list-level rendering responsibilities may remain as thin adapters, but they must delegate complete group construction and sizing decisions to this seam. This keeps side-specific values and state explicit without duplicating element markup, labels, or field-type logic.

### Styling contract

In `admin/src/styles/pages/_translations.scss`:

- The current translated textarea geometry is canonical. The source control adopts that geometry; this task does not shrink or normalize the translated side to the older 72px/112px display-box heights.
- Paired ACF controls share neutral geometry and appearance classes; side classes express semantics and targeting only.
- Universal modal textarea `min-height` and `resize` rules remain unchanged and authoritative for both controls.
- Any ACF-specific selector is narrowly scoped under `#ipz-modal-container`, removes only obsolete source geometry or asymmetric `[readonly]` presentation, and never uses `!important`.
- Generic `[readonly]` styling must not alter the source control’s background, color, font metrics, border, radius, shadow, padding, overflow, focus geometry, or dimensions relative to its paired translated control.
- Copy or processing state is communicated through the existing description/status treatment or a separate state indicator. It must not restyle only one member of the pair.
- The obsolete bespoke original-value display-box geometry is removed when no longer referenced.
- Existing equal-column and `max-width: 782px` stacked layout rules remain authoritative.

All styling remains in SCSS; no inline styles or runtime-injected CSS are permitted.

### Data and event flow

1. The modal receives the unchanged ACF row array for the selected language.
2. The disclosure pairs original and translated list items by the existing row order.
3. Both list adapters call the shared labeled-group seam with the same field metadata and their existing language-level `sourceDir` or `targetDir`; this task does not add content-based direction detection.
4. The source control exposes source content, remains keyboard-focusable, but has no save metadata and is always read-only.
5. Every field has zero metadata-bearing source controls and exactly one metadata-bearing translated control.
6. The serializer continues to select only translated controls whose `data-acf-mode` is `translate`; copy controls remain excluded. Processing controls retain their existing metadata but remain read-only while Save is unavailable.

## Error and Edge-Case Handling

- Missing or null values normalize through the same existing safe string path on both sides.
- Unknown field types fall back to the existing single-line textarea variant; they do not create a third geometry path.
- Duplicate labels do not create duplicate control IDs; IDs include side and stable row identity/index as required by the existing modal lifecycle.
- Existing language-level `sourceDir` and `targetDir` values remain authoritative so LTR and RTL source/target pairs retain readable text while their boxes remain geometrically equal.
- Copy and processing fields remain read-only without becoming saveable or visually malformed.
- Both controls preserve the canonical `resize: vertical` behavior. Pair geometry is required to match at initial render; intentional user resizing may change one control afterward.

## Verification Strategy

All international-press-zone gates run remotely through the project’s `/ipz-e2e` workflow; no local build, browser, screenshot, Playwright, or test fallback is allowed.

### Mandatory visual reproduction loop

Before implementation, add or run a focused Playwright assertion against the current UI that:

- Opens the Edit Translation modal and Custom Fields disclosure.
- Confirms the pre-fix mismatch by recording tag names, bounding boxes, and computed styles.
- Captures a before screenshot.

After implementation and the remote admin build, run the same scenario and require:

- Both members of every pair are `TEXTAREA` elements.
- Original controls are read-only.
- Translated controls preserve expected editability for translate, copy, and processing modes.
- Paired bounding-box widths and heights are equal within a documented subpixel tolerance.
- Computed `box-sizing`, padding, border width, border radius, line height, font family/size/weight, background, color, box shadow, min/max height, overflow, and `resize` values are equal.
- Default and keyboard-focus states preserve equal geometry and a visible focus indication.
- Pair symmetry holds at 375×667, 768×1024, and 1280×720 in both light and dark mode.
- Fixtures explicitly cover empty values, long/wrapped text, LTR and RTL source/target languages, `text`, `textarea`, `wysiwyg`, `link`, translate mode, copy mode, and processing state.
- The translated control’s recorded pre-fix bounding-box heights and computed geometry remain unchanged, including `resize: vertical`; pair equality must not be achieved by shrinking or normalizing both controls.
- Zero source controls carry ACF save metadata, and exactly one translated control per field carries token/mode metadata.
- Save serialization still includes only translated `mode="translate"` controls, excludes copy controls, and emits unchanged payloads; processing controls retain metadata but remain read-only with no Save action.
- An after screenshot records the corrected result.

If the post-fix visual assertions fail, revert only the task’s modified source and test files, diagnose the computed-style evidence, and repeat the implementation/build/verification loop until all assertions pass.

### Regression coverage

Extend `admin/tests/content-translate-accordions.test.js` to pin:

- Both original and translated rows use the shared textarea control contract.
- The original control is read-only and lacks translated save metadata.
- The translated control preserves token/mode metadata and save serialization.
- Both sides receive the same field-type sizing class and rows contract.
- Existing tab state, editing, generation, and disclosure behavior remain unchanged.

Retain existing shared-control tests in `admin/tests/form-controls.test.js` and `admin/tests/textarea-lifecycle.test.js`, and the backend ACF contract tests unchanged unless a regression demonstrates a genuine contract defect.

### Build and gate

The authoritative source files are under `admin/src/**`. The remote build runs `cd admin && npm run build` only as ephemeral verification; generated `admin/dist/**` assets are not committed for this task. Focused browser coverage uses `tests/e2e/translations-types-fields.spec.js` on Chromium and Firefox through the canonical remote runner, followed by the project-required remote gate set defined by `.claude/agents/expert.md` and `.claude/skills/ipz-e2e.md`.

## Files Expected to Change

- `plugins/international-press-zone/admin/src/pages/content-translate.js`
- `plugins/international-press-zone/admin/src/styles/pages/_translations.scss`
- `plugins/international-press-zone/admin/tests/content-translate-accordions.test.js`
- `plugins/international-press-zone/tests/e2e/translations-types-fields.spec.js`

No PHP/API or generated `admin/dist/**` changes are expected.

## Architecture Decisions

- **Accepted: one shared labeled-group construction seam.** Deleting it would scatter field-type sizing, neutral classes, labels, accessibility context, direction, and read-only decisions back across both side renderers; the boundary therefore hides real parity logic.
- **Accepted: retain side-level list adapters.** Original and translated sides still have distinct value, metadata, and editability responsibilities, while delegating their common control structure.
- **Rejected: preserve the original `<div>` and imitate textarea styles.** Different native elements and specificity paths cannot provide durable computed-style parity.
- **Rejected: add global modal textarea overrides.** The defect is ACF-specific; a global change risks unrelated modal controls and does not express the mirrored-control contract.
- **Rejected: introduce a new reusable component module.** This change has one current consumer; a local shared seam avoids a shallow single-adapter abstraction while remaining independently testable through rendered contracts.
