Skip to content

feat: add Ordering Interaction Editor - #6089

Open
Abhishek-Punhani wants to merge 1 commit into
learningequality:unstablefrom
Abhishek-Punhani:ordering-interaction
Open

feat: add Ordering Interaction Editor#6089
Abhishek-Punhani wants to merge 1 commit into
learningequality:unstablefrom
Abhishek-Punhani:ordering-interaction

Conversation

@Abhishek-Punhani

@Abhishek-Punhani Abhishek-Punhani commented Aug 13, 2026

Copy link
Copy Markdown
Member

Summary

Implemented the QTI Ordering Interaction editor, expanding the assessment authoring framework to support sequence-based questions.

OrderingInteractionEditor.vue — UI component for Ordering questions. Features real-time XML synchronization, responsive collapsible toolbars, ordered positional badges, and strict validation state handling.
OrderingInteractionDescriptor.js — Core interaction descriptor mapping the qti-order-interaction schema to the authoring UI.
parse.js — Parsing and XML serialization logic for Ordering interactions, ensuring the qti-correct-response sequence strictly matches the author's defined order.
validate.js — Validation rules ensuring ordering questions strictly contain at least two choices and no duplicate or empty content.
tests/* — Comprehensive test suites for parsing, validation, and editor interactions, adhering to ARIA-role querying standards.

References

Closes #6085

Reviewer guidance

Navigate to the QTI demo page. Test the Ordering Interaction editor. Verify that adding, removing, and reordering items works. Verify that validation correctly flags empty/duplicate items and warns if fewer than 2 items are present. Check that the responsive layout collapses correctly on smaller screens and that the correct order is preserved across XML serialisation cycles.

AI usage

Used Antigravity for final review and nitpicks.

@learning-equality-bot

Copy link
Copy Markdown

👋 Hi @Abhishek-Punhani, thanks for contributing!

For the review process to begin, please verify that the following is satisfied:

  • Contribution is aligned with our contributing guidelines

  • Pull request description has correctly filled AI usage section & follows our AI guidance:

    AI guidance

    State explicitly whether you didn't use or used AI & how.

    If you used it, ensure that the PR is aligned with Using AI as well as our DEEP framework. DEEP asks you:

    • Disclose — Be open about when you've used AI for support.
    • Engage critically — Question what is generated. Review code for correctness and unnecessary complexity.
    • Edit — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.
    • Process sharing — Explain how you used the AI so others can learn.

    Examples of good disclosures:

    "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."

    "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."

Also check that issue requirements are satisfied & you ran pre-commit locally.

Pull requests that don't follow the guidelines will be closed.

Reviewer assignment can take up to 2 weeks.

…ation and tests

Signed-off-by: Abhishek-Punhani <punhani.manavabhi@gmail.com>
@learning-equality-bot

Copy link
Copy Markdown

📢✨ Before we assign a reviewer, we'll turn on @rtibblesbot to pre-review. Its comments are generated by an LLM, and should be evaluated accordingly.

@rtibblesbot

rtibblesbot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🔵 Review posted

Last updated: 2026-08-13 16:38 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 #6089 — the ordering plugin mirrors interactions/choice/ closely and the view-mode emit guard the issue asked for is correctly implemented. Three explicit issue requirements are missing, two of which cause real defects: a newly created ordering item serializes to schema-invalid XML with zero choices, and max-choices/min-choices are dropped when an imported item is edited. Separately, a cluster of wiring in the editor is dead — @blur, the item refs, and the small-screen class.

CI passing. Manual QA did not run (dev server failed to start), so nothing here is visually verified — the narrow-viewport and focus/screen-reader findings need QA confirmation.

  • blocking: zero-item default state; dropped max-choices/min-choices; no teleportTargetId/AnswerSettings/setShuffle, so shuffle is unreachable
  • suggestion: dead @blur and ref/focus plumbing, unimplemented small-screen handling, focus lost on delete/move-to-top, no reorder announcement, unspecified duplicate-content rule, weak tests
  • nitpick: duplicated string, doubled mode watcher, view-mode header

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Ran a phased review pipeline over the pull request diff:

  • Classified the diff to select review passes (core, frontend, backend) and whether manual QA was required
  • Core review pass checked correctness, design, architecture, testing, completeness, and DRY/SRP/Rule-of-Three principles
  • Specialized frontend/backend review passes applied framework-specific lenses where those files changed
  • For UI changes: manual QA and an accessibility audit against a live dev server, when available
  • Checked CI status and linked issue acceptance criteria
  • Synthesized one review from those passes and chose the verdict from the findings, CI status, and QA evidence

return {
responseIdentifier: RESPONSE_IDENTIFIER,
prompt: '',
items: [],

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.

blocking: items: [] deviates from the issue ("Crucial: It must seed the items array with two empty items"), and it produces invalid XML.

InteractionSection/index.vue:68 builds fresh state via newDescriptor.parse('', []) when the author picks Ordering, so buildOrderingInteractionXML emits a childless <qti-order-interaction/>. OrderInteractionDType requires at least one choice (schema/xsd/imsqti_itemv3p0p1_v1p0.xsd:29157, minOccurs="1"). OrderingInteractionEditor.vue:293 then emits update:interaction on mount in edit mode, pushing that invalid XML up to QTIItemEditor before the author types anything — and the author sees "At least 2 items are required" on a pristine question.

Restoring the two-item seed fixes all of it. parse.spec.js:21 and OrderingInteractionEditor.spec.js:234 currently pin the deviation and need updating too.

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.

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.

Understood — matching choice's single-item seed it is. Withdrawing the two-item requirement.

:imageProcessor="EditorImageProcessor"
class="editor"
@update="setPrompt"
@blur="runValidation"

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.

suggestion: @blur="runValidation" never fires (here and on each item, line 116). TipTapEditor declares emits: ['update', 'minimize', 'open-editor'] (TipTapEditor.vue:329) and emits no blur; in Vue 2 a listener without .native only receives custom events.

So the issue's "calls runValidation() on prompt blur and each item content RTE blur" does not happen — validation comes solely from the 400 ms debounced watcher in useInteraction.js:60, which fires right after addItem() and turns the pristine new row red. That is the harsh UX the issue wanted to avoid, and OrderingInteractionEditor.spec.js:215-228 asserts it.

Either add a blur emit to TipTapEditor, or use @minimize (which already fires on click-outside and Escape), or drop the listeners.

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.

We do not need a blur here to execute runValidation, because update is already fired on blur, and this updates the interaction state, and it runs the validation on every state mutation, so this is not needed even if TipTapEditor emitted a blur event.

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.

You're right — I missed that TipTapEditor emits update only on blur (TipTapEditor.vue:250-263), so the state mutation and its validation already happen at that point. Withdrawing this finding.

</div>
<div class="item-content">
<TipTapEditor
:ref="el => setItemRef(el, index)"

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.

suggestion: the ref/focus plumbing is dead code. TipTapEditor's setup() return exposes no focus, so typeof editorEl.focus === 'function' at line 375 is always false. Focus on a new item comes from :autofocus="isItemOpen(item.id)" plus openItem(newId).

itemEditorRefs (274), setItemRef (276-278), this :ref binding and the focus block (374-375) can all go, along with the await nextTick() in onAddItem. Also, itemEditorRefs.value is assigned by index and never pruned, so removals leave stale entries. ChoiceInteractionEditor.onAddChoice has none of this.

return doc.documentElement;
}

describe('_defaultState()', () => {

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.

nitpick: these three cases reach into the _-prefixed export and assert the object literal back at itself, with no code path in between. parse('', []) (line 37) and parse('<not-valid', []) (line 42) already exercise the same default through the public descriptor, which is where a caller can observe it.

emit('update:interaction', newVal);
});

watch(

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.

nitpick: two watchers on props.mode with { immediate: true } (257-272 and here). Merging them makes the mount-time ordering — open prompt/item, then emit — explicit instead of dependent on declaration order.

message: 'Order',
context: 'Display name for an order question type',
},
orderingLabel: {

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.

nitpick: orderLabel ("Order", same context) sits four lines above and is unused. Two near-identical strings for one concept is extra translator work — reuse one or drop the other.

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.

Let's remove the orderLabel if it's not used! :)

}));

watch(workingInteraction, newVal => {
if (props.mode !== 'edit') return;

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.

praise: the props.mode !== 'edit' guard before emitting is the fix the issue asked for, and it is genuinely new — ChoiceInteractionEditor.vue:332 still emits unconditionally. OrderingInteractionEditor.spec.js:194 locks it in.

@AlexVelezLl AlexVelezLl left a comment

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.

Thanks! Looking good! Found some additional comments, the remaining rtibblesbot comments are valid and should be handled 👐

:imageProcessor="EditorImageProcessor"
class="editor"
@update="setPrompt"
@blur="runValidation"

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.

We do not need a blur here to execute runValidation, because update is already fired on blur, and this updates the interaction state, and it runs the validation on every state mutation, so this is not needed even if TipTapEditor emitted a blur event.

Comment on lines +57 to +63
<div
v-if="mode === 'edit'"
class="ordering-sublabel"
:style="{ color: $themeTokens.annotation }"
>
{{ correctOrderDescription$() }}
</div>

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.

Could you add this v-if="mode === 'edit'" to the choice interaction and text entry interaction please? so that we dont show the "Select one correct answer" if it's not on edit mode.

class="item-layout"
:class="{
'is-open': isItemOpen(item.id),
'small-screen': windowIsSmall,

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.

For 1, if we don't need a specific style for small screens, then we can just remove this class here.

For 2, it'd be best to collapse the move up and move down buttons if windowIsSmall.value is true.

return {
responseIdentifier: RESPONSE_IDENTIFIER,
prompt: '',
items: [],

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.

message: 'Order',
context: 'Display name for an order question type',
},
orderingLabel: {

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.

Let's remove the orderLabel if it's not used! :)

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] Implement Ordering Interaction editor

3 participants