Indices et progression¶
Les indices (appelés hints dans le code) sont des messages courts
que l'étudiant peut afficher à la demande en cliquant sur un bouton 💡
dans l'overlay. Ils sont pensés comme une roue de secours
pédagogique : l'étudiant résout l'énigme sans eux tant qu'il y
arrive ; s'il bloque, le formateur peut l'y autoriser. Cette page décrit
le mécanisme à trois niveaux qui pilote la visibilité des indices : le
champ hintAvailable de chaque énigme, le toggle formateur en temps
réel, et l'affichage conditionnel côté React.
Les trois leviers de visibilité¶
Un bouton 💡 n'apparaît sur une énigme que si les trois conditions suivantes sont satisfaites en même temps :
flowchart LR
A["enigme.hintAvailable<br/>(booléen défini en Java)"] --> AND{ET}
B["pedagogiqueHintsEnabled<br/>(toggle formateur)"] --> AND
C["enigme.hint != null<br/>(texte résolu i18n)"] --> AND
AND --> OUI[💡 Bouton affiché]
AND -.->|manque 1|NON[Aucun bouton]
| Niveau de contrôle | Portée | Qui décide | Quand |
|---|---|---|---|
Par énigme — hintAvailable |
Une seule étape | Concepteur du catalogue (code Java) | À la définition du record Enigme |
Global session — pedagogiqueHintsEnabled |
Toutes les étapes du parcours actif | Formateur | À chaud, pendant le parcours, via un bouton chaos-admin |
Rendu client — absence de hint |
Une seule étape | Configuration i18n | Au chargement de la session React |
Les deux premiers leviers sont indépendants : désactiver le toggle
global masque les boutons pour toutes les énigmes, y compris celles
qui ont hintAvailable = true. Inversement, le toggle ne fait rien
apparaître pour une énigme dont hintAvailable = false.
Le champ hintAvailable par énigme¶
Dans le record Enigme, hintAvailable est un boolean défini à la
construction :
new Enigme("BAC3-17", "bac3", 17,
t("bac3", "BAC3-17.text"), "right",
t("bac3", "BAC3-17.hint"),
true, // ← hintAvailable
h("…"),
t("bac3", "BAC3-17.culturalNote"),
"checkout"
);
Aujourd'hui, toutes les 100 énigmes des cinq niveaux ont
hintAvailable = true. Le champ est conservé comme point d'extension :
un concepteur pourrait désactiver l'indice sur une énigme spécifique
(par exemple une épreuve finale chronométrée de concours) sans toucher
à la logique globale.
L'indice lui-même (enigme.hint) est résolu depuis le JSON i18n via
t(level, "BACx-n.hint"). Si la clé est absente du fichier, t()
retourne [BACx-n.hint] — visible à l'écran, ce qui signale
immédiatement la clé manquante au concepteur sans empêcher l'énigme
de fonctionner.
Le toggle formateur¶
Le toggle global est porté par le champ pedagogiqueHintsEnabled de
ChaosStudentController, déclaré volatile pour garantir la
visibilité entre threads :
Il est initialisé à true au démarrage de l'application et réinitialisé
à true à chaque deactivate ou changement de niveau — chaque
parcours commence donc avec les indices activés. Le formateur peut
basculer ce flag à tout moment via un endpoint dédié.
Endpoint POST /pedagogique/hints¶
@PostMapping("/pedagogique/hints")
public ResponseEntity<?> setHintsEnabled(@RequestBody Map<String, Object> body,
HttpSession session, @RequestHeader(value = "X-Admin-Token", required = false) String tok) {
if (!AdminAuth.isAdmin(session, tok)) return unauth();
Object val = body.get("enabled");
if (!(val instanceof Boolean))
return ResponseEntity.badRequest().body(Map.of("error",
i18n.t("student.pedagogique.error.hints_enabled_required")));
pedagogiqueHintsEnabled = (Boolean) val;
log.info("[Pédagogique] Indices {} par le formateur",
pedagogiqueHintsEnabled ? "activés" : "masqués");
return ResponseEntity.ok(Map.of("success", true,
"hintsEnabled", pedagogiqueHintsEnabled));
}
| Propriété | Valeur |
|---|---|
| Méthode | POST |
| URL | /api/chaos/student/pedagogique/hints |
| Authentification | Header X-Admin-Token ou session admin |
| Body | {"enabled": true|false} |
| Réponse 200 | {"success": true, "hintsEnabled": <bool>} |
| Réponse 400 | Si enabled est absent ou pas un booléen |
| Réponse 401 | Si le token admin est manquant ou invalide |
La validation du body refuse explicitement tout ce qui n'est pas un
booléen — un client envoyant {"enabled": "true"} (chaîne) ou
{"enabled": 1} (entier) reçoit un 400.
Propagation aux étudiants¶
Contrairement à d'autres paramètres, le toggle n'est pas push
vers les étudiants : il est relu à chaque poll dans la réponse
/status. Cette stratégie « pull » simplifie l'architecture (pas de
WebSocket, pas de SSE) et reste suffisamment réactive puisque le
polling s'exécute toutes les 15 secondes.
sequenceDiagram
autonumber
actor F as Formateur
participant ADM as chaos-admin
participant CTR as ChaosStudentController
actor E1 as Étudiant 1
actor E2 as Étudiant 2
Note over E1,E2: Polling en cours<br/>(15s d'intervalle)
F->>ADM: Clique "Masquer les indices"
ADM->>CTR: POST /pedagogique/hints {enabled:false}
CTR->>CTR: pedagogiqueHintsEnabled = false
CTR-->>ADM: 200 {hintsEnabled:false}
Note over ADM: UI confirme "masqué"
par Prochains polls étudiants
E1->>CTR: GET /status
CTR-->>E1: pedagogique.hintsEnabled = false
E1->>E1: Bouton 💡 disparaît
and
E2->>CTR: GET /status
CTR-->>E2: pedagogique.hintsEnabled = false
E2->>E2: Bouton 💡 disparaît
end
Note over F: Formateur voit que<br/>tous les élèves ont compris
F->>ADM: Clique "Afficher les indices"
ADM->>CTR: POST /pedagogique/hints {enabled:true}
CTR->>CTR: pedagogiqueHintsEnabled = true
par Polls suivants
E1->>CTR: GET /status
CTR-->>E1: pedagogique.hintsEnabled = true
E1->>E1: Bouton 💡 réapparaît
and
E2->>CTR: GET /status
CTR-->>E2: pedagogique.hintsEnabled = true
end
Latence observée¶
La latence entre le clic formateur et la disparition du bouton 💡 chez l'étudiant est au pire de 15 secondes (intervalle de polling) plus le temps de réponse du backend (quelques millisecondes). En pratique, comme les polls de plusieurs étudiants ne sont pas synchronisés entre eux, la disparition est progressive sur toute la classe en moins d'un cycle complet.
Cette asynchronicité est un avantage pédagogique : si le formateur active les indices pour « débloquer » un étudiant précis, il n'y a pas d'effet visible immédiat sur l'ensemble de la classe qui pourrait parasiter la concentration des autres.
Affichage conditionnel côté React¶
Le champ hintsEnabled est propagé jusqu'à PedagogiqueOverlay via
la prop :
<PedagogiqueOverlay
enigme={state.enigme}
step={state.step}
totalSteps={state.totalSteps}
onValidated={refresh}
timerExpired={timerExpired}
skipPollingRef={skipPollingRef}
hintsEnabled={state.hintsEnabled !== false}
/>
Le test !== false est volontaire : il traite undefined et null
comme « activé » par défaut, afin d'être rétrocompatible avec les
sessions qui n'incluraient pas encore le champ (par exemple pendant un
rollback partiel frontend/backend).
L'overlay rend le bouton 💡 uniquement si les trois conditions sont remplies :
{enigme.hintAvailable && hintsEnabled && (
<div style={{ marginBottom: '10px' }}>
<button onClick={() => setHintVisible(v => !v)}>
💡 {hintVisible ? t('ped.hideHint') : t('ped.showHint')}
</button>
{hintVisible && enigme.hint && (
<div>{enigme.hint}</div>
)}
</div>
)}
Le bouton affiche ou masque l'indice en basculant un état local
(hintVisible) — le texte n'est jamais masqué côté serveur une fois
envoyé, c'est uniquement un état d'affichage. Le state hintVisible
est remis à false automatiquement à chaque changement d'étape via
le useEffect([enigme?.id]) de l'overlay.
Interaction avec le thème Logique¶
Le thème final Logique & Mathématiques (voir themes.md)
dispose de son propre mécanisme d'indices indépendant du toggle
formateur. Chaque question du pool de 25 est accompagnée d'un indice
court, stocké dans le champ hint du fichier logique_fr.json, et
affiché via un bouton 💡 local à la question. Le toggle global
pedagogiqueHintsEnabled n'affecte pas cette page.
La raison est fonctionnelle : la page de succès et le thème final s'affichent après la fin du parcours principal. À ce stade, la notion de « classe en cours » a perdu son sens — l'étudiant est sur son écran de fin, le formateur ne pilote plus rien. Lui couper l'accès aux indices du thème final serait un faux signal.
Clés i18n concernées¶
Pour traduire ou adapter les messages liés aux indices, cibler :
| Clé | Fichier | Usage |
|---|---|---|
ped.showHint |
frontend/src/i18n/fr.json et en.json |
Libellé du bouton quand l'indice est masqué |
ped.hideHint |
frontend/src/i18n/fr.json et en.json |
Libellé quand l'indice est visible |
BACx-n.hint |
backend/src/main/resources/i18n/enigmes/bacX/enigmes_XX.json |
Texte de chaque indice individuel |
student.pedagogique.error.hints_enabled_required |
messages_fr.properties et messages_en.properties |
Erreur 400 si body mal formé |
Pages suivantes : ← Système d'étoiles · Page de succès → · Thèmes finaux →