
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Target branch: unstable
Observed behavior
The QTI editor writes text-entry answers and inline choice options to the item XML with any whitespace the author typed around them:
- Text entry: an answer typed as
Paris is saved as <qti-value>Paris </qti-value>, while its <qti-map-entry map-key="Paris"> is trimmed. On reload the parser trims <qti-value>, so what the editor shows no longer matches what is stored.
- Inline choice: an option typed as
a is saved as <qti-inline-choice>a </qti-inline-choice>. Validation already trims option text, so a and a are flagged as duplicates, yet both are saved as different values.
- Publish: the derived Perseus item for a text-entry question copies the untrimmed
<qti-value> into its answers (perseus_derive._correct_values doesn't strip).
Errors and logs
None.
Expected behavior
Text-entry answers and inline choice option text are trimmed when the item is serialized, so the stored XML matches what the editor validates and shows after a reload. Rich-text content (prompts, choices, ordering items, hints) is out of scope.
This matches the legacy editor, which never stored surrounding whitespace: its Markdown serializer trimmed every field on save, numeric answers came from an <input type="number">, and parse_valid_number strips them on publish.
User-facing consequences
- Learners can be marked wrong for a correct answer: QTI string matching is exact, so a stored
Paris may not match a learner's Paris.
- Authors see an answer that differs from the stored one after reopening the question.
Steps to reproduce
- Open a channel's exercise and add a text-entry question.
- Enter an answer with a trailing space, e.g.
Paris , and save.
- Inspect the item's
raw_data: <qti-value> holds Paris and map-key holds Paris.
- Repeat with an inline choice question whose option has surrounding spaces: the
<qti-inline-choice> text keeps them.
Acceptance Criteria
Context
- Text entry:
frontend/shared/views/QTIEditor/interactions/textEntry/parse.js (buildTextEntryInteractionXML)
- Inline choice:
frontend/shared/views/QTIEditor/interactions/inlineChoice/parse.js (buildInlineChoiceInteractionXML)
- Publish derivation:
contentcuration/utils/assessment/qti/perseus_derive.py (_correct_values)
AI usage
I used Claude Code to trace how each QTI editor interaction handles whitespace on parse and serialization, compare it with the removed legacy editor, and draft this issue. I reviewed the findings and edited the draft.
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Target branch: unstable
Observed behavior
The QTI editor writes text-entry answers and inline choice options to the item XML with any whitespace the author typed around them:
Parisis saved as<qti-value>Paris </qti-value>, while its<qti-map-entry map-key="Paris">is trimmed. On reload the parser trims<qti-value>, so what the editor shows no longer matches what is stored.ais saved as<qti-inline-choice>a </qti-inline-choice>. Validation already trims option text, soaandaare flagged as duplicates, yet both are saved as different values.<qti-value>into its answers (perseus_derive._correct_valuesdoesn't strip).Errors and logs
None.
Expected behavior
Text-entry answers and inline choice option text are trimmed when the item is serialized, so the stored XML matches what the editor validates and shows after a reload. Rich-text content (prompts, choices, ordering items, hints) is out of scope.
This matches the legacy editor, which never stored surrounding whitespace: its Markdown serializer trimmed every field on save, numeric answers came from an
<input type="number">, andparse_valid_numberstrips them on publish.User-facing consequences
Parismay not match a learner'sParis.Steps to reproduce
Paris, and save.raw_data:<qti-value>holdsParisandmap-keyholdsParis.<qti-inline-choice>text keeps them.Acceptance Criteria
<qti-value>and itsmap-keywithout leading or trailing whitespace<qti-inline-choice>text without leading or trailing whitespace<qti-value>has surrounding whitespace produces trimmed Perseus answersContext
frontend/shared/views/QTIEditor/interactions/textEntry/parse.js(buildTextEntryInteractionXML)frontend/shared/views/QTIEditor/interactions/inlineChoice/parse.js(buildInlineChoiceInteractionXML)contentcuration/utils/assessment/qti/perseus_derive.py(_correct_values)AI usage
I used Claude Code to trace how each QTI editor interaction handles whitespace on parse and serialization, compare it with the removed legacy editor, and draft this issue. I reviewed the findings and edited the draft.