fix: composer UX improvements (cursor, upload guard, scroll, paste, perf)#694
Merged
Conversation
Add isUploading guard to both MessageComposer and ForumComposer: - sendDisabled memo now includes media.isUploading (disables button visually) - submitMessage uses isUploadingRef to prevent Enter key bypass
Split useRichTextEditor's focus into focusEnd() and focusPreserve(). focusPreserve() ensures DOM focus without moving the ProseMirror selection — used by the reply-target effect so incoming messages don't yank the caret to the end while the user is editing mid-text. focusEnd() remains the default for mount, channel switch, and entering edit mode where jumping to end is the correct behavior.
Instead of rebuilding all decorations from scratch on every keystroke, use DecorationSet.map(tr.mapping) to shift existing decoration positions for edits that don't touch mention boundaries (@ or #). Full rebuild only triggers when: - The meta key is set (names/channels list changed) - The edited text contains @ or # (mention may have been created/modified/destroyed) This eliminates unnecessary DOM churn from decoration replacement on every character typed, which could cause cursor flicker in edge cases.
Previously, pasting any content that contained mention/channel-link elements would flatten the ENTIRE paste to plain text, stripping all formatting (bold, italic, line breaks). This was because normalizeMentionClipboardHtml returned doc.body.textContent. Now it returns cleaned HTML (with mention wrappers replaced by plain spans and font-weight:600 styles stripped) and the paste handler uses Tiptap's insertContent to parse the HTML — preserving all surrounding rich formatting while still preventing mention font-weight from being misinterpreted as bold.
The composer is absolutely positioned over the chat content. Previously the scroll container had a static pb-24 (96px) for clearance, but the composer can grow well beyond that (multi-line text, attachments, reply banners). This left the bottom of the message timeline unscrollable. Add useComposerHeightPadding hook that uses ResizeObserver to track the composer wrapper's height and sets paddingBottom on the scroll container to match. If the user is already scrolled to the bottom, auto-scrolls to keep them there. Applied to both the main channel timeline (ChannelPane) and the thread panel (MessageThreadPanel).
The useComposerHeightPadding hook already provides dynamic bottom padding on the scroll container. The static pb-10 on the inner content div was additive, creating too much space below the last message.
The previous approach used insertContent(htmlString) which Tiptap treated as plain text, dumping raw HTML tags into the composer. Switch to view.pasteHTML(cleanHtml) which uses ProseMirror's built-in HTML parsing pipeline — the same path as default paste handling — to correctly convert the cleaned HTML into rich content nodes.
c5a7f71 to
1f4e2d4
Compare
1f4e2d4 to
4e65d83
Compare
When known mention names are available (resolved from message p-tags + profiles), the renderer now only highlights those specific names — no longer falls back to a generic @\S+ pattern that would highlight any @word as a mention. The fallback is preserved for when names are empty (old messages without p-tags, or while profiles are still loading). This makes the renderer consistent with the composer's mentionHighlightExtension which already only highlights known names.
4e65d83 to
f4dd4e6
Compare
tlongwell-block
approved these changes
May 21, 2026
Edits inside an already-highlighted mention (e.g. @max → @marx) didn't contain @ or #, so editAffectsMentionBoundary returned false and the old decoration was just mapped — leaving a stale highlight. Add editIntersectsDecoration check: if any changed range overlaps an existing decoration, trigger a full rebuild. The fast-path (edits that don't touch mentions at all) is preserved.
This was referenced May 28, 2026
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.
Composer UX Improvements
A batch of fixes and improvements to the chat composer, addressing cursor behavior, upload guards, scroll padding, paste formatting, and mention rendering consistency.
Changes
d97121c8f74e5110focusintofocusEnd/focusPreserve; reply-target effect no longer yanks caret when new messages arrive654f34baDecorationSet.map(tr.mapping)for normal keystrokes instead of full rebuild on every character68c79489normalizeMentionClipboardHtmlreturns cleaned HTML instead oftextContent; strips mentionfont-weight:600that confused Tiptap Bold detectionb07a5ce4useComposerHeightPaddinghook observes composer height via ResizeObserver, sets scroll containerpaddingBottomdynamically so content is never hidden8c762032756367d8insertContent(html)(treated as text) toview.pasteHTML(cleanHtml)for proper ProseMirror HTML→nodes parsing1f4e2d49buildPrefixPatternno longer falls back to generic@\S+when known names are available; only highlights @mentions that match resolved p-tag usersTesting
All manually verified:
Future follow-up (not in scope)
extractMentionPubkeysauto-resolution on send should require explicit dropdown selection