Repository navigation
Add the inline choice interaction editor - #6303
AlexVelezLl wants to merge 7 commits into
Conversation
🟡 Waiting for changesLast updated: 2026-10-08 21:31 UTC |
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #6303: the inline choice editor works end to end in manual QA. The type selector, insert flows, options panel, Shuffle, copy/paste ids, save/reopen and view mode all behave as #6182 specifies. CI is passing.
QA ran in the qti-demo shell because the QA server returned Redis 500s. axe found no new violations.
Suggestions, all inline:
- Options panel focus: clicking non-focusable parts drops focus, and
aria-modalwhile the passage stays editable. - Validation goes stale after removing a chip as text.
- Accessible name for the passage editor.
isSupportedBodyaccepts bodies TipTap can't hold.- Chip and announcer changes outside #6182's scope.
- Toolbar CSS and narrow-width overflow of Insert.
- Tests that match text anywhere on the page, and focus-trap tests that don't check where focus lands.
Written by rtibblesbot, an LLM-based coding agent.
|
|
||
| <!-- Passage --> | ||
| <div class="inline-choice-editor__section"> | ||
| <ValidationMessage v-if="hasDropdownErrors"> |
There was a problem hiding this comment.
nitpick: This message blames the options, but it also shows for NO_CORRECT_ANSWER, a dropdown-level error. Reword it to cover both, e.g. "One or more inline choices need attention".
18fbfe8 to
6055318
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #6303: 6 prior findings are still open; please fix or reply on each thread. 3 new suggestions below. 7 of 14 prior findings are resolved and 1 acknowledged.
CI passes. Manual QA ran 12 scenarios on the qti-demo shell, since Redis MISCONF blocked Django login. Scenarios: insert, options panel, keyboard/screen reader, undo/paste, shuffle, save/reopen, validation, RTL/touch. No regressions.
Still open:
aria-modalwhile the passage stays clickableisSupportedBodyaccepts bodies TipTap can't hold- chip behaviour that #6182 lists as out of scope
- undo restoring a chip isn't announced
- chip with options but no correct mark reads "Add answers"
- Insert overflows the passage toolbar below ~480px without touch. QA: 413px wide in a 301px toolbar at 375px.
- options use a bare
<input>, not the #5927 plain-text row - options-problem message shown for
NO_CORRECT_ANSWER(nitpick)
Prior-finding status
RESOLVED — InlineChoiceOptions/index.vue:9 — clicking heading/padding drops focus; Escape stops closing
UNADDRESSED — InlineChoiceOptions/index.vue:13 — aria-modal while passage stays interactive
RESOLVED — inlineChoice/Editor.vue:95 — validation stale after chip removed/restored
ACKNOWLEDGED — inlineChoice/Editor.vue:84 — passage editor indistinguishable from question field (separate issue under #6103)
UNADDRESSED — inlineChoice/Descriptor.js:43 — isSupportedBody accepts bodies TipTap can't hold
UNADDRESSED — InlineChoiceChip/index.vue:24 — chip behaviour out of scope per #6182
UNADDRESSED — InlineChoiceChip/index.vue:32 — chip with no correct mark reads as "Add answers"
UNADDRESSED — EditorToolbar.vue — Insert pushed off narrow toolbar (styling now via appearanceOverrides)
RESOLVED — inlineChoice/tests/Editor.spec.js — focus-trap tests pass with handlers swapped
RESOLVED — inlineChoice/tests/Editor.spec.js — no-id error test passes wherever it renders
RESOLVED — TipTapEditor/tests/imageSrc.spec.js — duplicates toStoredImageSrcs cases via stub
UNADDRESSED — InlineChoiceOptions/index.vue:97 — bare <input>, not the #5927 plain-text row
UNADDRESSED — inlineChoice/Editor.vue:52 — options-problem message for NO_CORRECT_ANSWER (nitpick)
RESOLVED — InlineChoiceChip/index.vue:14 — green fill on chip with no correct answer
RESOLVED — useInlineChoicePassage.js:75 — praise: live sync keeps editor history
Written by rtibblesbot, an LLM-based coding agent.
27faea8 to
79b7904
Compare
An item with several responses gets no response processing while any of them has nothing to score it by, such as an inline choice dropdown with no correct answer yet. That is an unfinished item, which validation already reports to the author, so it is not logged as well. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An unavailable action now fades as the toolbar's disabled buttons do, with a not-allowed cursor, and no longer changes on hover or press as if it could be used. In a small window it is an icon button, keeping its name for assistive technology, since its full label ran past the toolbar's edge in a narrow window or at a high zoom. As a last resort, a toolbar still too wide for its editor scrolls. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The editor stays open on a click outside it while any trigger inside it has aria-haspopup and aria-expanded="true". The toolbar's menus rely on that, and so does a node view that opens a panel beside the editor, such as the inline choice chip's options. It is now stated in the consumer docs and tested for a popup opened from the content, so a change to the check cannot silently close the editor while such a panel is in use. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A chip is not a place to type, and its button is how the keyboard reaches its options, so it is no longer selectable as a node: the arrow keys move past it rather than onto it. ProseMirror leaves Backspace and Delete beside such a node to the browser, which handles them unevenly, so they remove the chip themselves, in one undoable step. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The stored form of an editor's HTML, with images back to their stored filenames, is now `storedHTML` in `utils/imageSrc.js`. `TipTapEditor` reads its content out through it, and so can a consumer that reads an editor directly, without repeating how that form is made. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Turns on the Inline choice question type: an optional question, the passage holding the dropdown chips, and a modal options panel for the open chip, trapping focus with KFocusTrap and returning it to the chip on close. A press in the passage closes it, as the passage is then in use. Option edits go through the chip node's commands, so each is one step of the passage's history, and the passage is synced while a chip is open so the panel always matches the saved state. A change to the chips syncs it too, so validation follows a chip removed or restored without waiting for the passage to lose focus. Shuffle is the choice interaction's AnswerSettings, and hides reordering while on. The chips follow the design, and take the selection colour as a whole when a selection covers them. A preview without Show answers shows "Choose…" in place of each answer. The descriptor accepts any item body, since parse() splits every body into question and passage. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Left and Right on a focused chip put the caret back in the passage beside it, following the text direction, as they move past the chip there. - A chip the caret crosses is announced by its accessible name, since a screen reader reads out the text the caret crosses and a chip has none. Removing chips is announced too. Both use KDS's assertive live region, so they are read as a crossed character would be. - A chip that needs attention shows the error icon in place of its option count, as an option row does, so colour is not all that marks it. Its name says so in a preview that hides the answers too. - The passage editor is described by its helper text. The chip's label and accessible name move to passageChips.js, so the chip and the announcements name it the same way. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
79b7904 to
4ec7055
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #6303: manual QA found two blocking bugs:
- Clicking another chip can miss when the page is scrolled to its end.
- Ctrl+Z in an option field types back what it undid.
16 of 17 prior findings are resolved or acknowledged. Please address the open Editor.vue:56 nitpick: its message blames the options for a missing correct answer.
The other comments are smaller. CI pending. QA covered insert, options editing, keyboard, click-outside, chip navigation, undo/copy/paste, shuffle, save/reopen, validation, view mode and RTL. axe found nothing new.
Prior-finding status
RESOLVED — InlineChoiceOptions/index.vue:9 — clicking heading/padding drops focus to <body>
RESOLVED — InlineChoiceOptions/index.vue:13 — aria-modal while the passage stays usable
RESOLVED — inlineChoice/Editor.vue:100 — validation stale after chip removed/restored
ACKNOWLEDGED — inlineChoice/Editor.vue:89 — passage editor indistinguishable from question field
ACKNOWLEDGED — inlineChoice/Descriptor.js:43 — isSupportedBody accepts bodies TipTap can't hold
RESOLVED — InlineChoiceChip/index.vue:24 — chip behaviour changes out of scope
ACKNOWLEDGED — InlineChoiceChip/index.vue:42 — chip without correct mark reads like an option
RESOLVED — EditorToolbar.vue — Insert pushed off the toolbar in narrow windows
RESOLVED — inlineChoice/tests/Editor.spec.js — focus-trap tests pass with handlers swapped
RESOLVED — inlineChoice/tests/Editor.spec.js — no-id error test passes wherever it renders
RESOLVED — TipTapEditor/tests/imageSrc.spec.js — repeats toStoredImageSrcs cases through a stub
ACKNOWLEDGED — InlineChoiceOptions/index.vue:97 — bare <input> instead of plain-text toolbar row
UNADDRESSED — inlineChoice/Editor.vue:56 — message blames options for NO_CORRECT_ANSWER
RESOLVED — InlineChoiceChip/index.vue:14 — green fill on chip with no correct answer
RESOLVED — passageChips.js:45 — invalid chip marked by colour alone
RESOLVED — inlineChoice/Editor.vue:69 — helper text not referenced by the passage
RESOLVED — QTIItemEditor.spec.js:599 — findByText for the heading
Written by rtibblesbot, an LLM-based coding agent.
| toolbar buttons take no focus, so a press, not a focus change, tells. --> | ||
| <div | ||
| class="inline-choice-editor__section" | ||
| @pointerdown.capture="closeOptions" |
There was a problem hiding this comment.
blocking: Clicking another chip doesn't open it when the page is scrolled to its end.
Closing on pointerdown unmounts the panel before the click completes. The page gets shorter, so the browser clamps the scroll position. The chips then move down by the panel's height (~215px). Mouseup lands off the chip, so no click fires on it.
Steps: new Inline choice question (always added last) with three dropdowns, page scrolled to the bottom. Open dropdown 3, then click dropdown 1's chip.
- Expected: panel switches to dropdown 1.
- Actual: no panel opens. Once, the passage editor closed and focus fell to
<body>. - Reproduced 3 times. With 1500px of bottom padding the same steps work, so the scroll clamp is the cause.
Close on click instead, or keep the panel's height reserved until the pointer is released.
| }) | ||
| " | ||
| dir="auto" | ||
| @input="setOptionText(option.id, $event.target.value)" |
There was a problem hiding this comment.
blocking: Repeated Ctrl+Z in an option field types the undone text back.
Steps: insert a dropdown, type blue in Option 1, press Ctrl+Z six times.
- Expected:
blu,bl,b, empty, then earlier passage edits. - Actual:
blu→bl→b→ empty →b→bl. The chip follows each value.
Each native undo in the input fires @input. That records a new passage-history step. Once the input's own stack is empty, the browser hands Ctrl+Z to the passage editor. The passage editor then undoes those recorded undo steps.
Handle beforeinput here for historyUndo and historyRedo. Prevent the default, then run the passage editor's undo or redo, so only one history applies.
| class="error-icon" | ||
| :color="$themeTokens.error" | ||
| /> | ||
| <span |
There was a problem hiding this comment.
suggestion: The error icon replaces the option count, so a new chip never shows its count while the author edits options.
A newly inserted chip has errors from the start. The issue says option edits update "the open chip's count and label immediately". My earlier passageChips.js:45 ask (don't mark an invalid chip by colour alone) still stands.
Render the KIcon before the badge instead of using v-if/v-else.
|
|
||
| /* A last resort: the overflow list gives up tools first, but too narrow a window can | ||
| still leave what remains wider than the toolbar. */ | ||
| overflow-x: auto; |
There was a problem hiding this comment.
suggestion: The More menu (line 103) may now be clipped to the toolbar box.
overflow-x: auto also sets overflow-y to auto. That makes the toolbar the scroll parent for its popovers. KDS popovers stay inside their scroll parent by default.
Pass :constrainToScrollParent="false" to More, as FormatDropdown.vue:16 does. Then check More in a narrow window.
| expect(announced()).toBe(qtiEditorStrings.answerDropdownWithCorrect$({ label: 'x', count: 1 })); | ||
| }); | ||
|
|
||
| it('names a chip that needs attention as such', async () => { |
There was a problem hiding this comment.
suggestion: The hasErrors branch of chipMessage has no test.
This test never passes errorResponseIdentifiers. Dropping { hasErrors } there passes every test.
Pass errorResponseIdentifiers: ['r2'] through renderAt and expect answerDropdownNoCorrectNeedsAttention$.
| border-radius: 0.75em; | ||
| } | ||
|
|
||
| // A selected chip takes the selection's colour, which the browser only paints behind text, |
There was a problem hiding this comment.
nitpick: The selected chip doesn't take the selection's colour, though the comment here says it does.
QA saw a yellow chip inside blue selected text. The chip uses $themeBrand.secondary.v_100. The selected text uses the global ::selection (shared/styles/vuetify.scss:2482). So the chip looks like a separate highlight.
Match ::selection, or correct the comment.
Summary
References
Fixes #6182.
Beyond #6182's scope
#6182 leaves the dropdown chip and Insert to #6181, but this PR also changes the chip. Each change came up while building and testing the editor:
InlineChoiceNode.js). The arrow keys stopped on each dropdown to select it before moving past, and Backspace selected it rather than removing it.leaveForText). A dropdown is a focusable button in the text, and the arrow keys did nothing on it.useKLiveRegion, with two new strings. A screen reader said nothing in either case, since a dropdown has no text to read out.These can move to their own PR if preferred. Not done yet: undoing a removal brings the dropdown back without an announcement, while redo announces the removal.
Reviewer guidance
Demo
Grabacion.de.pantalla.2026-10-07.a.las.4.02.52.p.m.mov
AI usage
Claude Code implemented this from the issue and design screenshots, with tests, then self-reviewed it. I reviewed the changes and steered the design and accessibility decisions.
🤖 Generated with Claude Code