Skip to content

Question type conversion - #6311

Open
AlexVelezLl wants to merge 6 commits into
learningequality:unstablefrom
AlexVelezLl:question-type-conversion
Open

AlexVelezLl wants to merge 6 commits into
learningequality:unstablefrom
AlexVelezLl:question-type-conversion

Conversation

@AlexVelezLl

@AlexVelezLl AlexVelezLl commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Summary

Changing a question's type used to keep only the question and drop the answers silently. Now answers carry over wherever the new type can hold them. When a change would remove something, or change what counts as correct, a modal lists each change in one sentence and asks for confirmation. A change that loses nothing switches straight away.

  • Answers carry over within three families: answer lists (single, multi, numeric, text entry, free response, ordering), pairs (associate ↔ match) and passages (inline choice ↔ gap match). Across families, only the question carries over.
  • Content the new type can show is kept even if it isn't valid there, and validation points it out.
  • Editors focus their question only when the card asks, through a focus() method, so focus stays on the type selector through a type change.

Gap match can't be picked yet. Its conversions are in place, but gap match has no editor or type option until #6204, so authors can't reach them and only the tests cover them. Making it selectable before its editor exists would leave the question with nothing to edit.

The conversion rules and the decisions behind them are in #6190.

References

Closes #6190. Builds on the inline choice editor (#6303) and the gap match descriptor (#6323).

Reviewer guidance

  • Commits, each reviewable on its own:
    1. Conversion core: rules table, passage folding, descriptor defaults
    2. Each interaction's conversion module, and the parse factories they build new pieces with
    3. Editors focus their question only when the card asks (includes a TipTapEditor fix). Editors used to focus their question whenever they mounted in edit mode, so the remount after a type change took focus away from the type selector, which [QTI] Convert answers or warn when changing question type in the QTI editor #6190's keyboard criterion needs it to keep.
    4. The confirmation modal, and wiring the conversion into InteractionSection; this replaces Read and show Numeric answers in the exercise language #6282's questionType watch in useTextEntryInteraction
    5. A test that every interaction exports all of its content
    6. Numeric answers going to another type are written as the exercise language shows them (3,5 in German)
  • To try it: fill in a question, switch its type, and check the modal against the table in [QTI] Convert answers or warn when changing question type in the QTI editor #6190. For example, multi → text entry, text entry → single, choice → ordering, inline choice → single. Cancel, pick the same type again, and confirm, with the mouse and with the keyboard only.
  • Checked in Chrome on the dev server: every modal in the examples above, numeric answers in German and French, match grouping, focus on the type selector after every switch, keyboard-only confirm and cancel, and that opening a card focuses its question. axe reports nothing new with the modal open.

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 and reviewed the changes.

🤖 Generated with Claude Code

@rtibblesbot

rtibblesbot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

🟢 Re-reviewing

Last updated: 2026-10-28 16:45 UTC

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: conversion, the confirm modal and focus return work as #6190 specifies. Manual QA covered every conversion row, mouse and keyboard cancel/confirm, and de/fr numerics. It found no regressions.

CI pending.

  • Suggestions: scope (inline choice editor, focus on card open, toolbar restyle, global ::selection), an unannounced dropdown restore, and gap match not being reachable yet.
  • Nitpicks: a hard-coded number in a string, test robustness, duplicated comments, and the toolbar at 360px.

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: .stylelintrc.js, contentcuration/contentcuration/frontend/shared/styles/vuetify.scss, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue….
  • Decide whether the inline choice editor, card-open focus and toolbar restyle ship here or split from the conversion PR.
  • Confirm the app-wide ::selection change (KDS colour, no Vuetify rule) is acceptable on dark surfaces.
  • After any split or fixes, re-run conversion QA, including the screen-reader announcement on dropdown undo/paste.
QA captures (50)

Convert a filled single choice and a multi select (two correct, two wrong answers) to numeric input, text entry and single choice — each modal lists only the applicable sentences (I, E, R) per #6190's table, confirm, then check the kept answers, correct marks, shuffle/fixed settings, and that they survive saving and reopening the question

Step Screenshot
Modal multi to numeric Modal multi to numeric
Modal multi to single Modal multi to single
Modal multi to text entry Modal multi to text entry
Modal single to numeric Modal single to numeric
Modal single to text entry Modal single to text entry
Multi select filled Multi select filled
Multi to numeric result Multi to numeric result
Multi to single result Multi to single result
Multi to text entry result Multi to text entry result
Q5 single after reload Q5 single after reload
Single choice filled Single choice filled

Pick a type change that shows the modal, cancel, then pick the same type again and confirm — once with the mouse and once keyboard-only; the question is unchanged after cancel, the selector reverts to the original type, focus returns to the type selector after cancel and confirm, and the modal title names the picked type

Step Screenshot
Keyboard modal open Keyboard modal open
Mouse after cancel Mouse after cancel
Mouse after confirm Mouse after confirm
Mouse modal open Mouse modal open
Q5 before Q5 before
2-keyboard-cancel-confirm.webm
2-mouse-cancel-confirm.webm

Switch types where nothing is lost (single → multi, numeric → text entry, text entry → multi, free response → any type, an empty question → any type) — no modal appears, the type switches straight away, answers carry over, and focus stays on the type selector rather than jumping into the remounted editor

Step Screenshot
Multi to associate after keyboard Multi to associate after keyboard
Numeric to text entry Numeric to text entry
Single to multi after Single to multi after
Text entry to multi Text entry to multi
3-free-response-switches.webm
3-single-to-multi.webm

Convert between choice types and ordering — single/multi → ordering shows C and O and the current order becomes the correct order; ordering → single/multi shows N; numeric/text entry → ordering shows O; with one answer the O/N sentences are absent

Step Screenshot
Multi to ordering modal Multi to ordering modal
Ordering to multi modal Ordering to multi modal
Ordering to single modal Ordering to single modal
Q2 textentry before Q2 textentry before
Q3 to ordering modal Q3 to ordering modal
Q4 to ordering modal Q4 to ordering modal
Q5 multi after Q5 multi after
Q5 ordering after Q5 ordering after
Q5 single answers Q5 single answers
Q5 single before Q5 single before
Single one answer to ordering modal Single one answer to ordering modal
Single to ordering modal Single to ordering modal
Textentry one answer to ordering Textentry one answer to ordering

Convert associate ↔ match with pairs sharing a source and with distractors — match groups shared-source pairs into one row with several matches, the reverse splits them, no modal either way; then associate or match → an answer-list type shows A and removes all answers

