Move distractor explanations inline instead of a repeated bottom section

The "WHY OTHER OPTIONS ARE INCORRECT" aggregate section duplicated
explanations that selected wrong choices already showed inside their
own resolved follow-up quiz. Removed that section entirely; choices
the user never picked now get their "why incorrect" explanation
docked directly inside their own card (reusing the same followup
panel/attached-card styling), so every distractor's explanation lives
with its choice instead of in a separate list at the bottom.
This commit is contained in:
hz
2026-07-23 02:01:29 -05:00
parent 4db5b9d374
commit 59ffaf4376
+41 -82
View File
@@ -73,10 +73,7 @@ var correct_review_panel: PanelContainer
var teaching_cards: HFlowContainer
var review_followups_section: VBoxContainer
var review_followups_content: VBoxContainer
var wrong_options_section: VBoxContainer
var wrong_options_content: VBoxContainer
var review_followup_states: Array = []
var wrong_option_expanded := {}
var question_scroll: ScrollContainer
var question_elo_label: Label
var next_button: Button
@@ -635,19 +632,6 @@ func _build_answer_review_ui() -> void:
review_followups_content.add_theme_constant_override("separation", 8)
review_followups_section.add_child(review_followups_content)
wrong_options_section = VBoxContainer.new()
wrong_options_section.visible = false
wrong_options_section.add_theme_constant_override("separation", 8)
question_content.add_child(wrong_options_section)
var wrong_options_heading := Label.new()
wrong_options_heading.text = "WHY OTHER OPTIONS ARE INCORRECT"
wrong_options_heading.name = "WrongOptionsHeading"
_set_scaled_label(wrong_options_heading, 15, Brand.text_color(true))
wrong_options_section.add_child(wrong_options_heading)
wrong_options_content = VBoxContainer.new()
wrong_options_content.add_theme_constant_override("separation", 6)
wrong_options_section.add_child(wrong_options_content)
func _fit_modal_panels() -> void:
var available := get_viewport_rect().size - Vector2(32, 32)
@@ -875,19 +859,6 @@ func _review_followup_box(_dark: bool, accent: Color, answered: bool) -> StyleBo
return box
func _wrong_option_box(dark: bool) -> StyleBoxFlat:
var box := StyleBoxFlat.new()
box.bg_color = Color("0b172b") if dark else Color("ffffff")
box.border_color = Color("2b3958") if dark else Brand.LIGHT_BORDER
box.set_border_width_all(1)
box.set_corner_radius_all(6)
box.content_margin_left = 10
box.content_margin_right = 10
box.content_margin_top = 6
box.content_margin_bottom = 6
return box
func _style_review_followup_option(button: Button, dark: bool, option_id: String, selected_id: String, correct_id: String, answered: bool) -> void:
var state := "default"
if answered and option_id == correct_id:
@@ -929,9 +900,6 @@ func _apply_question_theme() -> void:
var review_followup_subtitle := find_child("ReviewFollowupSubtitle", true, false) as Label
if review_followup_subtitle != null:
review_followup_subtitle.add_theme_color_override("font_color", Brand.muted_color(dark))
var wrong_options_heading := find_child("WrongOptionsHeading", true, false) as Label
if wrong_options_heading != null:
wrong_options_heading.add_theme_color_override("font_color", Brand.text_color(dark))
if correct_review_panel != null:
correct_review_panel.add_theme_stylebox_override("panel", _correct_review_panel_box(dark))
question_elo_label.add_theme_color_override("font_color", Brand.AMBER)
@@ -975,7 +943,7 @@ func _apply_question_theme() -> void:
if correct_review_panel != null and correct_review_panel.visible:
_render_teaching_cards()
_render_review_followups()
_render_wrong_option_reviews()
_render_unselected_distractor_explanations()
func on_show(_payload: Variant = null) -> void:
@@ -1542,7 +1510,7 @@ func _dock_correct_review_panel(correct_id: String) -> void:
func _render_answer_review_sections() -> void:
_render_teaching_cards()
_render_review_followups()
_render_wrong_option_reviews()
_render_unselected_distractor_explanations()
func _clear_answer_review() -> void:
@@ -1552,13 +1520,9 @@ func _clear_answer_review() -> void:
correct_review_panel.visible = false
_clear_container_children(teaching_cards)
_clear_container_children(review_followups_content)
_clear_container_children(wrong_options_content)
review_followup_states.clear()
wrong_option_expanded.clear()
if review_followups_section != null:
review_followups_section.visible = false
if wrong_options_section != null:
wrong_options_section.visible = false
_update_content_vertical_alignment()
@@ -1699,58 +1663,53 @@ func _answer_review_followup(index: int, choice_id: String) -> void:
_render_review_followups()
func _render_wrong_option_reviews() -> void:
if wrong_options_content == null:
return
_clear_container_children(wrong_options_content)
func _render_unselected_distractor_explanations() -> void:
# Choices the user never picked (so never triggered the wrong-answer
# follow-up quiz) still need their "why incorrect" explanation shown -
# nest it directly in that choice's own card instead of a repeated
# aggregate list at the bottom. Choices that DID trigger a follow-up
# already show this same explanation inside the resolved quiz view.
var question := BlockSessionService.get_current_question()
var correct_id := str(question.get("correct_choice_id", ""))
var explanations: Dictionary = question.get("choice_explanations", {})
var incorrect_count := 0
for choice in _get_presented_choices():
var choices := _get_presented_choices()
for index in range(choices.size()):
if index >= choice_followup_panels.size():
continue
var choice: Dictionary = choices[index]
if typeof(choice) != TYPE_DICTIONARY:
continue
var choice_id := str(choice.get("id", ""))
if choice_id == correct_id:
if choice_id == "" or choice_id == correct_id or followup_states.has(choice_id):
continue
incorrect_count += 1
_add_wrong_option_review(choice, str(explanations.get(choice_id, "")))
wrong_options_section.visible = incorrect_count > 0
_clear_followup_content(index)
var content := choice_followup_contents[index]
var body_content := _add_followup_body(content, false)
var heading := Label.new()
heading.text = "Why this is incorrect:"
_set_scaled_label(heading, 14, Color("fb5f6d"))
body_content.add_child(heading)
var explanation := Label.new()
explanation.text = _distractor_explanation_text(choice_id)
explanation.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_set_scaled_label(explanation, 14, Brand.text_color(_followup_is_dark()))
body_content.add_child(explanation)
choice_followup_panels[index].add_theme_stylebox_override(
"panel",
_attached_panel_box(_followup_is_dark(), Color("fb7185"))
)
choice_followup_panels[index].visible = true
_style_choice_button_for_state(index, choice_id)
func _add_wrong_option_review(choice: Dictionary, explanation: String) -> void:
var dark := _followup_is_dark()
var choice_id := str(choice.get("id", ""))
var expanded := bool(wrong_option_expanded.get(choice_id, false))
var card := PanelContainer.new()
card.add_theme_stylebox_override("panel", _wrong_option_box(dark))
wrong_options_content.add_child(card)
var content := VBoxContainer.new()
content.add_theme_constant_override("separation", 6)
card.add_child(content)
var header := Button.new()
header.flat = true
header.alignment = HORIZONTAL_ALIGNMENT_LEFT
header.text = "%s. %s" % [_display_label_for_choice_id(choice_id), str(choice.get("text", ""))]
header.icon = preload("res://assets/icons/lucide/chevron-down.svg") if expanded else preload("res://assets/icons/lucide/chevron-right.svg")
header.icon_alignment = HORIZONTAL_ALIGNMENT_RIGHT
header.expand_icon = false
header.add_theme_color_override("font_color", Brand.text_color(dark))
header.add_theme_color_override("icon_normal_color", Brand.muted_color(dark))
_set_scaled_button_font(header, 14)
header.pressed.connect(_toggle_wrong_option_review.bind(choice_id))
content.add_child(header)
if expanded:
var detail := Label.new()
detail.text = explanation
detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_set_scaled_label(detail, 14, Brand.muted_color(dark))
content.add_child(detail)
func _toggle_wrong_option_review(choice_id: String) -> void:
wrong_option_expanded[choice_id] = not bool(wrong_option_expanded.get(choice_id, false))
_render_wrong_option_reviews()
func _distractor_explanation_text(choice_id: String) -> String:
var question := BlockSessionService.get_current_question()
var explanations: Dictionary = question.get("choice_explanations", {})
var explanation := str(explanations.get(choice_id, "")).strip_edges()
if explanation.begins_with("Correct. "):
explanation = explanation.substr(9)
if explanation != "":
return explanation
return "Review the stem finding that makes this option less appropriate."
func _strip_correct_prefix(value: String) -> String: