{
  "contract_version": "gab_386_contract_v1",
  "gab_id": "GAB-386",
  "canonical_name": "MediaLearningMediaGallery",
  "module_owner": "EdTechMediaLearning",
  "renderer_key": "media_viewer",
  "required_fields": [
    "gab_id",
    "gallery_id",
    "items",
    "pedagogical_order",
    "broken_item_fallback"
  ],
  "optional_fields": [
    "title",
    "pagination",
    "mobile_perf"
  ],
  "field_types": {
    "gallery_id": "string — identifiant unique de la galerie",
    "pedagogical_order": "boolean — true = ordre défini par items[].order",
    "items": "array<{id,label,icon,source_rights,alt,order:number,broken:boolean}>",
    "broken_item_fallback": "string — message affiché si un item est cassé",
    "pagination": "boolean — activer la pagination côté rendu",
    "mobile_perf": "boolean — optimisation mobile activée"
  },
  "constraints": [
    "items doit contenir au moins 1 élément.",
    "Chaque item porte source_rights et alt — jamais omis ; si inconnu, utiliser '_TODO:...'.",
    "L'item cassé (broken:true) doit être rendu avec le fallback sans bloquer les autres.",
    "pedagogical_order:true impose que les items soient rendus selon items[].order croissant.",
    "Un item sans source_rights explicite ne peut pas être rendu sans avertissement."
  ],
  "blocked_conditions": [
    "items absent ou vide (BLOCKED)",
    "gallery_id absent (BLOCKED)",
    "broken_item_fallback absent (BLOCKED)"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "alt_text_per_item",
    "broken_item_communicated"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "grille rendue, 4 items, item cassé avec fallback, 0 erreur" },
    { "case": "items vide ou absent", "expected": "BLOCKED listant le champ" },
    { "case": "gallery_id absent", "expected": "BLOCKED listant gallery_id" },
    { "case": "item broken:true", "expected": "placeholder + fallback texte, les autres items restent accessibles" },
    { "case": "instance externe injectée", "expected": "le rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-386",
    "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)."
  }
}
