Skip to content

fix: composer UX improvements (cursor, upload guard, scroll, paste, perf)#694

Merged
tellaho merged 10 commits into
mainfrom
tho/fix/composer-improvements
May 21, 2026
Merged

fix: composer UX improvements (cursor, upload guard, scroll, paste, perf)#694
tellaho merged 10 commits into
mainfrom
tho/fix/composer-improvements

Conversation

@tellaho
Copy link
Copy Markdown
Collaborator

@tellaho tellaho commented May 21, 2026

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

Commit Description
d97121c8 Block send while media is uploading — disables Send button + Enter key guard while uploads are in progress (both MessageComposer and ForumComposer)
f74e5110 Prevent cursor jumping to end on thread re-renders — splits focus into focusEnd/focusPreserve; reply-target effect no longer yanks caret when new messages arrive
654f34ba Optimize mentionHighlightExtension — uses DecorationSet.map(tr.mapping) for normal keystrokes instead of full rebuild on every character
68c79489 Preserve rich formatting when pasting mentionsnormalizeMentionClipboardHtml returns cleaned HTML instead of textContent; strips mention font-weight:600 that confused Tiptap Bold detection
b07a5ce4 Dynamic scroll paddinguseComposerHeightPadding hook observes composer height via ResizeObserver, sets scroll container paddingBottom dynamically so content is never hidden
8c762032 Remove redundant pb-10 — inner content wrapper padding was stacking with dynamic padding; removed from both MessageTimeline and MessageThreadPanel
756367d8 Fix paste regression — switched from insertContent(html) (treated as text) to view.pasteHTML(cleanHtml) for proper ProseMirror HTML→nodes parsing
1f4e2d49 Renderer mention consistencybuildPrefixPattern no longer falls back to generic @\S+ when known names are available; only highlights @mentions that match resolved p-tag users

Testing

All manually verified:

  • ✅ Cursor stays put when new messages arrive in thread
  • ✅ Cannot send while upload in progress (Enter + click both blocked)
  • ✅ Scroll padding adjusts dynamically — last message always visible
  • ✅ Paste preserves formatting (no raw HTML leak)
  • ✅ Autocomplete preserves bold/line breaks
  • ✅ Renderer only highlights real @mentions (not arbitrary @words)

Future follow-up (not in scope)

  • Composer live-highlighting typed @mentions against relay-wide user directory (needs user directory API)
  • Revisiting whether extractMentionPubkeys auto-resolution on send should require explicit dropdown selection

tellaho added 7 commits May 21, 2026 12:17
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.
@tellaho tellaho force-pushed the tho/fix/composer-improvements branch from c5a7f71 to 1f4e2d4 Compare May 21, 2026 19:17
@tellaho tellaho marked this pull request as ready for review May 21, 2026 19:49
@tellaho tellaho requested a review from a team as a code owner May 21, 2026 19:49
@tellaho tellaho force-pushed the tho/fix/composer-improvements branch from 1f4e2d4 to 4e65d83 Compare May 21, 2026 19:51
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.
@tellaho tellaho force-pushed the tho/fix/composer-improvements branch from 4e65d83 to f4dd4e6 Compare May 21, 2026 20:35
tellaho added 2 commits May 21, 2026 14:30
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.
Assert @Marge does NOT match inside @margex and #general does NOT
match inside #generally. Protects the (?=\W|$) lookahead fix.
@tellaho tellaho enabled auto-merge (squash) May 21, 2026 22:45
@tellaho tellaho merged commit ee4ee5f into main May 21, 2026
15 checks passed
@tellaho tellaho deleted the tho/fix/composer-improvements branch May 21, 2026 22:53
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