Skip to content

feat(threads): implement thread side panel with full functionality#123

Open
Just-Insane wants to merge 29 commits intoSableClient:devfrom
Just-Insane:feat/threads
Open

feat(threads): implement thread side panel with full functionality#123
Just-Insane wants to merge 29 commits intoSableClient:devfrom
Just-Insane:feat/threads

Conversation

@Just-Insane
Copy link
Contributor

@Just-Insane Just-Insane commented Mar 10, 2026

Summary

Implements Matrix thread support with side panels, browser, and notification integration.

Features

  • Thread Drawer: Side panel for viewing and replying within threads
    • Full message rendering with replies, reactions, edits, and deletions
    • Emoji picker and sticker support
    • Automatic read receipt management
  • Thread Browser: Panel listing all room threads with search functionality
  • Thread Chips: Visual indicators on messages that start threads
    • Shows reply count
    • Click to open thread
  • Unread Badge: Discord-style notification badge on thread icon showing unread count
  • Cross-device Sync: Threads created on other devices automatically appear in list
    • Initializes Thread objects from room history on mount
    • Auto-creates Thread objects when receiving new thread events
  • Notification Integration: Clicking inbox notifications for thread replies opens the thread

Implementation Details

  • Uses matrix-js-sdk Thread API (room.createThread(), room.getThreads())
  • Filters thread replies from main timeline
  • Scans timeline on mount to initialize Thread objects for existing threads
  • Auto-creates Thread objects when receiving ThreadEvent.New
  • Auto-opens drawer when navigating to thread events from notifications
  • Tracks unread status using thread.getEventReadUpTo() and thread.hasCurrentUserParticipated

Related

Future enhancement tracked in #124 to leverage SDK's thread-specific notification APIs (getThreadUnreadNotificationCount(), hasThreadUnreadNotification()).

@Just-Insane
Copy link
Contributor Author

Also Fixes #124 - Use the SDK's notification options, will properly respect user's notification settings.

@Just-Insane Just-Insane marked this pull request as ready for review March 10, 2026 05:58
@Just-Insane Just-Insane requested a review from a team March 10, 2026 05:58
@Just-Insane Just-Insane marked this pull request as draft March 10, 2026 12:11
@Just-Insane Just-Insane force-pushed the feat/threads branch 2 times, most recently from fa71c0a to 1d65bfa Compare March 10, 2026 18:17
@Just-Insane Just-Insane marked this pull request as ready for review March 10, 2026 19:16
@7w1
Copy link
Member

7w1 commented Mar 11, 2026

A few issues I found, not sure which ones overlap with #124 :

  • Pressing reply on a thread message does nothing.
  • Reactions don't render until something triggers a render (editing a message, sending a message, etc)
  • No view source button on thread messages.
  • Threads don't auto scroll down to latest message.
  • When jumping to a message in a thread there's no background indication so... can't really tell exactly what's being jumped to.
  • No hover effect on messages
  • No mention background colors

Also, maybe we should align the editor at the bottom with the main editor? Looks a bit weird having it lower down.

@Just-Insane Just-Insane marked this pull request as draft March 12, 2026 03:12
@Just-Insane
Copy link
Contributor Author

A few issues I found, not sure which ones overlap with #124 :

  • Pressing reply on a thread message does nothing.
  • Reactions don't render until something triggers a render (editing a message, sending a message, etc)
  • No view source button on thread messages.
  • Threads don't auto scroll down to latest message.
  • When jumping to a message in a thread there's no background indication so... can't really tell exactly what's being jumped to.
  • No hover effect on messages
  • No mention background colors

Also, maybe we should align the editor at the bottom with the main editor? Looks a bit weird having it lower down.

These should all be fixed now, if you want to test?

@Just-Insane Just-Insane marked this pull request as ready for review March 12, 2026 04:57
@Just-Insane Just-Insane marked this pull request as draft March 13, 2026 16:05
Evie Gauthier added 9 commits March 13, 2026 12:06
- Add ThreadDrawer component with message rendering and input
- Add ThreadBrowser panel for viewing all threads in a room
- Add thread chips on messages showing reply count and participants
- Enable message actions in threads (edit, react, reply, delete)
- Add emoji and sticker rendering support in threads
- Filter thread replies from main timeline to avoid duplicates
- Auto-create Thread objects when starting threads or syncing from other devices
- Add unread thread badge to header icon (Discord-style)
- Auto-open thread drawer when navigating to thread events from notifications
- Reorder room header icons: search, pinned, threads, widgets, members, more
- Add automatic read receipts when viewing threads

