Autofocus RTE when answer/hint editor opens#5921
Conversation
Answer and hint fields now receive focus when their RTE opens (on click or when a new answer/hint is added), matching the existing behaviour of the question editor. Cursor is placed at the end of any existing text so the user can type immediately. Also fixes a pre-existing bug in HintsEditor desktop layout where @open-editor was passing the undefined variable `answerIdx` instead of `hintIdx`. Fixes learningequality#5885 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rtibblesbot
left a comment
There was a problem hiding this comment.
Clean fix for a P1 bug. The two-path design — mode watcher for cached editors, TipTap's own autofocus option for freshly initialised ones — is the right approach.
CI passing. "After" video confirms answer editors open focused with cursor at end of text.
- suggestion: guard
editor.valueinsidenextTick(inline) - praise:
emitOpen(hintIdx)rename (inline)
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Reviewed the pull request diff checking for:
- Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
- Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
- Architecture: duplicated concerns, minimal interfaces, composition over inheritance
- Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
- Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
- Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
- Checked CI status and linked issue acceptance criteria
- For UI changes: inspected screenshots for layout, visual completeness, and consistency
| } | ||
| if (newMode === 'edit' && editor.value && props.autofocus) { | ||
| nextTick(() => { | ||
| editor.value.commands.focus('end'); |
There was a problem hiding this comment.
suggestion: editor.value is checked synchronously on line 208 but isn't re-checked inside the nextTick callback. If the component unmounts between the watcher firing and the callback executing (e.g. the answer is deleted in the same tick it was opened), useEditor's onUnmounted calls destroyEditor() which sets editor.value = null, and this line will throw.
Simple fix: editor.value?.commands.focus('end');
There was a problem hiding this comment.
Good catch — you're right that the synchronous guard doesn't protect against the component unmounting between the watcher firing and the nextTick callback running. Restored the optional chaining in f8bc773.
| @update="updateHintText($event, hintIdx)" | ||
| @minimize="emitClose" | ||
| @open-editor="emitOpen(answerIdx)" | ||
| @open-editor="emitOpen(hintIdx)" |
There was a problem hiding this comment.
praise: Good catch on the pre-existing answerIdx → hintIdx rename. answerIdx was never in scope here, so emitOpen was silently receiving undefined and the keyboard open-editor path was broken for hints on desktop. Including it in this PR rather than leaving it as a separate issue was the right call.
The synchronous null check doesn't protect against the component unmounting between the watcher firing and the nextTick callback running — useEditor's onUnmounted sets editor.value to null, which would cause a throw. Optional chaining handles this safely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds tests to AnswersEditor and HintsEditor specs verifying that the editor at the open index receives autofocus=true and all other editors receive autofocus=false. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndAllComponents
Replace { name: 'RichTextEditor' } with the imported TipTapEditor
reference so that a component rename causes a clear import error
rather than a silent test failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rtibblesbot
left a comment
There was a problem hiding this comment.
Prior findings
Resolved:
editor.valueunguarded insidenextTickcallback (suggestion) — fixed witheditor.value?.commands.focus('end')atTipTapEditor.vue:210
1/1 prior findings resolved. 0 re-raised.
CI still running (frontend tests, build, linting in progress); no failures at time of review. "After" video confirms answer editor receives focus immediately on open, cursor at end — behavior matches description. No new findings.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
| }); | ||
|
|
||
| it('passes autofocus=true to the open answer editor', () => { | ||
| const editors = wrapper.findAllComponents(TipTapEditor); |
There was a problem hiding this comment.
praise: Switching from { name: 'RichTextEditor' } to the imported TipTapEditor reference is strictly better — name-based lookup silently returns zero results after a rename, while reference-based lookup fails loudly. Good improvement to existing test infrastructure.
Summary
HintsEditordesktop layout where@open-editorwas passing the undefined variableanswerIdxinstead ofhintIdxHow it works:
TipTapEditor's mode watcher now callseditor.commands.focus('end')when switching to'edit'mode andprops.autofocusistrue.AnswersEditorandHintsEditorpass:autofocus="isAnswerOpen(answerIdx)"/:autofocus="isHintOpen(hintIdx)"so autofocus is tied to the currently-open editor. For newly created answers/hints (where the editor isn't yet in thekeep-alivecache), the TipTapautofocusoption handles focus at initialisation time.Manually verified: clicking a closed answer or hint opens the RTE with focus and cursor at the end of existing text; clicking "New answer" / "New hint" opens an empty focused editor.
Before: Clicking an answer/hint opens the RTE editor but keyboard focus stays elsewhere — user must click again to type
editor.-.focus.mp4
After: Clicking an answer/hint opens the RTE and immediately receives focus with cursor at end of text
Screen.Recording.2026-05-18.at.17.00.29.mov
References
Fixes #5885
Reviewer guidance
@open-editor="emitOpen(hintIdx)"fix inHintsEditordesktop layout (wasanswerIdx) is a safe rename —answerIdxwas alwaysundefinedin that scopeAI usage
Implemented with Claude Code. I reviewed the generated diff, confirmed the approach (mode watcher + autofocus prop threading), and manually tested the exercise editor in the dev environment.