Skip to content

feat(editor): experimental Tiptap-based message composer#876

Draft
Just-Insane wants to merge 6 commits into
SableClient:devfrom
Just-Insane:feat/tiptap-composer
Draft

feat(editor): experimental Tiptap-based message composer#876
Just-Insane wants to merge 6 commits into
SableClient:devfrom
Just-Insane:feat/tiptap-composer

Conversation

@Just-Insane
Copy link
Copy Markdown
Contributor

@Just-Insane Just-Insane commented May 19, 2026

Description

Add an experimental Tiptap (ProseMirror) message composer as a drop-in replacement for the Slate composer, gated behind Settings > Experimental > "Tiptap Composer". Supports @mention, #room, and :emoticon: autocomplete with inline formatting. Uploads, replies, scheduled messages, and voice messages are not yet implemented.

Fixes #

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

AI disclosure:

  • Partially AI assisted (clarify which code was AI assisted and briefly explain what it does).
  • Fully AI generated (explain what all the generated code does in moderate detail).

The Tiptap extension wiring and tiptapToMatrixCustomHTML serialiser were drafted with AI assistance. Each autocomplete type (@mention, #room, :emoticon:) is implemented as a Tiptap Suggestion plugin that delegates query/render to the existing Sable autocomplete state machines. The serialiser walks the ProseMirror document tree and emits <a> pill markup for mention and room nodes. The tiptapCustomHtmlEqualsPlainText function uses DOMParser.parseFromString (replacing a manual regex chain flagged by CodeQL) to extract plain text from the HTML output for the equality check.

Adds an opt-in Tiptap-based message composer gated behind
Settings > Experimental > 'Tiptap Composer'.

New packages:
  @tiptap/core @tiptap/react @tiptap/pm @tiptap/starter-kit
  @tiptap/extension-mention @tiptap/extension-link
  @tiptap/extension-placeholder @tiptap/suggestion

New files:
  src/app/components/editor-tiptap/
    extensions/MentionExtension.ts   – Matrix mention node
    extensions/EmoticonExtension.ts  – custom emoji node
    extensions/CommandExtension.ts   – /command node
    TiptapEditor.tsx                 – drop-in editor component
    TiptapEditor.css.ts              – vanilla-extract styles
    output.ts                        – Matrix HTML + plain-text serializers
    index.ts                         – public barrel

  src/app/features/room/
    RoomInputTiptap.tsx              – experimental composer component
    tiptap-autocomplete/
      TiptapAutocompleteMenu.tsx     – shared popup shell (no Slate dep)
      TiptapMentionAutocomplete.tsx  – @user mention popup
      TiptapRoomMentionAutocomplete.tsx – #room mention popup
      TiptapEmoticonAutocomplete.tsx – :emoticon: popup

Modified files:
  src/app/state/settings.ts           – add useTiptapComposer: boolean
  src/app/features/settings/experimental/Experimental.tsx
                                      – add TiptapComposerToggle
  src/app/features/room/RoomView.tsx  – conditional render of
                                        RoomInputTiptap vs RoomInput

Not yet implemented in Tiptap composer:
  file uploads, reply drafts, scheduled messages, voice recording,
  emoji board, message draft persistence, command autocomplete popup
…TiptapEditor

vanilla-extract selectors{} only supports &-anchored self-selectors.
Descendant selectors like '& .ProseMirror' must use globalStyle() instead.
Comment thread src/app/components/editor-tiptap/output.ts Fixed
Comment thread src/app/components/editor-tiptap/output.ts Fixed
- Remove unused Icon, Icons, JoinRule, isRoomAlias, getMxIdServer, getViaServers,
  mDirectAtom imports from TiptapRoomMentionAutocomplete
- Remove unused viaServers and isDM variables from TiptapRoomMentionAutocomplete
- Remove unused useEffect from TiptapAutocompleteMenu and RoomInputTiptap
- Remove unused Box and useMediaAuthentication from TiptapEmoticonAutocomplete
- Remove unused label/highlight destructure in output.ts mention case
- Fix roomId shadow in insertRoomMention callback (rename to mentionRoomId)
- Fix \0 control character in regex → \u0000 with eslint-disable comment
- Add eslint-disable for no-redundant-type-constituents on TiptapEditorInstance
- Consolidate editor-tiptap imports in RoomInputTiptap to go through index.ts
- Remove unused @tiptap/pm and @tiptap/suggestion from package.json
Comment thread src/app/components/editor-tiptap/output.ts Fixed
Comment thread src/app/components/editor-tiptap/output.ts Fixed
@Just-Insane
Copy link
Copy Markdown
Contributor Author

This is likely not going forward, in theory it works, but rebuilding all the integrations like the emoji picker, audio, etc is a lot of work that I don't have time for.

Not sure if this should be left as a POC that others can pick up and tinker with or...?

…dings

Replaces manual tag-stripping regex + chained entity unescaping with
DOMParser.parseFromString, which handles both safely. Resolves two CodeQL
security warnings in tiptapCustomHtmlEqualsPlainText:
- CWE-116: double-unescaping via chained &amp; → & replacement
- CWE-80: incomplete tag sanitization leaving <script paths open
…dundant cast

- Move getKey (renamed getEmoticonKey) out of TiptapEmoticonAutocomplete
  to module scope to satisfy oxlint consistent-function-scoping.
- Move getSearchStr (renamed getRoomSearchStr) out of
  TiptapRoomMentionAutocomplete to module scope for the same reason.
- Remove redundant 'as' cast on prefix in RoomInputTiptap — the type
  guard at line 84 already narrows the type to '@' | '#' | ':'.
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