{
  "contract_version": "gab_093_contract_v1",
  "gab_id": "GAB-093",
  "canonical_name": "VisualExamRecap",
  "module_owner": "EdTechVisualLearning",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "exam_recap_id",
    "title",
    "tabs"
  ],
  "optional_fields": [
    "subject",
    "primary_cta"
  ],
  "field_types": {
    "tabs": "array<{tab_id:string, label:string, items:array<{icon:string, text:string}>}> — min 1 tab, min 1 item par tab",
    "primary_cta": "object{label:string, action:string}"
  },
  "constraints": [
    "tabs : tableau non vide, chaque tab contient tab_id + label + items[].",
    "Chaque item contient icon (emoji) + text (contenu affiché).",
    "primary_cta.label : libellé du bouton CTA structurel (vient du JSON, pas du HTML).",
    "Le premier tab est actif par défaut (index 0)."
  ],
  "blocked_conditions": [
    "tabs absent ou tableau vide",
    "exam_recap_id absent",
    "title absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "aria_labels_tabs"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, 3 onglets, 0 erreur" },
    { "case": "champ requis manquant (tabs absent)", "expected": "BLOCKED listant le champ" },
    { "case": "tabs vide []", "expected": "BLOCKED — tabs vides" },
    { "case": "instance externe injectée via init(ext)", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" },
    { "case": "navigation clavier entre onglets", "expected": "Tab + Enter/espace change l'onglet actif" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-093",
    "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)."
  }
}