Fixes thread browser showing empty list and inbox notifications not opening threads.
- Add PushProcessor for notification highlights
- Add Reply component support for in-thread replies
- Add message forwarding props support
- Add send status handling (resend/delete failed sends)
- Add memoized linkifyOpts and htmlReactParserOptions
- Add developer tools and read receipts support
- Bring full feature parity with RoomTimeline event rendering
Add key and data-message-id attributes to Message component in threads
to enable proper hover effects and message identification.
- Add messageList class with hover styles to ThreadDrawer.css.ts
- Apply messageList class to root message and replies containers
- Update padding to match RoomTimeline: S600 vertical spacing
- Fix input alignment: change from S200 to S400 horizontal padding
Don't add m.in_reply_to for regular thread messages, only for actual
replies within threads. Thread messages now use is_falling_back: true
per Matrix spec, and replies within threads use is_falling_back: false.
Add thread relation to reply drafts in threads so replies
are properly associated with the thread root event.
- Remove horizontal line separators to match room timeline
- Align header left padding (S400) with messages and input
- Update reply count label padding to match spacing system (S200/S400)
- Remove unused Line import
- Increase header size from 400 to 600 to align bottom border with room header
- Reduce root message and replies padding from S600 to S400 vertically
- Add bottom border separator after root message section (ThreadRootSection)
- Add bottom border separator after reply count label (ThreadReplyCountSection)
- Add following indicator below thread input, filtered to thread participants only
- Update RoomViewFollowing to accept optional threadEventId and participantIds props
Evie Gauthier added 7 commits March 13, 2026 12:07
…matrix.ts

- Fix import formatting in ThreadDrawer.tsx
- Change onResend/onDeleteFailedSend to accept MatrixEvent instead of string
- Remove unused variables (mx, autoplayEmojis)
- Fix variable shadowing issue (renamed nicknames to localNicknames)
- Format JSX props and code blocks per prettier rules
- Remove unused eslint-disable directive
- Fix trailing whitespace in RoomInput.tsx
- Format function arguments in matrix.ts
- Remove unused autoplayEmojis variable
- Format htmlReactParserOptions dependency array to single line
- Remove unnecessary 'room' dependency from handleDeleteFailedSend
- Remove unnecessary type assertion from querySelector
- Format querySelector template string to single line
- Format latestThreadEventId ternary expression
…read indicator

- Add shrink="No" to reply count Box to prevent it from moving up as messages are added
- Remove threadRootId prop from Reply component in thread view to hide redundant thread indicator icon
- Add height: 100% to ThreadDrawer container to enforce flex constraints
- Add minHeight: 0 to ThreadDrawerContent for proper flex shrinking
- Constrain root message to 40vh max height with scroll
- Move reply count label inside scroll container to prevent overlap
- Ensures thread content scrolls properly regardless of message count
@Just-Insane Just-Insane marked this pull request as ready for review March 13, 2026 16:09
Just-Insane pushed a commit to Just-Insane/Sable that referenced this pull request Mar 13, 2026
# Conflicts:
#	src/app/features/room/Room.tsx
#	src/app/features/room/RoomInput.tsx
Evie Gauthier added 12 commits March 13, 2026 14:34
- Remove unused imports (getEditedEvent, MessageLayout, MessageEvent, customHtmlCss)
- Fix import formatting to be multi-line
- Fix import order
- Move React hooks before early return to fix rules-of-hooks errors
- Fix function signatures for factoryRenderLinkifyWithMention and renderMatrixMention
- Fix getReactCustomHtmlParser call to use correct parameters
Wrap message content in each thread preview with a Scroll container limited to 200px max height. This matches the pattern used in ThreadDrawer for displaying the root message and prevents very long messages from dominating the thread browser view.
Change max height from 40vh to 200px to give more vertical space to thread replies. Short root messages no longer take up excessive space.
Add RoomEvent.Relations listener to catch reaction additions/removals on thread messages. Previously reactions would only appear after closing and reopening the thread view.
- ThreadBrowser now filters out reactions/edits when counting replies
- RoomTimeline getThreadReplyCount no longer falls back to thread.length
- Both now use consistent logic: count thread.events excluding root, reactions, and edits
- Fixes issue where main room showed incorrect reply count (e.g., 3 instead of 2)
When thread.events is empty or only has reactions/edits but the server
knows there are replies (thread.length > 0), we need to show the thread
chip. The previous commit broke this by removing the fallback.
RoomEvent.Relations doesn't exist in matrix-js-sdk types. Reactions
are already handled by RoomEvent.Timeline and ThreadEvent.Update, so
the onRelations handler was redundant.
- Always count replies from live timeline instead of thread.events
- thread.events may not update when reactions are added/removed
- thread.length includes reactions/edits in server count
- Timeline-based count filters reactions/edits correctly
- Ensures counts update immediately when reactions are removed
- Fixes issue where chips showed stale counts after reactions changed
The root message itself should never be counted as a reply, even if
its threadRootId property is set to its own event ID in edge cases.
Adds explicit ev.getId() !== mEventId check to both getThreadReplyCount
and ThreadReplyChip component.
- Thread button now closes open thread drawer instead of doing nothing
  - Button checks if openThreadId is set and closes it
  - Otherwise, toggles thread browser as before
  - aria-pressed now shows state for both browser and open thread

- Add RoomEvent.Redaction listener to ThreadDrawer
  - Reactions are redaction events when removed
  - Without this, removing reactions required manual refresh
  - Now drawer re-renders immediately on reaction add/remove
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