Step Screenshot
Associate before Associate before
Associate split Associate split
Associate to multi modal Associate to multi modal
Match grouped Match grouped
Match to single modal Match to single modal
Multi after answers removed Multi after answers removed

In a German and then a French exercise, convert numeric input with decimal answers (e.g. 3.5 stored) to text entry, single and multi — answers appear as 3,5; convert text entry/choice answers like 3,5 back to numeric and check the stored value and the displayed value after reopening

Step Screenshot
De numeric before De numeric before
De numeric to multiple choice De numeric to multiple choice
De numeric to single choice De numeric to single choice
De numeric to text entry De numeric to text entry
De text entry back to numeric reopened De text entry back to numeric reopened
Fr multiple choice Fr multiple choice
Fr numeric to single choice Fr numeric to single choice

Written by rtibblesbot, an LLM-based coding agent.

Comment thread contentcuration/contentcuration/frontend/shared/styles/vuetify.scss Outdated
@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from 8051484 to 9e7aeb2 Compare October 23, 2026 13:04

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: the More menu opens off-screen in narrow windows. Two prior findings are still open. The other 12 are settled.

CI passing. QA covered ten scenarios: inline choice authoring, keyboard passage editing, validation, narrow toolbars, every type-change family, numeric conversion in German, persistence. Only the More menu failed. Every axe violation was pre-existing.

  • blocking: More menu opens off-screen in narrow windows (inline).
  • suggestion (open): gap match conversions are still unreachable. Please add getTypeOptions to gapMatch/Descriptor.js:52 and drop it from HEADLESS_INTERACTIONS.
  • nitpick (open): InteractionSection.spec.js:122 still reads KSelect's .select-value-text. Please assert on the visible text instead.
  • nitpick: many new comments explain the branch's own design (inline).
Prior-finding status

