Skip to content

Autofocus RTE when answer/hint editor opens#5921

Open
akolson wants to merge 4 commits into
learningequality:hotfixesfrom
akolson:new-rte-autofocus
Open

Autofocus RTE when answer/hint editor opens#5921
akolson wants to merge 4 commits into
learningequality:hotfixesfrom
akolson:new-rte-autofocus

Conversation

@akolson
Copy link
Copy Markdown
Member

@akolson akolson commented May 18, 2026

Summary

  • Answer and hint RTE fields now automatically receive focus when opened (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 continue typing immediately without repositioning the cursor
  • Fixed a pre-existing bug in HintsEditor desktop layout where @open-editor was passing the undefined variable answerIdx instead of hintIdx

How it works: TipTapEditor's mode watcher now calls editor.commands.focus('end') when switching to 'edit' mode and props.autofocus is true. AnswersEditor and HintsEditor pass :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 the keep-alive cache), the TipTap autofocus option 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 an exercise with existing answers and hints
  • Click a closed answer → RTE should open with cursor at end of text, ready to type
  • Click a closed hint → same behaviour
  • Click "New answer" / "New hint" → empty RTE should open focused
  • Verify the question editor still behaves as before (no regression)
  • The @open-editor="emitOpen(hintIdx)" fix in HintsEditor desktop layout (was answerIdx) is a safe rename — answerIdx was always undefined in that scope

AI 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.

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>
Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.value inside nextTick (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');
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: 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');

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)"
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: Good catch on the pre-existing answerIdxhintIdx 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.

@akolson akolson changed the title fix(exercises): autofocus RTE when answer/hint editor opens Autofocus RTE when answer/hint editor opens May 18, 2026
akolson and others added 3 commits May 18, 2026 17:45
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>
Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior findings

Resolved:

  • editor.value unguarded inside nextTick callback (suggestion) — fixed with editor.value?.commands.focus('end') at TipTapEditor.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);
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: 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.

@akolson akolson requested a review from rtibbles May 18, 2026 16:02
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.

2 participants