{
  "contract_version": "gab_240_contract_v1",
  "gab_id": "GAB-240",
  "canonical_name": "FeedbackLearningNextLearningAction",
  "module_owner": "EdTechFeedbackLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "title",
    "summary",
    "next_action",
    "primary_cta"
  ],
  "optional_fields": [
    "next_learning_action_id",
    "next_action_type",
    "reason",
    "secondary_cta",
    "next_action_types_map",
    "no_reward_write",
    "accessibility"
  ],
  "field_types": {
    "title": "string — titre principal affiché dans le head et le hero",
    "summary": "string — sous-titre résumant le principe (1 action + alternative)",
    "next_action_type": "enum['retry','review','practice','advance','memory','ask_ketty','remediation'] — type actif par défaut",
    "next_action": "object{icon:string, label:string, module_chip_class:enum['exercise','visual','memory','adaptive','ketty'], module_chip_label:string}",
    "reason": "string — explication pédagogique pourquoi cette action (HTML inline autorisé, balise <b> uniquement)",
    "primary_cta": "object{label:string, action:string}",
    "secondary_cta": "object{label:string, action:string}",
    "next_action_types_map": "object<next_action_type, {icon,label,module_chip_class,module_chip_label,cta,reason}> — map complète des 7 types pour le sélecteur interactif",
    "no_reward_write": "boolean — toujours true (doctrine no-reward-write : ce GAB ne déclenche jamais XP/coins/badges)",
    "accessibility": "object{keyboard_navigable:boolean, focus_visible:boolean, prefers_reduced_motion:boolean}"
  },
  "constraints": [
    "next_action_type doit appartenir à l'enum défini (7 valeurs) si fourni.",
    "next_action.label : texte en langage élève, pas de note officielle, jamais de mention XP/reward.",
    "reason : HTML limité à <b> ; pas de script, pas de lien externe.",
    "no_reward_write : ce GAB pointe vers une ref, jamais ne marque l'activité comme terminée ni n'avance un compteur de progression.",
    "primary_cta.label : libellé bouton structurel venant du JSON, ne pas hardcoder dans le HTML.",
    "next_action_types_map optionnel mais recommandé pour le sélecteur interactif de démonstration."
  ],
  "blocked_conditions": [
    "title absent (BLOCKED)",
    "summary absent (BLOCKED)",
    "next_action absent (BLOCKED)",
    "primary_cta absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_labels_on_interactive_buttons"
  ],
  "qa_cases": [
    { "case": "instance conforme (type=retry)", "expected": "rendu complet, action-box retry visible, reason affichée, 0 erreur" },
    { "case": "champ requis manquant (ex: title absent)", "expected": "zone BLOCKED avec message listant le champ manquant" },
    { "case": "next_action_type=ask_ketty via enum-chip", "expected": "action-box mise à jour (icône 🦊, label Ketty, chip gold), CTA mis à jour" },
    { "case": "next_action_types_map absent", "expected": "sélecteur masqué, rendu de base fonctionnel" },
    { "case": "instance externe injectée via ENGINE.init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, CTAs empilés sur mobile" }
  ],
  "external_refs": {
    "gab_220": "AdaptiveNextStepBridge — bridge ADAPTATIF (signal AdaptiveLearning, 8 next_step_types). Ne pas confondre avec GAB-240 (feedback immédiat, 7 types).",
    "distinction": "GAB-240 = action IMMÉDIATE après feedback (feedback_state). GAB-220 = sortie ADAPTATIVE (signal AdaptiveLearning). NextActionCTA générique = pont neutre sans feedback ni signal."
  },
  "traceability": {
    "derived_from_core_gab": "GAB-240",
    "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)."
  }
}
