# Dialog Orchestrator — Plan V4 (headless)

Statut: validé (Itération 1 à engager)
Responsable: Frontend (UI headless) + Backend (contrat `interaction`)
Périmètre: composant headless de dialogue + orchestrateur multi‑instances (modales et embeds) + helpers `confirm/form` + intégration progressive (remplacement des alertes)

## 0) Contexte et justification
- V4 — UI rend, pas de logique métier; contrat HTTP/JSON canonique.
- Besoin générique d'interactions système↔utilisateur (confirmations, formulaires, parcours conditionnels « tunnel ») sans multiplier les implémentations ad hoc.
- Remplacer `alert/confirm/prompt` par une solution a11y, testable, i18n, et évolutive (modal/panel/embed).
- Coexistence: permettre plusieurs dialogues (surtout embeds inertes) tout en garantissant une seule modale active (focus‑trap) à T.

## 1) Objectifs
- Headless dialog engine (état, validation, navigation) — sans DOM ni métier.
- Renderers par défaut (modal `<sb-modal>`; embed inline) — a11y stricte.
- Orchestrateur multi‑instances: registre, une modale active, embeds inertes activables.
- Schéma I/O JSON versionné, minimal mais extensible.
- Alignement backend: `interaction` (200 ok) → nouvelle commande avec `values`.

## 2) Schéma I/O (synthèse)
- Entrée (`schema`):
```
{
  "schemaVersion": 1,
  "id": "dlg_123",
  "layout": "modal|embed|panel",
  "mode": "confirm|form|wizard",
  "size": "sm|md|lg",
  "title": "...", "description": null,
  "ctx": {}, "defaults": {}, "data": {},
  "fields": [ /* form only */ ],
  "validation": {}, "formValidation": [], "asyncValidation": [],
  "steps": [ /* wizard only */ ],
  "actions": [ { "id":"ok", "label":"Valider", "submit":true, "closeOnSubmit":true } ],
  "mount": { "host":"global-modal|container:#id", "activation":"active|manual" },
  "policy": { "scope":"global|board:123|notif:42", "concurrency":"stack|coexist|replace", "maxOpenPerScope":5, "ttlMs":900000 }
}
```
- DSL conditions (bornée): `eq, ne, in, and, or, not` + accès lecture `$data`, `$ctx`; utilisable dans `visibleIf` (champs) et `next` (wizard).
- Sortie (succès):
```
{ "id":"dlg_123", "ok":true, "values":{...}, "meta": { "reason":"SUBMIT", "durationMs": 8200, "path": ["step1","summary"] } }
```
- Sortie (annulation/expiration/remplacement): `{ "id":"dlg_123", "ok":false, "code":"USER_CANCELLED"|"INTERACTION_EXPIRED"|"REPLACED", "meta": { "reason":"CANCEL"|"EXPIRED"|"REPLACED" } }`

## 3) Orchestrateur (headless)
- API:
```
spawn(def): DialogHandle
activate(id): void
close(id, reason?): void
list(filter?): InstanceSummary[]
openDialog(def): Promise<{ data, meta }>
// Phase 2+: promote(id, { host:'global-modal' })
// Hook dirty-guard (fermeture): handle.beforeClose(fn: (reason) => boolean|Promise<boolean>)
```
- États (Itération 1): `idle → active → (completed|cancelled|expired)`
- Invariants: une seule modale `active`; modales non actives: `inert + aria-hidden + tabindex=-1` ; embeds inertes hors tab‑order jusqu'à activation.
- Fallback: si `mount.host` introuvable → rendu `global-modal` + log soft.
- Collisions: deux embeds avec le même `id` dans le même `scope` → refus (`LIMIT_REACHED`) sauf si `policy.concurrency:"replace"`.
  - `replace` n'est autorisé que si `scope` ET `layout` sont identiques.

## 4) Renderers (par défaut)
- Modal: basé sur `<sb-modal>` (focus‑trap, ESC, aria-labelledby/aria-describedby, a11y clavier).
- Embed: rendu inline dans `container:#id` (région, pas de role="dialog"). Activation manuelle.
- Retour de focus: mémoriser l’élément invocateur et le restaurer à la fermeture de la modale (critère a11y DoD).
- ESC + dirty‑guard: la touche ESC respecte `beforeClose` (pas de fermeture si le formulaire est modifié et que le garde le refuse).

## 5) Helpers
- `confirmDialog({ title, message, okLabel?, cancelLabel?, accent? }): Promise<{ ok:true }>`
- `formDialog({ title, fields, defaults?, actions? }): Promise<{ ok:true, values }>`

## 6) Backend (contrat V4)
- Quand un handler a besoin d'input: `200 { ok:true, interaction:{ type:"dialog", schema:{...} } }`.
- Le client ouvre le dialogue, collecte `values`, puis **poste une nouvelle commande** (ex: `ApplyUserChoices` avec `interactionId`, `values`).
- Revalidation back; TTL + signature sur `interactionId`; codes d'erreur spécifiques (`INTERACTION_EXPIRED`, `INVALID_PAYLOAD`, `fieldErrors`, `formError`).

## 7) Sécurité, limites, a11y
- Échappement systématique (labels/titres); aucun HTML brut dans le schéma.
- Limites: taille max schéma/steps/options; options massives → fetch incrémental (phase 2).
- A11y: une seule modale `role="dialog"` active; ESC ferme la modale active uniquement.
 - Validateur de schéma côté front (avant spawn): version supportée, champs inconnus, limites taille/options, absence de cycles steps; logs dev explicites.
   - Codes standardisés: `SCHEMA_UNSUPPORTED_VERSION`, `SCHEMA_UNKNOWN_FIELD`, `SCHEMA_TOO_LARGE`, `SCHEMA_CYCLE_DETECTED`, `SCHEMA_INVALID_DSL`.
 - maxOpenPerScope: s’applique à tous types confondus (modals et embeds); valeur par défaut 5 (configurable).

## 8) Tests & télémétrie
- Unitaires: orchestrateur (single focus), fallback host, TTL→expired; validation/visibleIf; transitions wizard simples.
- E2E: ouvrir 2 modales + 1 embed; garantir focus‑trap unique; activation embed.
- Télémétrie minimale: `interactionId`, `durationMs`, `path`, `cancelled` (sans données personnelles).

## 9) Phases & DoD
- Itération 1
  - Orchestrateur + registre + renderers modal/embed; `openDialog`; helpers confirm/form.
  - Remplacement d'une confirmation existante (p.ex. suppression catégorie) pour valider la chaîne.
  - A11y/focus/ESC conformes; fallback host (avec test unitaire); TTL; retour de focus invocateur.
  - Validateur de schéma côté front activé.
  - Tests unitaires de base verts; docs.
- Itération 2
  - Wizard complet (next, review, allowBack), datasets dynamiques; promote(embed→modal); télémétrie.
- Itération 3
  - Champs avancés (tag‑picker, upload, accordéons), validations async; queue/priorité/minimized si besoin.

### Definition of Done (Itération 1)
- API `openDialog` + orchestrateur; renderer modal/embed.
- Helpers `confirmDialog`/`formDialog`;
- Remplacement d'une confirmation existante côté Admin;
- Tests unitaires orchestrateur + validation/visibleIf;
- Docs à jour (présent dossier).

## 10) Risques & mitigations
- Empilage de modales: orchestrateur impose une seule active.
- « Schema creep »: DSL bornée + versionnement + limites; gouvernance (owner du schéma).
- Perfs avec nombreux embeds: activation manuelle (lazy), limites d'options; promote si besoin.
- A11y mobile: vérifier focus/scroll; fermer au back tap.