ACKNOWLEDGED — interactions/index.js:7 — inline choice editor outside #6190 (stacked on #6303)
ACKNOWLEDGED — inlineChoice/useInlineChoicePassage.js:180 — no announcement when a dropdown comes back (#6303)
RESOLVED — components/QTIItemEditor/index.vue:305 — focus on card open outside scope (withdrawn: 47c85b0a9 ties it to focus returning to the type selector)
ACKNOWLEDGED — TipTapEditor/components/EditorToolbar.vue:140 — toolbar changes outside #6190 (#6303)
ACKNOWLEDGED — TipTapEditor/components/EditorToolbar.vue:404 — Insert/collapse scroll off at 360px (#6303)
UNADDRESSED — interactions/gapMatch/Descriptor.js:52 — gap match conversions unreachable without getTypeOptions
ACKNOWLEDGED — qtiEditorStrings.js:373 — hard-coded 2 (#6303)
RESOLVED — TipTapEditor/tests/TipTapEditor.spec.js — mocked requestAnimationFrame leaks on failure
RESOLVED — interactions/tests/editors.spec.js — TEXTAREA assertion passes on wrong field
ACKNOWLEDGED — inlineChoice/components/InlineChoiceOptions/index.vue:306 — focus lost outside an option <li> (#6303)
RESOLVED — conversion/tests/convertInteraction.spec.js — case-sensitivity test name
UNADDRESSED — components/InteractionSection/tests/InteractionSection.spec.js:122 — depends on KSelect .select-value-text
RESOLVED — interactions/choice/Editor.vue — focus comment copied into six editors
ACKNOWLEDGED — styles/vuetify.scss:2482 — removed rule changes selection app-wide (#6303)

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: .stylelintrc.js, contentcuration/contentcuration/frontend/shared/styles/vuetify.scss, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue….
  • Decide whether converting a question's type should drop answers, correct marks and formatting by the #6190 table, since the stored item changes when the author confirms.
  • Confirm the shared conversion core and per-descriptor convert.js modules are a pattern other interaction types should follow.
  • Once the More menu fix lands, re-check toolbar menus at 360px and 600px in the question and passage editors.
QA captures (50)

Author an inline choice question from scratch: type a passage, insert dropdowns, then use each dropdown's options panel to add, edit, remove and reorder options and mark the correct one — Shuffle hides reordering, Escape/close/passage click return focus to the dropdown, and the question survives closing and reopening its card

Step Screenshot
Closed card view mode Closed card view mode
Options panel four options correct marked Options panel four options correct marked
Options panel shuffle on no reorder Options panel shuffle on no reorder
Passage two dropdowns Passage two dropdowns
1-options-panel-flow.webm

Edit an inline choice passage with the keyboard only — arrow keys move the caret past dropdowns instead of selecting them, Backspace/Delete beside a dropdown removes it, Left/Right from a focused dropdown returns to the text, undo/redo inside the options panel, and live-region announcements for crossed and removed dropdowns

Step Screenshot
Backspace removed dropdown Backspace removed dropdown
Caret after dropdown Caret after dropdown
Options panel keyboard open Options panel keyboard open
Undo opens other dropdown Undo opens other dropdown
2-keyboard-passage-editing.webm

Check inline choice validation and view mode — errors on the option, dropdown and passage they belong to, the "needs attention" naming in a preview that hides answers, and each dropdown showing its correct answer with Show answers on and "Choose…" with it off

Step Screenshot
Duplicate options Duplicate options
New dropdown errors New dropdown errors
No dropdown error No dropdown error
Too few options Too few options
View answers hidden View answers hidden
View answers shown View answers shown
View hidden needs attention View hidden needs attention
View hidden rtl dir View hidden rtl dir
View shown needs attention View shown needs attention
View shown no answer View shown no answer

Use the rich text toolbar in a narrow window on both the passage editor and an ordinary question/answer editor — Insert shrinks to its icon and looks like other disabled tools when unavailable, the toolbar scrolls sideways once tools collapse into More, the More menu opens outside the toolbar, and selected text uses KDS's selection colour on other Studio pages

Step Screenshot
Channel list selection Channel list selection
Narrow passage insert available Narrow passage insert available
Narrow passage insert unavailable Narrow passage insert unavailable
Narrow question toolbar Narrow question toolbar
Question more menu 800 Question more menu 800
Wide passage insert Wide passage insert
Wide question toolbar Wide question toolbar

Change type within the answer-list family with the mouse — single→multi switches with no modal, multi→single shows R, multi→text entry shows I and E, single/multi→ordering shows C and O, ordering→single shows N, anything→free response shows A; check each modal lists only the sentences that apply, Cancel leaves the question and type unchanged, and Confirm produces the answers and correct marks the rules in #6190 describe

Step Screenshot
Modal rtl dir Modal rtl dir
Multi to ordering confirmed Multi to ordering confirmed
Multi to single cancelled Multi to single cancelled
Multi to single confirmed Multi to single confirmed
Multi to single modal r Multi to single modal r
Multi to text entry confirmed Multi to text entry confirmed
Multi to text entry modal i e Multi to text entry modal i e
Ordering to free response confirmed Ordering to free response confirmed
Ordering to single confirmed Ordering to single confirmed
Ordering to single modal n Ordering to single modal n
Single to free response modal a Single to free response modal a
Single to multi no modal Single to multi no modal
Single to ordering modal c o Single to ordering modal c o
Single to text entry modal i Single to text entry modal i
Text entry multi single final Text entry multi single final
5-type-change-flow.webm

Convert numeric and text entry answers — numeric→single/multi in a German or French exercise writes numbers as "3,5", text entry→numeric switches without a modal and keeps non-numeric answers with validation pointing them out, and rich single/multi/ordering answers (formatting, image, formula) going to numeric or text entry show F and drop image/formula-only answers

Step Screenshot
Numeric german answers Numeric german answers
Numeric to multiple choice german Numeric to multiple choice german
Numeric with invalid to single Numeric with invalid to single
Rich multiple choice all correct Rich multiple choice all correct
Rich multiple to text entry modal Rich multiple to text entry modal
Rich ordering to text entry modal Rich ordering to text entry modal
Rich single choice before Rich single choice before

Written by rtibblesbot, an LLM-based coding agent.

@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from 9e7aeb2 to e0b8567 Compare October 23, 2026 23:15

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: the More menu still opens off-screen in narrow windows. 14 of 16 prior findings are resolved or acknowledged; one new nitpick.

  • blocking: More menu still opens off-screen in narrow windows (inline). It comes from #6303, so fixing it there carries over.
  • nitpick: isHidden re-implements toBeVisible() (inline).

CI passing. Manual QA covered inline choice authoring, validation, undo/redo, keyboard/RTL, and every type-change family. All behaved as specified except the More menu. Axe found only pre-existing violations.

Prior-finding status

RESOLVED — interactions/index.js:7 — inline choice editor outside #6190 scope
RESOLVED — inlineChoice/useInlineChoicePassage.js:180 — no announcement when a dropdown comes back
RESOLVED — QTIItemEditor/index.vue:305 — focusing the question on card open out of scope
RESOLVED — EditorToolbar.vue:140 — toolbar changes outside #6190
RESOLVED — EditorToolbar.vue:404 — Insert/collapse scroll off at 360px
ACKNOWLEDGED — gapMatch/Descriptor.js:52 — gap match conversions unreachable until #6204
RESOLVED — qtiEditorStrings.js — hard-coded 2 in too-few-options message
RESOLVED — TipTapEditor.spec.js — mocked requestAnimationFrame leaks
RESOLVED — editors.spec.js — TEXTAREA focus assertion too weak
RESOLVED — InlineChoiceOptions/index.vue:306 — focus lost outside option <li>
RESOLVED — convertInteraction.spec.js — case-sensitivity test name
RESOLVED — InteractionSection.spec.js — depends on KSelect .select-value-text
RESOLVED — choice/Editor.vue — focus comment copied into six editors
RESOLVED — vuetify.scss:2482 — removed text-selection rule
UNADDRESSED — EditorToolbar.vue:106 — More menu opens off-screen in narrow windows
ACKNOWLEDGED — conversion/answers.js:28 — design-narrating comments (author keeps them as rationale)

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: .stylelintrc.js, contentcuration/contentcuration/frontend/shared/styles/vuetify.scss, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue….
  • Check that the conversion rules in conversion/answers.js (what each type change keeps or drops) match the product intent of #6190, since other interactions will copy them.
  • Re-check the More menu at 480px and 360px widths once the preventOverflow fix lands from #6303.
QA captures (50)

Build an inline choice question from scratch in an exercise's Questions tab, save, close and reopen the card — pick Inline choice in the type selector, type a passage, insert a blank dropdown with the prominent Insert action and convert highlighted text into another, fill options and mark correct ones in the options panel, check the passage, chips and correct answers survive the reopen and the exercise's sync request

Step Screenshot
Closed card Closed card
Options panel filled Options panel filled
Passage typed insert action Passage typed insert action
Reopened second dropdown Reopened second dropdown
Second dropdown from highlight Second dropdown from highlight
Type selector Type selector
page@674dc52acb9218fcdd6cb3befa1a538d.webm

Edit an inline choice dropdown's options panel with the mouse — open a chip, add and delete options, mark a different option correct, reorder options and confirm reordering is unavailable while shuffle is on, close the panel by its close button and by clicking outside, and check clicks inside the panel don't close the passage editor or lose edits

Step Screenshot
Closed by close button Closed by close button
Option 2 marked correct Option 2 marked correct
Shuffle on no reorder Shuffle on no reorder
2-options-panel-mouse.webm

Drive an inline choice passage with the keyboard and a screen reader view of the DOM only — arrow the caret past chips without selecting them, open a chip's options from the keyboard, Tab and Shift+Tab wrap inside the panel's focus trap, delete an option and check focus stays in the panel, Escape returns focus to the chip, and chip/panel aria labels and live announcements (option counts, dropdown removed) are present

Step Screenshot
Correct option deleted Correct option deleted
Dropdown removed Dropdown removed
Duplicate option error Duplicate option error
Panel opened from keyboard Panel opened from keyboard
Second dropdown panel focus close Second dropdown panel focus close
3-keyboard-chip-panel.webm

Undo and redo inside an inline choice question — Ctrl/Cmd+Z, Shift+Ctrl/Cmd+Z and Ctrl+Y from the passage, from a focused chip and from inside the options panel after adding, removing and editing options and deleting a chip, checking the passage and panel stay in step and focus stays in the panel

Step Screenshot
Before undo panel Before undo panel
Undo opens restored chip Undo opens restored chip
4-panel-undo-redo.webm

Inline choice validation — leave the passage with no dropdown, a dropdown with fewer than the minimum options, empty and duplicate options, no correct option, check each error message appears once and in the right place (chip, panel, question), then fix each and see it clear

Step Screenshot
Recheck duplicate Recheck duplicate

Rich text toolbar in a narrow window — shrink the window to a small breakpoint while editing an inline choice passage and another question's rich text field, check the Insert action becomes icon-only with an accessible name, the toolbar scrolls rather than overflowing, the More menu opens outside the toolbar and is usable, the unavailable Insert action looks disabled and doesn't change on hover, and text selection highlight is still visible

Step Screenshot
Inline choice toolbar 480 Inline choice toolbar 480
Insert unavailable hover Insert unavailable hover
Insert unavailable selection Insert unavailable selection
Toolbar 320 scrolled Toolbar 320 scrolled
Toolbar 320 start Toolbar 320 start

Change type within the answer-list family with the mouse — fill a multi select with several correct and wrong answers, switch to single choice, text entry, numeric input and ordering in turn, checking each modal lists exactly the sentences from #6190's table (R, I/E, C/O), Cancel leaves the question and type untouched, Change carries over answers, correct marks and shuffle as the table says, and a lossless switch (single to multi, numeric to text entry, free response to anything) opens no modal

Step Screenshot
Free response Free response
Modal multi to ordering Modal multi to ordering
Modal multi to single Modal multi to single
Modal multi to text entry Modal multi to text entry
Modal numeric to ordering Modal numeric to ordering
Multi select filled Multi select filled
Ordering after change Ordering after change
Single after change Single after change
Single saved after reload Single saved after reload
Text entry after change Text entry after change

Change type from numeric input and text entry — numeric answers with decimals in an exercise whose language is German or French move to single choice and text entry written as 3,5 rather than 3.5, non-numeric text entry answers going to numeric input are kept and flagged by validation, and rich choice answers with formatting, images or formulas going to text entry show the formatting-removed sentence and drop image-only answers

Step Screenshot
De numeric before De numeric before
De numeric to single modal De numeric to single modal
De numeric to single result De numeric to single result
De numeric to text entry De numeric to text entry
Fr numeric to single result Fr numeric to single result
Fr numeric to text entry Fr numeric to text entry
Rich choice before Rich choice before
Rich choice to text entry modal Rich choice to text entry modal
Rich multi to text entry modal Rich multi to text entry modal
Rich multi to text entry result Rich multi to text entry result
Text entry to numeric flagged Text entry to numeric flagged

Change type between pairs and across families — associate to match groups pairs sharing a source into one row and keeps distractors with no modal, match back to associate likewise, then associate or match to single choice warns that all answers will be removed and keeps only the question; check the saved item after each confirmed change

Step Screenshot
Associate after match Associate after match
Associate before Associate before
9-associate-to-single-change.webm

Written by rtibblesbot, an LLM-based coding agent.

@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from e0b8567 to 90c2e43 Compare October 26, 2026 13:52

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: 16 of 17 prior findings resolved or acknowledged; 1 blocking still open.

  • blocking (contested): EditorToolbar.vue:106: the More menu still opens above the viewport in narrow windows. On a 480×900 passage its top sits at -231px. Its first tools can't be seen or reached. The KDS #1384 fix isn't released yet. Until it is, keep the menu inside the viewport here, or get a maintainer to agree to ship with it in the open thread.

CI pending. Manual QA covered every conversion in #6190's table by mouse and keyboard. Server raw_data was checked after reload. Nothing else turned up.

Prior-finding status

ACKNOWLEDGED — interactions/index.js:7 — inline choice editor outside #6190 (stacked on #6303)
ACKNOWLEDGED — inlineChoice/useInlineChoicePassage.js:180 — no announcement when a dropdown comes back (#6303)
RESOLVED — QTIItemEditor/index.vue:305 — focus on card open out of scope (withdrawn: the remounted editor for a new type must not take focus, so this belongs to the conversion)
ACKNOWLEDGED — EditorToolbar.vue:140 — toolbar changes outside #6190 (#6303)
ACKNOWLEDGED — EditorToolbar.vue:404 — Insert/collapse scroll off at 360px (#6303)
ACKNOWLEDGED — gapMatch/Descriptor.js:52 — gap match conversions unreachable until #6204
ACKNOWLEDGED — qtiEditorStrings.js — hard-coded 2 (#6303)
RESOLVED — TipTapEditor.spec.js — mocked requestAnimationFrame leaks on failure
RESOLVED — editors.spec.js — TEXTAREA focus assertion too weak
ACKNOWLEDGED — InlineChoiceOptions/index.vue:306 — focus lost outside an option <li> (#6303)
RESOLVED — convertInteraction.spec.js — case-sensitivity test name
RESOLVED — InteractionSection.spec.js — depends on .select-value-text
RESOLVED — choice/Editor.vue — focus comment copied into six editors
ACKNOWLEDGED — vuetify.scss:2482 — removed selection rule (#6303)
ACKNOWLEDGED — conversion/answers.js:28 — design-explaining comments (author keeps them as rationale)
CONTESTED — EditorToolbar.vue:106 — More menu opens off-screen in narrow windows (deferred to KDS #1384; no maintainer agreement)
RESOLVED — InteractionSection.spec.js — isHidden re-implements toBeVisible()

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: .stylelintrc.js, contentcuration/contentcuration/frontend/shared/styles/vuetify.scss, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue….
  • Decide whether to merge with the More menu off-screen in narrow windows until KDS #1384 ships, or to require a local fix first.
  • Confirm the conversion rules (what each type change drops, keeps or folds into the prompt) are the product behaviour #6190 intended, since stored items change.
  • After #6303 and #6204 land, re-run the type-change flows, including gap match once it is selectable.
QA captures (50)

Convert a filled multi select question to text entry, numeric input and single choice — modal lists only the sentences that apply (I, E, R per #6190), cancel leaves answers and the selector on the old type, picking the same type again reopens the modal, confirm converts correctly and the result survives save and reopen

Step Screenshot
Modal multi to numeric Modal multi to numeric
Modal multi to single Modal multi to single
Modal multi to text entry Modal multi to text entry
Multi filled Multi filled
Numeric after reload Numeric after reload
Single after reload Single after reload
Text entry after convert Text entry after convert
1-multi-to-text-entry-cancel-reopen-confirm.webm

Convert answer-list questions with no loss — numeric and text entry to single/multi (every answer marked correct), single to multi, free response to every type, and back — switches with no modal; non-numeric answers kept going to numeric input are flagged by validation

Step Screenshot
After reload show answers After reload show answers
Free response filled Free response filled
Single filled Single filled
Single to multi Single to multi
Text entry filled Text entry filled
Text entry to multi Text entry to multi
Text entry to numeric flagged Text entry to numeric flagged
2-free-response-round-trip.webm

Convert single/multi choice to ordering and ordering back to single/multi — modal shows C and O, or N, only with more than one answer; the converted order and correct marks are what the table says

Step Screenshot
Multi converted to ordering Multi converted to ordering
Multi to ordering modal Multi to ordering modal
Ordering to single modal Ordering to single modal
3-choice-ordering-round-trip.webm

Convert choice answers with formatting, an image, a formula, and an answer that is only an image to numeric input and text entry — modal adds the F sentence, formatting is stripped and image-only answers are removed, the question's own images and formulas carry over intact

Step Screenshot
All media answers removed All media answers removed
Formatting only modal Formatting only modal
Numeric after Numeric after
Numeric answers Numeric answers
Numeric modal Numeric modal
Seeded choice questions Seeded choice questions
Text entry after Text entry after
Text entry before Text entry before
Text entry modal Text entry modal
4-convert-to-numeric.webm
4-convert-to-text-entry.webm

Convert numeric input answers like 3.5 and 1000 to text entry and single choice in an exercise whose language is German, then French, then English — answers are written as that language shows them, and editing them back to numeric still validates

Step Screenshot
De back to numeric De back to numeric
De numeric start De numeric start
De numeric validation De numeric validation
De single back to numeric De single back to numeric
De single choice De single choice
De text entry De text entry
En single choice En single choice
En text entry En text entry
Fr back to numeric Fr back to numeric
Fr single choice Fr single choice
Fr text entry Fr text entry
5-de-round-trips.webm

Convert associate to match and back, with pairs sharing a source and with distractors — no modal, shared sources become one match row with several matches; then convert associate or match to a choice type and confirm the A modal removes all answers

Step Screenshot
Associate before Associate before
Associate to multi modal Associate to multi modal
Associate to multi result Associate to multi result
Match after associate Match after associate
Match before choice Match before choice
Match to single modal Match to single modal
6-associate-match-round-trip.webm

Written by rtibblesbot, an LLM-based coding agent.

@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from 90c2e43 to 6d2cdc2 Compare October 26, 2026 20:14
@AlexVelezLl
AlexVelezLl marked this pull request as ready for review October 26, 2026 20:16
@AlexVelezLl
AlexVelezLl requested review from a team, leq-bot and rtibblesbot October 26, 2026 20:16

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch was rebased since my last review; HEAD is now 6d2cdc2, with no content changes.


Written by rtibblesbot, an LLM-based coding agent.

@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from 6d2cdc2 to f8c0eef Compare October 26, 2026 22:14

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: switching answers into Numeric in a German exercise changes their values (blocking, inline). Please fix it before merge.

1 of 17 prior findings is still open. It doesn't block this PR.

CI is pending. Manual QA covered every type pair in #6190's table, keyboard-only flows and persistence. Only the German numeric import failed.

Prior-finding status

RESOLVED — interactions/index.js:7 — inline choice editor outside #6190's scope (#6303's code)
RESOLVED — inlineChoice/useInlineChoicePassage.js:180 — no announcement when a dropdown comes back (#6303's code)
RESOLVED — QTIItemEditor/index.vue:305 — focusing the question on card open
RESOLVED — EditorToolbar.vue:140 — toolbar changes outside #6190 (#6303's code)
RESOLVED — EditorToolbar.vue:404 — Insert/collapse scroll off at 360px (#6303's code)
ACKNOWLEDGED — gapMatch/Descriptor.js:52 — gap match conversions unreachable until #6204
RESOLVED — qtiEditorStrings.js — hard-coded 2 (#6303's code)
RESOLVED — TipTapEditor.spec.js — mocked requestAnimationFrame leaks
RESOLVED — editors.spec.js — TEXTAREA assertion passes on the wrong field
RESOLVED — InlineChoiceOptions/index.vue:306 — focus lost outside an option <li> (#6303's code)
RESOLVED — convertInteraction.spec.js — case-sensitivity test name
RESOLVED — InteractionSection.spec.js — depends on .select-value-text
RESOLVED — choice/Editor.vue — focus comment copied into six editors
RESOLVED — vuetify.scss — removed rule changes text selection (#6303's code)
ACKNOWLEDGED — conversion/answers.js:28 — design-rationale comments (author keeps them)
RESOLVED — InteractionSection.spec.js — isHidden re-implements toBeVisible()
CONTESTED — EditorToolbar.vue:106 — More menu opens off-screen in narrow windows (deferred to KDS#1384 with no maintainer agreement; now on unstable via #6303, so it doesn't block this PR)

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QuestionTypeSelector/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useChoiceInteraction.js….
  • Confirm the convention that converted numeric answers travel in the exercise language's display form, and that import reads them back in that language.
  • Re-run the German text entry/choice → Numeric switches (1.000, 2.500) and check the stored map-key values and the console.
QA captures (50)

Open a question card, fill a single choice question with several answers and one correct, switch to multi select then back — no modal on single→multi, answers, correct marks and shuffle/fixed settings carry over, focus stays on the type selector after each switch, and opening the card still focuses the question editor

Step Screenshot
After switch to multi After switch to multi
Back to single reopened Back to single reopened
Single choice filled Single choice filled
1-single-multi-round-trip.webm

Fill a multi select question with several correct and wrong answers, switch to single choice, numeric input and text entry in turn, confirming each — modal lists only the sentences #6190's table gives (R; I and E), wrong answers are removed for numeric/text entry, only the first correct stays marked for single, and the saved question reloads the same after closing and reopening the card

Step Screenshot
Modal multi to numeric Modal multi to numeric
Modal multi to single Modal multi to single
Modal multi to text entry Modal multi to text entry
Multi select filled Multi select filled
Numeric after conversion Numeric after conversion
Single after conversion Single after conversion
Text entry after conversion Text entry after conversion
2-multi-to-single.webm

Fill a choice question with rich answers (bold, image-only, formula-only, plain text) and switch to text entry — modal adds the formatting sentence, formatting is stripped, image-only and formula-only answers are dropped, plain answers kept

Step Screenshot
Formula only to text entry Formula only to text entry
Modal multi to text entry Modal multi to text entry
Multi choice rich answers Multi choice rich answers
Text entry after conversion Text entry after conversion
Text entry answers Text entry answers
3-multi-to-text-entry.webm

Fill a numeric input question (including a non-number answer) and a text entry question, switch each to single choice, multi select and to each other — modal only where the table says (R for single with several answers, none for multi or numeric↔text entry), all answers become marked correct in choice types, the non-number answer is kept and validation flags it in numeric

Step Screenshot
Multi to numeric non number flagged Multi to numeric non number flagged
Numeric to multi all correct Numeric to multi all correct
Numeric to single modal Numeric to single modal
Numeric to single result Numeric to single result
Numeric to text entry Numeric to text entry
Numeric with non number Numeric with non number
Text entry filled Text entry filled
Text entry to multi all correct Text entry to multi all correct

Set an exercise's language to German (or French) in its details, write numeric answers like 3,5 and 1.000, switch numeric input to text entry and to single choice and back to numeric — answers read as the language shows them (3,5), not the stored 3.5, and round-trip without changing value

Step Screenshot
Numeric after round trip Numeric after round trip
Numeric german answers Numeric german answers
Single choice from numeric Single choice from numeric
Text entry from numeric Text entry from numeric

Switch between answer-list types and ordering both ways — choice→ordering lists C and O with more than one answer, ordering→choice lists N, numeric/text entry→ordering lists only O, current order becomes the correct order, item text carries over

Step Screenshot
Modal choice to ordering Modal choice to ordering
Modal numeric to ordering Modal numeric to ordering
Modal ordering to choice Modal ordering to choice
Modal text entry to ordering Modal text entry to ordering
Multi choice before ordering Multi choice before ordering
Multi choice from ordering Multi choice from ordering
Ordering from choice Ordering from choice
Ordering from numeric Ordering from numeric
Text entry before ordering Text entry before ordering
6-ordering-choice-round-trip.webm

Fill an associate question with pairs that share a source plus distractors, switch to match and back — no modal, shared-source pairs group into one match row with several matches, distractors survive; then switch associate or match to single choice and confirm the "All answers will be removed" modal and that only the question text stays

Step Screenshot
Associate after match Associate after match
Associate filled Associate filled
Associate from match Associate from match
Associate to single modal Associate to single modal
Match after associate Match after associate
Match to single modal Match to single modal
Single after associate Single after associate

Fill an inline choice question with a passage holding two or more dropdowns, switch to single choice, ordering and associate — modal shows the passage sentence with "_____", the passage is appended to the question with each dropdown shown as _____, answers removed; confirm gap match is not offered in the type selector

Step Screenshot
Converted to associate Converted to associate
Converted to ordering Converted to ordering
Converted to single Converted to single

Written by rtibblesbot, an LLM-based coding agent.

@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from f8c0eef to 3cf4919 Compare October 27, 2026 14:37

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: all 18 prior findings are resolved or acknowledged. Manual QA found 3 new suggestions (inline).

Withdrawn: focusing the question on card open is in scope, not out of scope. Editors no longer focus themselves. Without it, a remount for a new type leaves focus on the type select.

CI passing. Manual QA covered every conversion family, the modal by keyboard, and de/fr round trips. No new a11y violations.

Prior-finding status

RESOLVED — interactions/index.js:7 — inline choice editor out of scope (no longer in this PR's diff)
RESOLVED — inlineChoice/useInlineChoicePassage.js:180 — screen readers silent when a dropdown returns (no longer in this PR's diff)
RESOLVED — components/QTIItemEditor/index.vue:305 — focus on card open out of scope (withdrawn)
RESOLVED — EditorToolbar.vue:140 — toolbar changes out of scope (no longer in this PR's diff)
RESOLVED — EditorToolbar.vue:404 — Insert/collapse scroll off at 360px (no longer in this PR's diff)
RESOLVED — EditorToolbar.vue:106 — More menu opens off-screen (no longer in this PR's diff; KDS#1384)
ACKNOWLEDGED — gapMatch/Descriptor.js:52 — gap match conversions unreachable until #6204
RESOLVED — qtiEditorStrings.js — hard-coded 2 (no longer in this PR's diff)
RESOLVED — TipTapEditor.spec.js — leaked requestAnimationFrame mock
RESOLVED — interactions/tests/editors.spec.js — TEXTAREA focus assertion
RESOLVED — InlineChoiceOptions/index.vue:306 — focus lost outside option li (no longer in this PR's diff)
RESOLVED — convertInteraction.spec.js — case-sensitivity test name
RESOLVED — InteractionSection.spec.js — KSelect .select-value-text dependency
RESOLVED — choice/Editor.vue — comment copied into six editors
RESOLVED — shared/styles/vuetify.scss — selection rule removal (no longer in this PR's diff)
ACKNOWLEDGED — conversion/answers.js:28 — design-rationale comments (author keeps them)
RESOLVED — InteractionSection.spec.js — isHidden re-implements toBeVisible()
RESOLVED — textEntry/convert.js — German numeric answers change value on switch

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QuestionTypeSelector/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useChoiceInteraction.js….
  • Decide whether converting answers should dedupe equal numbers and reject ambiguous German group separators, or list them as losses.
  • Confirm the per-type loss rules and the modal wording match what product wants for #6190.
  • Re-check in the app that opening a card near the list end keeps its focused question on screen after the scroll fix.
QA captures (50)

Switch a filled question between answer-list types that lose nothing (single ↔ multi with one correct, text entry → numeric with plain numbers, empty question to any type) — no modal appears, answers and correct marks carry over, focus stays on the type selector, and the converted question is still there after closing and reopening the exercise editor

Step Screenshot
Converted to multi Converted to multi
Empty question Empty question
Numeric after reopen Numeric after reopen
Single choice filled Single choice filled
Text entry filled Text entry filled
1-single-multi-single.webm

Fill a multi-select question with several correct and some incorrect answers, some holding bold text, an image or a formula, then switch it to text entry — the modal names the new type and lists incorrect answers removed, any one correct answer accepted and formatting removed; Cancel leaves the question and selector unchanged, picking text entry again reopens the modal, Change keeps only the correct answers as plain text and drops image-only or formula-only ones

Step Screenshot
Modal text entry losses Modal text entry losses
Multi select filled Multi select filled
Text entry after change Text entry after change
2-cancel-reopen-change.webm

Switch a text entry or numeric question with several answers to single select and to multi select, and a multi-select question with several correct answers to single select — answers become choices marked correct, the modal says only the first correct answer stays correct where it applies, and the editor shows the result after Change

Step Screenshot
Edge empty first correct before Edge empty first correct before
Edge empty first correct single Edge empty first correct single
Multi to single modal Multi to single modal
Multi to single result Multi to single result
Numeric before Numeric before
Numeric to multi result Numeric to multi result
Text entry before Text entry before
Text entry to multi result Text entry to multi result
Text entry to single modal Text entry to single modal
Text entry to single result Text entry to single result
3-multi-to-single-keyboard.webm

Switch a choice question to ordering and an ordering question back to single and multi select — the modal says the current order becomes the correct order and answers are no longer marked correct, then that the correct order won't be kept; after Change the items keep their order and validation flags the choice question that now has no correct answer

Step Screenshot
Choice before ordering Choice before ordering
Modal choice to ordering Modal choice to ordering
Modal ordering to multi Modal ordering to multi
Modal ordering to single Modal ordering to single
Multi after change incomplete Multi after change incomplete
Ordering after change Ordering after change
Ordering reordered Ordering reordered
Single after change incomplete Single after change incomplete
4-choice-to-ordering-and-back.webm

Switch a filled question of each answer-list type to free response and back, and a choice question to associate or match — the modal says all answers will be removed, Change leaves only the question, and switching from an empty question to these types shows no modal

Step Screenshot
Single choice filled Single choice filled
Single to associate after change Single to associate after change
Single to associate modal Single to associate modal
Single to free response after change Single to free response after change
Single to free response modal Single to free response modal
Single to match after change Single to match after change
Single to match modal Single to match modal
5-single-to-match.webm

Fill an associate question with pairs and distractors, switch it to match and back — pairs and distractors carry over without a modal, match grouping is correct in the editor; then switch the match question to single select and confirm that answers are removed after the modal warns

Step Screenshot
Associate filled Associate filled
Back to associate Back to associate
Match grouped Match grouped
Match to single modal Match to single modal
Single after change Single after change
6-associate-to-match-and-back.webm
6-match-to-single-modal.webm

Fill an inline choice question with a passage holding several dropdowns, switch it to single select, text entry and ordering — the modal says the passage is added to the question with each blank shown as underscores and its answers removed; after Change the question text holds the passage with underscore blanks and the original question text before it

Step Screenshot
Inline choice before Inline choice before
Modal ordering Modal ordering
Modal single Modal single
Modal text entry Modal text entry
7-inline-to-single.webm

Written by rtibblesbot, an LLM-based coding agent.

@rtibblesbot
rtibblesbot dismissed stale reviews from themself October 27, 2026 16:14

Superseded by my review of 3cf4919: no blocking findings remain.

@AlexVelezLl
AlexVelezLl force-pushed the question-type-conversion branch from 3cf4919 to 19dad3d Compare October 27, 2026 18:21

@rtibblesbot rtibblesbot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6311: 21 prior findings resolved or acknowledged; none still open.

  • CI pending.
  • Manual QA covered nine conversion scenarios, including keyboard, focus, persistence and de/fr locales. No regressions from this PR.
  • One pre-existing note on textEntry/convert.js:64.
Prior-finding status

ACKNOWLEDGED — interactions/index.js:7 — inline choice editor outside #6190's scope (#6303's)
ACKNOWLEDGED — inlineChoice/useInlineChoicePassage.js:180 — no announcement when a dropdown comes back (#6303's)
RESOLVED — QTIItemEditor/index.vue:305 — focusing the question on card open out of scope
ACKNOWLEDGED — EditorToolbar.vue:140 — toolbar changes outside #6190 (#6303's)
ACKNOWLEDGED — EditorToolbar.vue:404 — Insert/collapse scroll off at 360px (#6303's)
ACKNOWLEDGED — gapMatch/Descriptor.js:52 — gap match conversions unreachable until #6204
ACKNOWLEDGED — qtiEditorStrings.js — hard-coded 2 (#6303's)
RESOLVED — TipTapEditor.spec.js — leaked requestAnimationFrame mock
RESOLVED — interactions/tests/editors.spec.js — TEXTAREA focus assertion too weak
ACKNOWLEDGED — InlineChoiceOptions/index.vue:306 — focus lost outside an option <li> (#6303's)
RESOLVED — convertInteraction.spec.js — case-sensitivity test name
RESOLVED — InteractionSection.spec.js — depends on .select-value-text
RESOLVED — choice/Editor.vue — focus comment copied into six editors
ACKNOWLEDGED — styles/vuetify.scss — removed text-selection rule (#6303's)
ACKNOWLEDGED — conversion/answers.js:28 — design-explaining comments
RESOLVED — InteractionSection.spec.js — isHidden re-implements toBeVisible()
RESOLVED — textEntry/convert.js — German exercise changes answer values on switch into Numeric
RESOLVED — textEntry/convert.js — text entry → Numeric duplicate answers
RESOLVED — QTIItemEditor/index.vue — card scrolls before its question opens
ACKNOWLEDGED — textEntry/convert.js:64 — German 3.5 becomes Numeric 35 (author: matches typing; fix belongs in readLocaleNumber)
ACKNOWLEDGED — EditorToolbar.vue:106 — More menu opens off-screen in narrow windows (both threads; not in this branch's diff, deferred to KDS#1384)

Next step: Needs a full code review by a human engineer, then manual QA once that reviewer sets Needs QA.

  • User-visible files changed: contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/ChangeQuestionTypeModal/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/InteractionSection/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QTIItemEditor/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/QuestionTypeSelector/index.vue, contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useChoiceInteraction.js….
  • Confirm the per-pair conversion rules (what each switch drops or keeps) are the product behaviour wanted, since authors will rely on them.
  • Decide whether German 3.5 → 35 on switch into Numeric needs a readLocaleNumber fix before release.
  • Check the change dialog in RTL and at narrow widths, which QA did not cover.
QA captures (50)

Change a filled multi-select question (two correct, two incorrect answers, one with bold text and one image-only) to text entry, confirm, then save and reopen the exercise — modal lists incorrect-removed, any-correct-accepted and formatting-removed in that order with the type label in the intro, carried answers are plain text with the image-only one gone, the change persists after reopen

Step Screenshot
After convert text entry After convert text entry
Change type modal Change type modal
Filled multi select Filled multi select
Reopened text entry Reopened text entry
1-convert-to-text-entry.webm

Change between single, multi, numeric, text entry and free response on filled questions — no-loss switches (single to multi, text entry to multi) apply with no modal, multi with several correct to single keeps only the first correct, any type to free response warns all answers removed, empty new questions switch silently

Step Screenshot
Filled multi three correct Filled multi three correct
Filled single choice Filled single choice
Filled text entry Filled text entry
Free response after Free response after
Multi text to numeric after Multi text to numeric after
Multi text to numeric modal Multi text to numeric modal
Multi to free response modal Multi to free response modal
Multi to single after Multi to single after
Multi to single modal Multi to single modal
Single to multi no modal Single to multi no modal
Text entry to multi no modal Text entry to multi no modal
2-multi-to-single-confirm.webm

Pick a lossy type, cancel the modal, pick the same type again and confirm, using only the keyboard — selector reverts to the original type on cancel, question untouched, the modal reappears on the second pick, focus returns to the type selector after cancel, after confirm and after a no-modal switch

Step Screenshot
After cancel reverted After cancel reverted
After confirm single After confirm single
Dropdown single highlighted Dropdown single highlighted
Modal cancel focused Modal cancel focused
Modal open keyboard Modal open keyboard
Q8 open type selector focused Q8 open type selector focused
3-keyboard-cancel-then-confirm.webm

Change a choice question to ordering and an ordering question back to single and multi select — modal shows order-becomes-answer and correct-answers-not-kept, then order-not-kept, item order and content carry over, no correct mark survives, rich content in items is kept

Step Screenshot
After multi After multi
After ordering After ordering
After single After single
Before multi select Before multi select
Before ordering Before ordering
Multi to ordering modal Multi to ordering modal
No correct to ordering modal No correct to ordering modal
Ordering to multi modal Ordering to multi modal
Ordering to single modal Ordering to single modal
4-multi-to-ordering.webm
4-ordering-to-multi.webm
4-ordering-to-single.webm

Set the exercise language to German (then French), fill a numeric question with decimals like 3,5, change it to text entry, single select and back to numeric — values appear as the language writes them in the new type, round trip returns the same stored number, nothing reads 3,5 as 35, saved XML holds the stored value

Step Screenshot
De back to numeric De back to numeric
De single choice De single choice
De text entry to numeric dedup De text entry to numeric dedup
De text entry De text entry
Fr text to numeric Fr text to numeric
New numeric New numeric
5-fr-round-trip.webm

Change an associate question with pairs and a distractor to match and back, then match to single select — pairs and distractors carry with no modal, match grouping is correct, switching to a non-pair type warns all answers removed and keeps the prompt

Step Screenshot
Associate after match Associate after match
Associate before Associate before
Match after associate Match after associate
Match to single modal Match to single modal
Single after match Single after match

Change a filled inline choice question to single select, text entry and free response — modal says the passage joins the question with each blank shown as the underscore text and its answers go, the prompt then holds the passage with blanks in place, inline choice options appear nowhere

Step Screenshot
After free response After free response
After single After single

Written by rtibblesbot, an LLM-based coding agent.

// stay as written, for validation to point out.
const stored = content =>
questionType === QuestionType.NUMERIC
? (readLocaleNumber(content, language) ?? content)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pre-existing: In a German exercise, a text entry or choice answer 3.5 converts to Numeric 35 without warning. The saved item has map-key="35". The cause is readLocaleNumber, which treats . as a grouping separator in any position. You said the fix belongs there, since it also fixes typing. Please file a follow-up issue for it. Suggested rule: accept a grouping separator only when every group after it has 3 digits.

de text entry 3.5 converted to numeric 35

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested rule: accept a grouping separator only when every group after it has 3 digits.

That's incorrect, surely? Indian number grouping uses less than 3 digits in a group. I think we'd be better off continuing to reuse the external library that we are relying on for this.

...state,
prompt,
choices: items.length
? items.map(({ content, correct, fixed }) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text entry → choice drops caseSensitive without a warning, so switching back clears it. We should carry it through or report it as a loss.

@AlexVelezLl AlexVelezLl Oct 28, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now covers every setting an author can change, not only case sensitivity: shuffle (single, multiple and inline choice), show answer count (multiple choice), and case sensitivity (text entry). When a setting the author changed has no place in the new type, the modal says "Settings specific to this question type will be lost."

  • Each type's conversion format maps the settings it keeps to the value they start at, taken from its parse defaults. Each type exports only the settings changed from those values.
  • A setting carries to any type that keeps it, even when the answers don't, e.g. inline choice shuffle → choice. Ordering and gap match keep a shuffle, as learners always see them shuffled.
  • Case sensitivity only counts for answers whose letters have case: H2O, not 1,5.
  • Choice → inline choice can't keep shuffle: inline choice saves it on its dropdowns, and the converted question has none, so it's reported.

In 6dd9d7e.

Written with Claude 🤖, under my guidelines and supervision.

[CorrectCriterion.ACCEPTED]: (items, filled, losses) => {
if (filled.some(item => !item.correct)) losses.push(ConversionLoss.INCORRECT_ANSWERS_REMOVED);
// Every correct choice had to be selected; any one accepted answer is enough.
if (filled.filter(item => item.correct).length > 1) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Counts correct answers before image-only ones are removed, so E shows when only one survives. We should count after changeContentLevels.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

AlexVelezLl and others added 6 commits October 28, 2026 10:29
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 what makes answers correct 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>
Each interaction now exports its state into the conversion facets and
imports from them, in its own convert.js, so a question keeps what the
new type can hold. The tests convert a filled question of each type to
every other type, against one table of the losses each change brings.

A convert module makes a new choice, item, answer, row or distractor
through its interaction's parse module, which now has the one factory
for it; the parse modules and the composables that add one use them
too, so each is built the same way wherever it is made. Gap match and
inline choice parse modules also write the gaps and dropdowns they
read, so how a passage holds them stays their own.

Answer lists (single and multi select, numeric, text entry, free
response, ordering) differ in what makes an answer correct:

- only the correct choices become accepted numeric or text entry
  answers, since a wrong one would become an accepted one, and when
  several were correct the author is told any one is now enough
- 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
  correct answer, are reported so the author can confirm first

Answers that aren't numbers stay when switching to numeric, for
validation to flag. Shuffle travels 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.

Pairs (associate and match) carry their pairs and distractors. 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 round
trip gives back the row it started from.

Passages (inline choice and gap match) travel as neutral blanks, so
neither needs the other's markup. To a type with no passage, the
passage is folded into the question, each blank shown as _____, since
it is often the whole question. Between the two, answers move with
their blanks: each dropdown's wrong options join gap match's shared
pool without a warning, since the pool is the equivalent, and each
dropdown made from gap match offers the whole pool, shuffled. Options
the conversion repeats are dropped rather than left for validation to
flag, and formatting a gap's answer can hold but an option can't is
reported.

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, in a modal; one that loses nothing
switches straight away. The modal names the new type and gives one
full sentence for each thing that would go or change. Each sentence is
a string of its own, so translators never get fragments to join.

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. Confirming saves the question as it was converted when
the type was picked: the modal blocks editing, so it can't have
changed.

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. Answers switching into numeric
are read in that language too, as typing them into the numeric field
would read them: in German, 1.000 is a thousand and 1,5 is 1.5.
Reading them as xsd:double first, as learningequality#6282 did, would turn German 1.000
into 1, and store 1,5 as a float it isn't. Answers that store the same
number, such as text entry 2 and 2.0, are kept once, as numeric scores
them as one.

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
AlexVelezLl force-pushed the question-type-conversion branch from 19dad3d to 6dd9d7e Compare October 28, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[QTI] Convert answers or warn when changing question type in the QTI editor

3 participants