Repository navigation
[QTI] Text entry question with an image in its prompt fails to save #6267
Description
❌ 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
Adding an image or small text to the prompt of a text entry question (numeric, text entry, free response) produces an item the server rejects, so the question doesn't save.
- TipTap emits images and small text as top-level
<img>and<small>. buildTextEntryInteractionXMLwrites the prompt directly into<qti-item-body>, which the QTI schema only allows to hold block elements.- The sync API runs
validate_qti_itemon every QTI write and rejects the item.
Inline choice has the same constraint and already handles it with BLOCK_TAGS / wrapInlineRuns in interactions/inlineChoice/parse.js, but only for itself. Every other interaction writes TipTap content into qti-prompt, qti-simple-choice, qti-simple-associable-choice or qti-html-content, all of which accept it.
Errors and logs
Schema check on an item body with a top-level image, as the text entry builder writes it:
Element '{http://www.imsglobal.org/xsd/imsqtiasi_v3p0}img': This element is not expected.
Expected behavior
Any TipTap content in a text entry prompt saves and reopens unchanged. Every interaction that writes authored HTML directly into <qti-item-body> goes through a single shared helper, instead of each one handling the rule itself:
- The inline choice wrapping moves out of
inlineChoice/parse.jsinto a shared helper inserialization/xml.jsthat turns an HTML fragment into a list of block elements for<qti-item-body>. - Builders call it where they place content, before setting any markers: inline choice for its prompt and passage (its
data-studio-promptmarker must stay on a top-level element), text entry for its prompt.
User-facing consequences
Authors can't add an image or small text to the prompt of a numeric, text entry or free response question. The save fails with a sync error.
Steps to reproduce
- Open an exercise and add a numeric or text entry question.
- Add an image to the question prompt.
- Save. The item is rejected by the sync API's QTI validation.
Acceptance Criteria
- A text entry prompt containing an image, small text, or both saves, passes
validate_qti_item, and reopens with the same content - Inline choice keeps its current behavior, including prompt/passage separation for prompts with images or small text
- Inline choice and text entry use the same helper from
serialization/xml.js;inlineChoice/parse.jsno longer defines its own - Tests cover a text entry prompt with an image and with small text, built and parsed back
Context
Alternatives considered:
- Wrap in
assembleItemXml: covers every interaction without builders calling anything, but it runs after builders set their markers. Inline choice would tag a bare<img data-studio-prompt>, assembly would wrap it in an unmarked<div>, and on reopen the image would move from the prompt to the passage. - Make TipTap emit only block elements at the root: only fixes TipTap's output, not stored or imported XML that reaches the inline choice parser. It also changes the stored markup of every rich-text field where a top-level
<img>is already valid, andImageandSmallwould have to parse both the old and new forms permanently.
AI usage
I used Claude Code to check TipTap's output and every interaction's content slot against the server QTI schema, compare the alternatives, and draft this issue. I reviewed the findings and edited the draft. The save failure is based on the schema check, not reproduced in the app.

Activity
[-]Text entry question with an image in its prompt fails to save[/-][+][QTI] Text entry question with an image in its prompt fails to save[/+]rtibblesbot commentedon Oct 1, 2026
Plan: Text entry prompts with images or small text save past
validate_qti_item, via one shared<qti-item-body>block-wrapping helper.xml.jsAssumptions — the issue did not settle these; say so if one is wrong.
extractPromptHTMLkeeps XMLinnerHTML; if reviewers want HTML-form output (<img>not<img/>), switch it toserializeAsHtmlas inline choice does.@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?