{
  "contract_version": "gab_104_contract_v1",
  "gab_id": "GAB-104",
  "canonical_name": "MemoryRecallQuestion",
  "module_owner": "EdTechMemoryLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "recall_id",
    "question",
    "answer"
  ],
  "optional_fields": [
    "title",
    "label",
    "label_icon",
    "think_prompt",
    "answer_label",
    "reveal_cta",
    "self_rating"
  ],
  "field_types": {
    "recall_id": "string — identifiant unique de la question de rappel",
    "question": "string — la question posée à l'apprenant pour forcer le rappel actif",
    "answer": "string — réponse révélée après effort de rappel",
    "think_prompt": "string — invite à réfléchir avant de révéler (optionnel, a un défaut)",
    "answer_label": "string — libellé de la zone réponse (optionnel, défaut : 'Réponse')",
    "reveal_cta": "string — libellé du bouton de révélation (optionnel, a un défaut)",
    "self_rating": "object{no:{label,value}, mid:{label,value}, ok:{label,value}} — boutons auto-évaluation"
  },
  "constraints": [
    "question et answer sont des champs pédagogiques : aucune valeur ne doit être inventée par le renderer.",
    "reveal_cta : bouton structurel (HTML), libellé venant du JSON.",
    "self_rating : trois niveaux obligatoires (no/mid/ok) si le champ est présent.",
    "Le contenu de answer n'est pas affiché avant l'action de l'utilisateur (rappel actif = effort d'abord)."
  ],
  "blocked_conditions": [
    "question absente",
    "answer absente",
    "recall_id absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "boutons libellés explicitement (pas d'icône seule)"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, question + zone révélation + auto-évaluation, 0 erreur" },
    { "case": "champ requis manquant (question, answer ou recall_id)", "expected": "BLOCKED listant le champ manquant" },
    { "case": "instance externe injectée via ENGINE.init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "self_rating absent", "expected": "rendu avec valeurs par défaut pour les 3 boutons" },
    { "case": "clic 'Révéler'", "expected": "zone réponse apparaît, bouton révéler masqué, boutons auto-évaluation visibles" },
    { "case": "clic bouton auto-évaluation (Raté/Presque/Réussi)", "expected": "réponse masquée, bouton révéler ré-affiché (cycle reset)" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, boutons auto-évaluation empilés sur mobile" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-104",
    "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)."
  }
}
