<!-- 04_VISUAL/xwui/xwui-lib/xwui-lib-super/src/collaboration/XWUIConflictResolver/XWUIConflictResolver.md -->
# XWUIConflictResolver

One record where two sources changed the same field, shown as an ordered pair of sides (value, note, who, when, source tile) with
Keep A, Keep B, an optional Merge, an optional "always keep this side for this field" policy, a stale state when a side moved
again, and a resolved state that says what was decided and for whom it was logged, with Undo. One conflict or many. It covers a
sync conflict between two connected tools and the 409 of an optimistic-concurrency save (your version, the version saved
meanwhile, the value both started from). Suggest only: every decision is an event; the host saves and answers with data.
Composes XWUISourceBadge (every source tile), XWUITag (the field flag), XWUIButton (every command) and XWUIProgressSkeleton
(loading).

## Basic Usage

```example
file: examples/BasicUsage.ts
html: examples/BasicUsage.html
title: Basic Usage
description: One field changed in two connected tools: keep a side, keep it for good, Undo.
```

```example
file: examples/EditConflict409.ts
html: examples/EditConflict409.html
title: Edit conflict (409)
description: Your version, the version saved meanwhile and the value both started from, with merge.
```

## Data

| Field | What it is |
| --- | --- |
| `conflicts[]` | `{ id, title, key?, source?, fieldLabel, sides: [A, B], base?, explanation?, merge?, policy?, stale?, error?, resolution? }` |
| side | `{ id, label, value, note?, by?, at?, source?, keepName? }`; `keepName` is the word in "Keep {side}" (unset: source label, else label) |
| `source` | `{ label, mark?, markHue? }`, drawn by XWUISourceBadge; `markHue` is a hue token NAME or a host token, never a colour |
| `base` | `{ value, label? }`: the value both edits started from (the 409's base version) |
| `policy` | `{ label?, sideId? }`: the keep-for-good link; `sideId` unset = the second side |
| `stale` | `true` or a message: a side moved again (a second 409); Keep waits, Refresh asks for the latest |
| `error` | the last save failed for another reason: read once, buttons live again |
| `resolution` | `{ sideId \| 'merge', message? }`: decided; `message` says what and for whom it was logged |
| `status`, `message` | the list: `'ready'` (default), `'loading'` (placeholders), `'error'` (message and Retry) |

A conflict without an id, a field or two named sides with distinct ids is not drawn; a repeated id keeps its first entry.

### Mapping a 409

A versioned write rejected with `conflict` carries `{ kind, id, current, currentVersion, baseVersion }`. The page builds one
conflict per field it edited: side A is its own edit (`id: 'mine'`), side B the field read from `current` with who and when from
the record, and `base` the field's value at `baseVersion` when the page kept it. Keep mine re-saves against `currentVersion`; keep
theirs drops the edit; a second 409 during that save becomes `stale`.

## Options

| Option | Default | What it does |
| --- | --- | --- |
| `layout` | `'auto'` | `'side-by-side'`, `'stacked'`, or auto (stacked when the component is narrower than 34rem) |
| `allowMerge` | `false` | Offer Merge on conflicts that carry `merge` |
| `allowPolicy` | `true` | Draw the keep-for-good link on conflicts that carry `policy` |
| `allowUndo` | `true` | Draw Undo on resolved conflicts |
| `headingLevel` | `3` | Heading level of each conflict title (2 to 6) |
| `valueDir` | `'auto'` | Each value takes its own direction; `'ltr'` pins values left to right (amounts, codes) |
| `ariaLabel` | `labels.list` | Accessible name of the component |
| `labels` | locale pack | `keep`, `keepName`, `merge`, `undo`, `refresh`, `retry`, `policy`, `empty`, `stale`, `resolved`, `merged`, `base`, `error`, `loading`, `undone`, `list` |
| `locale`, `numberingSystem` | page language, `'latn'` | The spoken count |

## Events and methods

| Event | Detail | When |
| --- | --- | --- |
| `resolve` | `{ id, sideId, policy }` | Keep (`policy: false`), the policy link (`policy: true`), Merge (`sideId: 'merge'`) |
| `undo` | `{ id }` | Undo on a resolved conflict |
| `refresh` | `{ id }` | Refresh on a stale conflict |
| `retry` | `{}` | Retry on a list that failed to load |

Host handlers: `onResolve`, `onUndo`, `onRefresh`, `onRetry` (released by `destroy()`). Commands as the buttons run them:
`resolve(id, sideId, { policy })`, `merge(id)`, `undo(id)`, `refresh(id)`, `retry()`, each false when not allowed now. Host answers:
`setResolution(id, resolution | null)`, `setStale(id, stale)`, `patchConflict(id, patch)`, `removeConflict(id)`,
`setConflicts(list)`, plus `updateData`, `updateConfig`, `getPhase(id)`, `getElement()`, `destroy()`.

A command is sent once per state: the conflict is `pending` (aria-busy, the chosen button busy, the other unavailable) until the
host's data moves it to resolved, stale, error or off the list.

## Accessibility and RTL

Each conflict is a `role="group"` named by its record and field. The sides are an ordered list. Keep buttons are named "Keep
{side} for {field} on {record}", starting with their visible words. After a decision focus moves to the next conflict that waits,
else to the decided conflict's Undo, else to the component; after Undo it returns to the reopened conflict; if the host removes
the focused control, focus goes to the next waiting conflict instead of the page. Outcomes ("Kept Jira. 2 conflicts left") are
spoken through one polite live region. State is never colour alone: the kept side has a check, the dropped value is struck, a
stale conflict has an alert shape and words. Logical properties only; direction comes from the parent `dir`; keys are
left-to-right isolates; user text is `dir="auto"`; the spoken count uses Western digits unless `numberingSystem` says otherwise.
The focus scroll is smooth only without `prefers-reduced-motion`.

```api
```
