{
  "contract_version": "gab_009_contract_v1",
  "gab_id": "GAB-009",
  "canonical_name": "SessionConnectionFallback",
  "module_owner": "EdTechPlayEngine",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "title",
    "body",
    "retry_cta"
  ],
  "optional_fields": [
    "fallback_id",
    "icon",
    "hint",
    "status_offline_label",
    "status_searching_label",
    "status_online_label",
    "retry_searching_label",
    "retry_success_label",
    "secondary_cta"
  ],
  "field_types": {
    "gab_id": "string — valeur fixe 'GAB-009'",
    "renderer_key": "string — valeur fixe 'text_cta'",
    "fallback_id": "string — identifiant unique de l'instance de fallback",
    "icon": "string — emoji ou caractère représentant l'état réseau (défaut : 📡)",
    "title": "string — titre principal affiché dans la carte fallback",
    "hint": "string — sous-titre court de réassurance (ex : 'Pas de panique.')",
    "body": "string — message explicatif complet affiché à l'élève",
    "status_offline_label": "string — libellé du badge de statut en mode hors-ligne",
    "status_searching_label": "string — libellé du badge de statut pendant la recherche réseau",
    "status_online_label": "string — libellé du badge de statut une fois la connexion rétablie",
    "retry_cta": "string — libellé du bouton de retry à l'état initial",
    "retry_searching_label": "string — libellé du bouton pendant la tentative de reconnexion",
    "retry_success_label": "string — libellé du bouton une fois la connexion rétablie",
    "secondary_cta": "string — libellé du bouton secondaire (sortie avec sauvegarde)"
  },
  "constraints": [
    "title, body, retry_cta sont requis — le rendu est BLOCKED si l'un est absent.",
    "Le moteur gère les états UI (searching, online, offline) ; l'instance fournit uniquement les libellés textuels.",
    "L'icône d'animation (searching spinner) est pilotée par le moteur, pas par l'instance.",
    "secondary_cta est optionnel : si absent, le bouton secondaire est masqué.",
    "Les libellés de statut (status_*_label) ont des valeurs par défaut dans le moteur si non fournis."
  ],
  "blocked_conditions": [
    "gab_id absent",
    "title absent",
    "body absent",
    "retry_cta absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "boutons identifiés par libellé visible",
    "responsive 375/768/1024"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, titre/body/boutons depuis JSON, 0 erreur" },
    { "case": "champ requis manquant (ex: retry_cta absent)", "expected": "BLOCKED listant le champ manquant" },
    { "case": "click bouton retry", "expected": "animation searching, puis state online après 1.6 s, retour offline après 2.5 s supplémentaires" },
    { "case": "secondary_cta absent", "expected": "bouton secondaire masqué sans erreur" },
    { "case": "instance externe injectée via ENGINE.init(data)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, boutons empilés sur mobile" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-009",
    "note": "Ce schema VALIDE l'instance. Le contrat pédagogique complet (input_contract/validation_logic/feedback_scoring_logic) vit dans le CORE-GAB officiel, pas ici (évite la duplication). Source HTML : INDEX-300-playengine-GAB-006-010-PLAYABLE.html, stage data-tpl=9, handlers fbRetry()."
  }
}
