# ACF field table accordions

Audience: AI coding agents first.

## Outcome

Replace the unbounded ACF field tree with compact, accessible field-group accordions. Keep every group collapsed initially. Render each expanded group as a project-standard table with `Name`, `Slug`, `Type`, `Key`, and `Setting` columns. Render child collections and flexible-content layouts as nested disclosures. Use alternating row surfaces without eagerly mounting closed tables.

## UX contract

- Preserve source order. Prefix top-level summaries with one-based position: `1. Annuals`, `2. …`, through `N`.
- Initial paint and browser reload: every disclosure is closed. Browser reload does not preserve disclosure, focus, search, or scroll.
- Each disclosure opens independently. Use native `<details>/<summary>` semantics. Nested accessible names MUST include parent path.
- Group summary shows ordinal, title, stable group key, and field count.
- Expanded group reuses project table surface and control styling.
- Table columns, in order: `Name`, `Slug`, `Type`, `Key`, `Setting`.
- `Name` uses label, then slug, then key. `Slug` uses ACF `name`; `Type` uses `type`; `Key` uses `key`; `Setting` contains existing selector and status.
- Every table has a caption or accessible name tied to its group/layout. Every header uses `scope="col"`. Status updates use existing live-region behavior.
- Zebra parity restarts in each table and counts field rows only. Odd rows use normal surface; even rows use existing alternate-surface token. Hover and `:focus-visible` remain distinct.
- Repeater, group, and clone fields with validated children expose a nested disclosure from Name. Valid empty collections render a specified empty state.
- A flexible-content field's Name disclosure lists layouts. Each layout summary shows stable layout identity, label/name, and field count. Opening a layout mounts only that layout's child-field five-column table. Layout metadata MUST NOT be fabricated as a field row.
- Successful or failed in-page mutation/data refresh preserves user disclosure state, query, scroll, and logical focus. Capture stable field key plus control role before render; restore that control if present, otherwise focus its owning summary.
- Empty, unavailable, malformed, loading, and retry states retain current meaning.

## Search state

- Match case-folded normalized values from field label, name/slug, type, key, configured-mode label, runtime-policy label, group title/key, and layout label/name.
- On empty→nonempty query transition, snapshot user-open disclosure keys once.
- Filter out nonmatching field rows and groups. Retain matched rows plus required ancestors. Derive search-required open keys without mutating the snapshot.
- While search is active, required ancestors remain open; user toggles update only non-required disclosure state.
- Query changes recompute matches and required ancestors from the same snapshot.
- Clearing search restores the snapshot exactly.

## Rendering and identity

`FieldsTranslatePage` validates and indexes the complete graph for search, but mounts a child table only when its disclosure is user-open or search-required. Closed groups MUST NOT contain table/control nodes. Policy values remain keyed by stable field key.

Stable disclosure identities:

- group: `group:<group.key>`
- field children: `field:<field.key>`
- layout: `layout:<field.key>:<layout.name>`

Duplicate or missing stable keys are invalid. Layout names need only be unique within their owning flexible field. Repeated labels never define identity. State is page-memory only; never persist disclosure state to localStorage.

## Validation

- Reject malformed graph data before rendering.
- Require child arrays only for API field types whose schema requires them; valid empty arrays are allowed.
- Handle clone references according to the actual API response schema; do not assume every clone owns inline children.
- Never infer or mutate policy while rendering hierarchy.
- Keep mutation queue serialized.

## Responsive and international behavior

- Semantic table wrapper owns horizontal overflow; body MUST NOT scroll horizontally.
- Complete Setting control remains reachable and operable at 320 CSS px and 200% zoom.
- Add wrapper keyboard focus only when overflow exists; avoid unconditional `tabindex="0"`.
- Keyboard operation MUST not lose focus across two-dimensional scrolling.
- Preserve RTL summary affordance, reduced-motion, high-contrast, and supported theme behavior.

## Verification

- DOM tests prove closed disclosures do not mount tables or controls.
- Tests open arbitrary top-level, nested, and layout disclosures independently and assert headers, captions, identities, row values, and independent layout-name collisions.
- Tests prove missing/duplicate keys fail, repeated labels remain independent, malformed required children fail, and valid empty collections render empty state.
- Zebra assertions prove parity restarts per table and excludes disclosure/container rows.
- Search tests prove deep matching, filtering, ancestor opening, query changes, toggles during search, and exact snapshot restoration.
- Mutation tests prove success/failure state preservation, stable-key submission, focus restoration, and summary fallback after row disappearance.
- Large synthetic graph test proves initial mounted table/control count is bounded by open/search-required disclosures, not total graph size.
- Browser verification on authorized cluster/local WordPress captures collapsed initial paint, nested expansion, keyboard operation, RTL, 320 px/200% zoom overflow, and computed normal/alternate/hover/focus backgrounds in supported themes.
- Never browser-test dev1. Dev1 verification is archive/version/asset identity only.

## Architecture decisions

- Use native disclosures rather than a one-consumer Accordion abstraction.
- Reuse existing table classes/tokens; page owns graph-to-row rendering and state.
- Keep state in memory; browser reload intentionally resets the view.
- Index the graph eagerly for deterministic search, but mount disclosure content lazily to bound initial DOM cost.
