Repository navigation
Question type conversion - #6311
Draft
AlexVelezLl wants to merge 10 commits into
Draft
AlexVelezLl wants to merge 10 commits into
AlexVelezLl wants to merge 10 commits into
Conversation
AlexVelezLl
force-pushed
the
question-type-conversion
branch
from
October 13, 2026 14:18
a234108 to
eb36df2
Compare
4 tasks
AlexVelezLl
force-pushed
the
question-type-conversion
branch
from
October 13, 2026 14:41
eb36df2 to
d7d0bcb
Compare
AlexVelezLl
force-pushed
the
question-type-conversion
branch
from
October 13, 2026 15:43
d7d0bcb to
bd4a38e
Compare
AlexVelezLl
force-pushed
the
question-type-conversion
branch
2 times, most recently
from
October 15, 2026 10:42
fcfebf4 to
71983f2
Compare
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Changing a question's type carries what the question holds into the new type, and names what that would remove or change so the author can confirm first. This adds the part that knows no interaction: each descriptor will export its state into neutral facets (answers, pairs, a passage) and import from them, and the orchestrator moves the facets a type takes, translates answer keys and content levels, and works out the losses from one table. A facet the new type doesn't take is reported as a loss, so a type that takes nothing, as the base descriptor's defaults do, loses the rest of the question only with a warning. A passage a type doesn't take is folded into the question instead, since it is often the whole question. Nothing calls this yet. The unused convertsFrom placeholder on the descriptors goes, replaced by the conversion methods. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Single and multi select, numeric, text entry, free response and ordering all hold a list of answers, and differ in what makes one correct. Each now exports and imports that list, so a question keeps its answers across a type change, by the rules in one table: - only the correct choices become accepted numeric or text entry answers, since a wrong one would become an accepted one - a question becoming single choice keeps only its first correct answer marked, since radio buttons can't show more than one - answers ordering can't mark correct, and an order that becomes the answer, are reported so the author can confirm first Answers that aren't numbers stay when switching to numeric, for validation to flag. The settings a list carries travel to the types that have them: shuffle between single and multi select, fixed answers and orientation between choice and ordering, and the expected length between the text entry types. Ordering's shuffle isn't the author's to set, and stays on. The tests convert a filled question of each type to every other type, against one table of the losses each change brings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Associate and match questions both pair answers up, so each now exports and imports its pairs and distractors, and switching between them keeps both. A match row with several matches becomes one associate pair per match, and associate pairs from the same source become one match row, so a row that went to associate comes back as the row it was. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An inline choice passage is often the whole question, since the question field above it is optional. Converting it to a type with no passage now folds the passage into the question, each dropdown shown as a blank, rather than dropping it. Its dropdowns travel as neutral blanks, so another passage type can take them without knowing inline choice's markup. Importing a passage gives each dropdown everything a learner could have chosen for that blank, as gap match does with its one shared pool, and the blank's own answer as its correct option. Gap match (learningequality#6203) only has to export and import the same blanks for the two to convert into each other. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Before a type change removes or changes part of a question, the author is asked to confirm it, with one full sentence for each thing that would go or change, under a line naming the new type. Each sentence is a string of its own, so translators never get fragments to join. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each interaction editor opened and focused its question as soon as it mounted in edit mode. That suited a card being opened, but any other mount took focus too: an editor remounted for a new question type pulled focus away from the type selector the author was using. Focus is now asked for, not implied. An editor exposes focus(), which opens its question for editing and so focuses it, and the card calls it, through InteractionSection, when it opens or is created open: the moments it already scrolls itself into view. An editor that only mounts leaves its question closed and focus where it was. The card asks after its section has re-rendered, so an editor that opens its question only in edit mode, as associate and match do, has heard of the mode change by then. TipTapEditor, switched into edit mode before it is ready, now focuses once it is. Its content only joins the page then, and a focus before that was lost: a new question's editor opened its question at once, but the question took no focus. Editors used to be created with autofocus already set, which TipTap applies once ready, so nothing hit this before. A test over the editor registry checks that every editor focuses its question when asked, so a new one can't miss it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Changing a question's type now converts what the question holds, instead of keeping only the question and dropping the answers without a word. A change that would remove or change part of the question first asks the author to confirm, saying what; one that loses nothing switches straight away. While the modal is open, the type selector shows the type the author picked, so cancelling puts it back and lets them pick that type again. KSelect keeps its own selection and ignores a pick of the option it already holds. The conversion runs again on confirm, from what the question holds then. Every type change now remounts the interaction editor from the converted question, including single and multi select and the text entry types, which used to keep the editor's own state. A free response therefore really clears its answers, rather than keeping them in memory until the next reload. The text entry editor no longer watches its question type: it never sees it change now, as a change remounts it. Its watch moved Numeric answers between how the exercise language writes them and how they are stored, and the remount does the same: leaving Numeric starts the next editor from the values stored, and opening on Numeric shows them in the language, reading text it can. The tests for it switch the type through the section now, where conversion and remount happen. Focus stays on the type selector through a type change. The remounted editor takes none, and before the modal opens the select is focused, so the modal returns focus to it rather than to the dropdown option that was picked, gone by then. Closes learningequality#6190 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A question keeps its content across a type change only if its descriptor exports it, and anything left out is lost without a warning. Every registered interaction now has to have a filled question in the conversion tests, and has to export every word that question holds, from its body or as accepted answers, so a new interaction, or a new field on an existing one, can't be missed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A numeric answer is stored as xsd:double but shown in the exercise language: 11.2 shows as 11,2 in French. Converting the question to another type carried the stored form, so a French author who switched to text entry found 11.2, an answer French learners wouldn't type, in place of the 11,2 they had seen. A round trip from text entry through numeric changed the accepted answer the same way. Numeric answers now leave as they were shown, in the exercise language, which the conversion is given for that. Switching into numeric is unchanged: the remounted editor reads the answers in the language. This reverses what learningequality#6282 chose for leaving numeric, which kept the stored form so learners could type it. The language's own conventions, regional digits included, are what its learners most likely type. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AlexVelezLl
force-pushed
the
question-type-conversion
branch
from
October 15, 2026 15:25
71983f2 to
f425d85
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Changing a question's type used to keep only the question and drop the answers without warning. Now the conversion keeps everything the new type can hold. When the change would also remove something, or change what counts as correct in a way validation can't catch, the editor first asks for confirmation in a modal that lists each change in one sentence. A change that loses nothing switches straight away.
_____, since the question field above it is optional.3,5in a German exercise), since that's what the author sees in the numeric editor. The exception is single choice, which keeps only its first correct answer marked, because radio buttons can't show more than one.focus()method on each editor, when it opens or when a new question is added.TipTapEditornow also focuses once it's ready when it was switched into edit mode before then; without that, a new question's editor would have opened its question without focusing it.Conversion rules
Each cell lists what the modal says when converting a filled question from the row's type to the column's type. "—" means nothing is lost, so there's no modal. A sentence only shows when it applies to the question's actual content. For example, "Answers not marked as correct will be removed" only shows if there is a non-empty answer that isn't marked correct.
Where this differs from the old editor's conversion:
The rules live in one table in
conversion/answerKeys.js.EXPECTED_LOSSESinconversion/__tests__/convertInteraction.spec.jschecks the table above for every pair.References
Closes #6190. Gap match (#6202) only needs its own export and import (#6203) to convert to and from inline choice.
Reviewer guidance
KSelect's publicfocus()(KSelect: Add a public focus() method kolibri-design-system#1354), which keeps focus on the type selectorfocus()(includes theTipTapEditorfix)InteractionSection. The remount replaces Read and show Numeric answers in the exercise language #6282'squestionTypewatch inuseTextEntryInteraction, so that watch is removed and its tests moved toInteractionSection.spec.unstableyet. Converting from inline choice is covered by tests here, and authors get it once Add the inline choice interaction editor #6303 merges. Its editor will also need afocus(); the registry test in step 7 will fail until it has one.importInlineChoiceContent,buildDropdown) andcarryPassageonly run when a passage goes into another passage type, and inline choice is the only one until gap match ([QTI] Gap match interaction: descriptor, parsing, XML assembly and validation #6203). They're exercised by tests only for now, kept so every family uses the same mechanism and gap match only adds its own export and import.conversion/folder sits besideinteractions/rather than inside it, since every folder ininteractions/is a plugin.Modal, confirm and cancel:
Keyboard only:
AI usage
Claude Code researched the options, wrote the plan with me, implemented it with tests, self-reviewed it, and addressed my review. I made the conversion decisions (which answers carry over, when to warn, folding the passage) and reviewed the changes.
🤖 Generated with Claude Code