# GAB-226 · SmartSelectCandidateList — « Liste courte de candidats vérifiés »

**Archétype / renderer_key :** `text_cta` (cartographie) · **module :** EdTechSmartSelect
**Critère validé :** changer le JSON change la liste sans modifier le HTML. check.py 12/12.

## Pack (structure officielle par-GAB)
```
GAB-226/
  renderer.html            ← moteur liste candidats (ne pas modifier par instance)
  instance.example.json    ← SOURCE DE VÉRITÉ (contenu réel, à plat)
  schema.contract.json     ← contrat de validation
  README-contract.md       ← ce fichier
```

## Champs requis (instance, à plat)
`gab_id` · `candidate_list_id` · `title` · `summary` · `ranking_mode` · `items[]`

Optionnels : `reason`, `quality_gate_notice`, `primary_cta{label,action}`, `secondary_cta{label,action}`, `accessibility`, `child_safety`.

## Structure items[]
Chaque item : `rank` (entier) · `is_top` (booléen — exactement 1 true) · `title` · `description` · `module_type` (enum) · `estimated_duration` · `difficulty` · `score` (0..100).

## ranking_mode — enum strict 5 valeurs
`best_match` · `teacher_priority` · `recent_need` · `exam_priority` · `accessibility_first`

## Ce qui vient du JSON vs HTML
- **JSON** : titre de la liste, résumé, mode ranking affiché, raison du ranking, chaque item (titre, description, module_type, durée, difficulté, score), notice quality-gate, libellés CTAs.
- **HTML** : layout rows, icônes par module_type, chip rank, barre de slots, structure générale.

## Garde-fous (child_safety)
- **Server-side verify** : tous les candidats doivent être vérifiés côté serveur avant injection dans l'instance. Pas de ref non vérifiée en prod.
- **Anti-fake** : le moteur refuse d'afficher une liste si `items` est vide ou absent (BLOCKED propre).
- **No medical data** : `child_safety.no_medical_data:true` — aucun profil santé/handicap stocké.
- **BLOCKED** si `title` absent / `ranking_mode` absent / `items` vide ou absent.

## QA à vérifier
1. Modifier un `title`/`description` item → rendu change sans toucher au HTML (critère d'or).
2. `items:[]` → BLOCKED propre dans le panneau.
3. `ranking_mode` absent → BLOCKED listé.
4. CTA primaire → panneau OK avec action.
5. Instance externe `ENGINE.init(ext)` → rendu change.
6. Responsive 375/768/1024 : rows empilées sur mobile, 0 débordement.

## Archétype
Gabarit **liste courte de candidats vérifiés et classés** pour le moteur SmartSelect. 3 à 7 items max (pool court). Le ranking_mode pilote l'ordre et la mise en avant (is_top). Use_when : 3-7 candidats indexés, ranking explicable, refs filtrées server-side. Do_not_use_when : catalogue complet (autre composant), refs non vérifiées, priorité prof écrasée (→ GAB-227).

## Distinctions module (external_refs / dependencies)
- **GAB-227 SmartSelectTeacherOverride** : override prof IDOR-proof — quand teacher_priority, le top candidat est assigné par le prof (vérifié JWT). GAB-226 ne fait pas de vérification IDOR — déléguer à GAB-227 si teacher_priority actif.
- **GAB-230 SmartSelectEmptyChoice** : état vide honnête — si SmartSelect ne trouve aucun candidat fiable, utiliser GAB-230 plutôt que forcer une liste vide ou fake.
- **GAB-202 PlayEngineBlockStack** : exécution de pile. GAB-226 CHOISIT les candidats, GAB-202 EXÉCUTE. Les refs items peuvent pointer vers des refs play/<id> consommables par PlayEngine.

## Source
`INDEX-300-smartselect-GAB-226-230-PLAYABLE.html` (stage `data-tpl="226"`, handlers `clRanking`, section CSS `.cl-*`).
