This repository was archived by the owner on Apr 23, 2026. It is now read-only.
Commit 8c9ecd5
refactor(editor): replace DOM manipulation with React node views and floating-ui (#253)
* refactor(editor): replace DOM manipulation with React node views and floating-ui
- Convert all inline node views (emoji, blob attachment, event preview) from
imperative document.createElement() to React components via ReactNodeViewRenderer
- Replace tippy.js with @floating-ui/react-dom for suggestion popup positioning
- Create useSuggestionRenderer hook bridging Tiptap suggestion callbacks to React state
- Extract shared EmojiMention, SubmitShortcut, and inline node extensions to separate files
- Extract types (EmojiTag, BlobAttachment, SerializedContent) to editor/types.ts
- Extract serialization logic to editor/utils/serialize.ts
- Remove redundant DOM keydown listener from RichEditor (handled by SubmitShortcut extension)
- Remove tippy.js dependency (-1045 lines net, RichEditor 632→297, MentionEditor 1038→354)
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* fix(editor): fix suggestion popover positioning, scrolling, and profile click behavior
- Replace UserName component in ProfileSuggestionList with plain text display
so clicking a suggestion autocompletes instead of opening their profile
(UserName has an onClick that calls addWindow and stopPropagation)
- Add react-virtuoso to ProfileSuggestionList for efficient lazy rendering
of up to 20 search results with fixed item height scrolling
- Add profile avatars with lazy loading and initial-letter fallback
- Fix SuggestionPopover positioning with autoUpdate for scroll/resize tracking
- Add size middleware to constrain popover max-height to available viewport space
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* refactor(editor): convert emoji suggestion from grid to scrollable list with Virtuoso
Replace the 8-column grid layout with a vertical list matching the profile
suggestion style — each row shows the emoji preview alongside its :shortcode:
name. Uses react-virtuoso with fixedItemHeight for lazy rendering and smooth
keyboard-driven scrolling through large emoji sets.
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* fix(editor): set mentionSuggestionChar to ':' for emoji nodes
When backspacing over a mention-based node, Tiptap inserts the node's
mentionSuggestionChar attribute as undo text. The EmojiMention extension
inherits Mention's default of '@', so deleting an emoji left '@' instead
of ':'. Fix by explicitly setting mentionSuggestionChar: ':' in the emoji
command's attrs for both RichEditor and MentionEditor.
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* test(editor): add comprehensive test suite for custom TipTap extensions
Tests all 8 custom extensions using headless TipTap Editor instances in
jsdom environment (TipTap has no official testing package):
- EmojiMention: schema, renderText (unicode vs custom), mentionSuggestionChar
attribute handling, backspace behavior regression test
- BlobAttachmentRichNode/InlineNode: schema (block vs inline), attributes,
renderText URL serialization, parseHTML selectors
- NostrEventPreviewRichNode/InlineNode: schema, renderText encoding for
note/nevent/naddr back to nostr: URIs
- SubmitShortcut: Mod-Enter always submits, Enter behavior with
enterSubmits flag
- FilePasteHandler: media type filtering (image/video/audio), non-media
rejection, mixed paste filtering, edge cases (no files, no callback)
- NostrPasteHandler: bech32 regex matching (npub/note/nevent/naddr/nprofile),
nostr: prefix handling, URL exclusion, node creation (mention vs preview),
surrounding text preservation, multiple entities
- Serialization: formatBlobSize, serializeRichContent (emoji tag extraction,
blob dedup, address refs), serializeInlineContent (mention→nostr: URI,
emoji→shortcode, blob→URL, event preview encoding)
90 new tests total.
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* fix(editor): paste handler and serialization bugs found via adversarial testing
NostrPasteHandler fixes:
- Punctuation after bech32 now matches (npub1..., npub1...! etc.)
Changed trailing lookahead from (?=$|\s) to (?=$|\s|[.,!?;:)\]}>])
- Fixed double-space between entities — unconditional " " after every
entity caused doubled spaces. Now only adds trailing space when entity
is at the very end of pasted text (for cursor positioning).
- Tightened regex character class from [\w] to [a-z0-9] to match actual
bech32 charset (rejects uppercase, underscore)
- Wrapped dispatch in try/catch to handle block-node-at-inline-position
errors gracefully (falls back to default paste)
Serialization fix:
- serializeRichContent now guards blob collection with `url && sha256`
matching the defensive checks already in serializeInlineContent.
Previously null sha256 would corrupt the dedup Set and null url would
produce invalid BlobAttachment entries.
Added 22 new edge case tests:
- Paste handler: punctuation boundaries, double-space regression,
malformed bech32 fallback, uppercase rejection, error resilience
- Serialization: empty editor, null sha256/url blobs, invalid pubkey
fallback, missing mention attrs, inline dedup, multi-paragraph
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* fix(editor): raise suggestion search limits for profiles and emojis
Both suggestion dropdowns use Virtuoso for virtualized rendering, so
they can handle large result sets without performance issues. The
previous limits (20 profiles, 24 emojis) were too restrictive — users
with many custom emojis sharing a substring or large contact lists
couldn't scroll to find the right match.
Raised both limits to 200 to allow thorough browsing while still
bounding the result set.
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* refactor(chat): rework emoji picker to scrollable list with search
Replace the fixed 1-row grid (8 emojis) with a scrollable virtualized
list matching the editor's EmojiSuggestionList look & feel:
- Search box at top with magnifying glass icon
- Virtuoso-backed scrollable list (8 visible items, unlimited results)
- Each row shows emoji icon + :shortcode: label
- Keyboard navigation: arrow keys to select, Enter to confirm
- Mouse hover highlights, click selects
- Frequently used emojis still shown first when no search query
- Narrower dialog (max-w-xs) for a compact picker feel
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
* fix: add address field to EmojiTag in editor types, fix GroupMessageOptions
- Add optional `address` field to EmojiTag in editor/types.ts to match
NIP-30 changes from main (30030 emoji set address)
- Extend GroupMessageOptions with MetaTagOptions to fix type error in
GroupMessageBlueprint's setMetaTags call
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* fix(editor): restore address attr, fix serialization, UserName, no-scroll
- Restore `address` attribute in shared EmojiMention extension (emoji.ts)
that was dropped during refactor — required for NIP-30 emoji set tracking
- Extract `address` from emoji nodes in both serializeRichContent and
serializeInlineContent so it makes it into published events
- Fix MentionEditorProps.onSubmit signature: use EmojiTag[] (not the narrower
inline type) so address field flows through to callers
- Restore UserName component in ProfileSuggestionList for proper display
with Grimoire member badges and supporter flame
- Remove scrollbar when all items fit: set overflow:hidden on Virtuoso when
items.length <= MAX_VISIBLE (profile list, emoji list, emoji picker dialog)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---------
Co-authored-by: Claude <[email protected]>1 parent cffb981 commit 8c9ecd5
30 files changed
Lines changed: 3480 additions & 1437 deletions
File tree
- src
- components
- chat
- editor
- extensions
- hooks
- node-views
- utils
- hooks
- lib
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
90 | | - | |
91 | | - | |
| 91 | + | |
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
32 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
33 | 37 | | |
34 | 38 | | |
35 | 39 | | |
| |||
0 commit comments