{
  "contract_version": "gab_192_contract_v1",
  "gab_id": "GAB-192",
  "canonical_name": "PathMapProgressTraveler",
  "module_owner": "EdTechPathMap",
  "renderer_key": "text_cta",
  "required_fields": [
    "gab_id",
    "traveler_id",
    "title",
    "node_start_label",
    "node_end_label",
    "traveler_emoji",
    "progress_current",
    "progress_total",
    "progress_percent",
    "primary_cta"
  ],
  "optional_fields": [
    "subtitle",
    "node_start_emoji",
    "node_end_emoji",
    "feedback_header",
    "feedback_text",
    "progress_label",
    "replay_label",
    "reduced_motion_note"
  ],
  "field_types": {
    "traveler_id": "string — identifiant unique de l'instance de voyage",
    "title": "string — titre principal affiché en haut de la carte",
    "subtitle": "string — sous-titre décrivant la mission validée",
    "node_start_emoji": "string (emoji) — icône du nœud de départ",
    "node_start_label": "string — libellé du nœud de départ (ex: 'CAUSES ✓')",
    "node_end_emoji": "string (emoji) — icône du nœud d'arrivée",
    "node_end_label": "string — libellé du nœud d'arrivée",
    "traveler_emoji": "string (emoji) — icône du voyageur animé",
    "feedback_header": "string — en-tête du bloc feedback (validation reçue)",
    "feedback_text": "string — message de feedback orientant vers l'étape suivante",
    "progress_label": "string — libellé de la barre de progression (ex: 'Parcours')",
    "progress_current": "integer ≥ 0 — étape actuelle",
    "progress_total": "integer > 0 — nombre total d'étapes",
    "progress_percent": "integer [0..100] — pourcentage affiché",
    "primary_cta": "object{label:string, action:string}",
    "replay_label": "string — libellé du bouton rejouer",
    "reduced_motion_note": "string — note affichée sur le respect de prefers-reduced-motion"
  },
  "constraints": [
    "progress_current doit être ≥ 1 (une mission vient d'être validée).",
    "progress_percent doit être cohérent avec progress_current/progress_total (± tolérance d'arrondi).",
    "primary_cta est le bouton structurel de navigation ; son libellé vient du JSON.",
    "traveler_emoji et node_start/end_emoji : strings non vides ; défaut '🦊' si absent.",
    "Interdit : afficher le voyageur si la mission n'est pas validée côté serveur (mouvement sans état réel)."
  ],
  "blocked_conditions": [
    "traveler_id absent",
    "title absent",
    "node_start_label absent",
    "node_end_label absent",
    "traveler_emoji absent",
    "primary_cta absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion — animation CSS désactivée via @media, fallback statique garanti",
    "aria_labels sur les nœuds et le voyageur",
    "texte alternatif sur les emojis fonctionnels"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, animation lancée, 0 erreur" },
    { "case": "champ requis manquant (ex: traveler_id)", "expected": "BLOCKED listant le champ absent" },
    { "case": "primary_cta absent", "expected": "BLOCKED — bouton non rendu" },
    { "case": "instance externe injectée via init(ext)", "expected": "le rendu change sans modifier le HTML" },
    { "case": "prefers-reduced-motion activé", "expected": "animation CSS inactive, voyageur affiché position finale" },
    { "case": "replay cliqué", "expected": "animation relancée depuis le début" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal, chemin visible" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-192",
    "note": "Ce schéma 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-pathmap-GAB-191-195-PLAYABLE.html, stage data-tpl='192'."
  }
}
