Show "Exercise N/3" so repeated content doesn't look like a freeze

Manual report: after "Bravo!", clicking Continue seemed to do
nothing. An automated test proved the mission does advance (a new
exercise instance loads, completed_count increments) -- but
mission_02_pont_des_phrases has exactly one sentence_correction
exercise in the data right now, so the "next" exercise is the
identical sentence the child just solved. Nothing was broken; there
was just no visible signal that progress happened.

Added a persistent progress label at the top of exercise_host,
updated on every exercise load. Doesn't address the underlying
content-volume gap (still tracked in decisions.md point 14), but
makes the repetition legible instead of indistinguishable from a
stall.

Rebuilt both exports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 18:18:40 +02:00
parent 91dcd7e07a
commit ffb3cd5afa
5 changed files with 57 additions and 1 deletions
+1
View File
@@ -30,6 +30,7 @@
"exercise_host.validate_button": "Valider",
"exercise_host.continue_button": "Continuer",
"exercise_host.finish_mission_button": "Terminer la mission",
"exercise_host.progress_label": "Exercice",
"feedback_panel.correct_title": "Bravo !",
"feedback_panel.incorrect_title": "Pas tout à fait",
"feedback_panel.hint_label": "Indice :",
+8
View File
@@ -146,3 +146,11 @@ Ajouts à l'arborescence proposée :
**Correction** : la comparaison passe maintenant par `TextNormalizer.normalize(...).to_lower()` des deux côtés (même normalisation que pour les dictées), tout en gardant le texte brut saisi dans `found` pour l'affichage du retour. Un test automatisé avec la séquence exacte rapportée (toucher une fois, taper, valider) n'avait pas reproduit le bug — preuve que la logique fonctionnait pour une saisie identique au caractère près ; c'est la tolérance aux variations incidentes qui manquait, pas la logique d'association mot/correction elle-même. Trois tests couvrent maintenant la casse, les espaces superflus et les accents décomposés.
**Leçon** : toute comparaison de texte saisi par l'enfant doit passer par `TextNormalizer`, pas par une égalité de chaîne brute — à vérifier si d'autres types d'exercices à saisie libre sont ajoutés plus tard.
## 17. Bug signalé manuellement : « Bravo » sans suite visible
**Symptôme** : après « Bravo ! » sur la mission « Le pont des phrases », cliquer sur « Continuer » semblait ne rien faire.
**Diagnostic** : un test automatisé confirme que la mission avance bien (un nouvel exercice est réellement chargé, `completed_count` augmente). Le contenu pédagogique de cette mission ne contient qu'une seule correction de phrase (`ce1_accord_adjectif_correction_01`, voir le point 14 sur le volume de contenu encore minimal) : le deuxième exercice de la mission est donc **exactement la même phrase**, ce qui donne l'impression visuelle de n'avoir pas bougé alors que la mission en est à l'exercice 2 sur 3.
**Correction** : ajout d'un repère visuel permanent (« Exercice N/3 ») en haut de `exercise_host`, mis à jour à chaque exercice chargé. Ne corrige pas le manque de contenu (qui reste à écrire), mais rend la répétition explicite plutôt que silencieuse.
+11
View File
@@ -26,6 +26,7 @@ var _last_exercise_id := ""
@onready var _exercise_container: Control = %ExerciseContainer
@onready var _overlay_container: Control = %OverlayContainer
@onready var _progress_label: Label = %ProgressLabel
## Accesseurs reserves aux tests d'integration (scenes/exercises/
@@ -67,6 +68,7 @@ func _load_next_exercise() -> void:
_mission, _last_exercise_id, _current_difficulty
)
_attempt_number = 1
_update_progress_label()
if _current_exercise_scene != null:
_current_exercise_scene.queue_free()
@@ -78,6 +80,15 @@ func _load_next_exercise() -> void:
_connect_answer_signal()
## Un contenu encore peu nombreux (voir docs/specs/decisions.md, point 14)
## peut faire redonner le meme exercice d'une mission a l'autre tentative :
## sans ce repere, ca ressemble a un blocage plutot qu'a une progression.
func _update_progress_label() -> void:
_progress_label.text = "%s %d/%d" % [
UIText.get_text("exercise_host.progress_label"), _completed_count + 1, MISSION_LENGTH
]
func _connect_answer_signal() -> void:
match _mission["exercise_type"]:
"multiple_choice":
+16 -1
View File
@@ -11,12 +11,27 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="HeaderBar" type="MarginContainer" parent="."]
layout_mode = 1
anchor_right = 1.0
offset_bottom = 40.0
grow_horizontal = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 8
[node name="ProgressLabel" type="Label" parent="HeaderBar"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 2
[node name="ExerciseContainer" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 40.0
grow_horizontal = 2
grow_vertical = 2
@@ -58,3 +58,24 @@ func test_correct_word_tap_and_retype_validates_through_exercise_host() -> void:
var skill_state := ProgressManager.get_skill_state("accord_groupe_nominal")
assert_eq(skill_state["recent_results"], [true])
# "Bravo pas de suite ici ?" : cliquer sur Continuer doit faire avancer
# la mission (deuxieme exercice charge), pas rester bloque sur le meme
# panneau.
var old_exercise_scene: Control = exercise_scene
feedback.get_node("%ContinueButton").pressed.emit()
await wait_process_frames(1)
assert_eq(host.get_completed_count(), 1, "le premier exercice doit compter comme termine")
assert_eq(overlay.get_child_count(), 0, "le panneau de retour doit disparaitre")
var new_exercise_scene: Control = host.get_node("%ExerciseContainer").get_child(0)
assert_ne(
new_exercise_scene,
old_exercise_scene,
"une nouvelle instance d'exercice doit remplacer la precedente",
)
assert_eq(host.get_attempt_number(), 1, "le nouvel exercice repart a la premiere tentative")
var progress_label: Label = host.get_node("%ProgressLabel")
assert_string_contains(progress_label.text, "2/3")