Fix rich-text editor Mod-Enter commit dropping selected content#377
Merged
Conversation
… rich editor In the rich-text (tiptap) block editor, committing with `Mod-Enter` while text was selected deleted the selected text. Most visible path: click a block → select-all → bold → commit → the block was written to disk EMPTY (a tight list item `- banana` became `-`; a paragraph was deleted entirely). Root cause: tiptap's HardBreak extension binds `Mod-Enter` → setHardBreak() — the same key RichTextEditor used to commit, but via a DOM keydown listener that ran AFTER ProseMirror's keymap plugins. So `Mod-Enter` (1) fired setHardBreak, replacing the current selection with a hard break (over a full selection: the text is deleted, leaving `paragraph[hardBreak]`), then (2) the DOM handler's preventDefault fired too late and committed the emptied doc. With a collapsed caret it only appended a harmless trailing hard break — which is why plain text edits looked fine but always carried a stray break. Not specific to `Plain`/list items and not introduced by the Plain-gate change (bd-7pxub583): it reproduces identically on a `Para`, in the shared, type-agnostic RichTextEditor path. Fix (A)+(B): - (B) editorConfig.ts: a shared `buildRichTextExtensions()` disables StarterKit's built-in HardBreak and re-adds one bound to `Shift-Enter` ONLY, so `Mod-Enter` never inserts a break. Adds `@tiptap/extension-hard-break` as a direct dep. - (A) RichTextEditor.tsx: Escape / `Mod-Enter` / `Enter` move into a high-priority tiptap keymap (`q2CommitKeymap`) that runs inside ProseMirror's keymap and wins deterministically; the racy DOM keydown listener is removed (the focusout-commit is kept). Preserved: Escape cancels, plain Enter is swallowed (no split), `Shift-Enter` is a hard break, dirty/stale guards. Tests: - richtext/editorConfig.test.ts (fast jsdom): real `Mod-Enter` over a full selection leaves the text intact; no trailing hardBreak at a collapsed caret; `Shift-Enter` still inserts one. Verified red with the fix reverted. - hub-client/e2e/q2-preview-richtext-bold-content-loss.spec.ts (Playwright, real keyboard): bolding a tight-list item and a paragraph now round-trip to `**text**` on disk with no content loss. Was red before the fix. preview-renderer: tsc clean, 487 unit + 517 integration green. hub-client build + 229 unit tests green. Strand: bd-hafs0qho Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
In the
format: q2-previewrich-text (tiptap) block editor, committing withMod-Enterwhile text was selected deleted the selected text. Most visible path: click a block → select-all → bold → commit → the block was written to disk empty (a tight list item- bananabecame-; a paragraph was deleted entirely).Found during end-to-end verification of #376 (rich-text editing for
Plainblocks). It is not specific toPlain/list items and not introduced by that change — it reproduces identically on aPara, in the shared, type-agnostic RichTextEditor path.Root cause
tiptap's HardBreak extension binds
Mod-Enter→setHardBreak()— the same key RichTextEditor used to commit, but via a DOMkeydownlistener that ran after ProseMirror's keymap plugins. SoMod-Enter:setHardBreak(), replacing the current selection with a hard break — over a full selection (after select-all + bold) the text is deleted, leavingparagraph[hardBreak];preventDefaultfired too late and committed the emptied doc.With a collapsed caret it only appended a harmless trailing hard break — which is why plain text edits looked fine but always carried a stray break. The serializer and the Rust commit path were both verified correct in isolation; the fault was entirely in the editor's key handling.
Fix — (A)+(B)
editorConfig.ts(new): a sharedbuildRichTextExtensions()disables StarterKit's built-in HardBreak and re-adds one bound toShift-Enteronly, soMod-Enternever inserts a break. Adds@tiptap/extension-hard-breakas a direct dep.RichTextEditor.tsx:Escape/Mod-Enter/Entermove into a high-priority tiptap keymap (q2CommitKeymap) that runs inside ProseMirror's keymap and wins deterministically; the racy DOMkeydownlistener is removed (the focusout-commit is kept). Preserved: Escape cancels, plain Enter is swallowed (no split),Shift-Enteris a hard break, dirty/stale guards.Rationale for doing both (per maintainer): a hard break on
Mod-Enteris never wanted here — the rich editor isn't meant for extensive multi-block editing (the preview's plain-text editor and hub-client's source editor cover that).Tests
richtext/editorConfig.test.ts(fast jsdom): a realMod-Enterover a full selection leaves the text intact; no trailing hardBreak at a collapsed caret;Shift-Enterstill inserts one. Verified red with the fix reverted.hub-client/e2e/q2-preview-richtext-bold-content-loss.spec.ts(Playwright, real keyboard): bolding a tight-list item and a paragraph now round-trip to**text**on disk with no content loss. Was red before the fix.Verification
cargo xtask verify— all steps pass.Strand: bd-hafs0qho (discovered-from bd-7pxub583).
🤖 Generated with Claude Code