Skip to content

Conversation

@ggazzo
Copy link
Member

@ggazzo ggazzo commented Oct 10, 2025

FDR-228

  • Introduced a new Reply type to manage different reply scenarios.
  • Added buildReplyContent method to construct reply content based on the new Reply type.
  • Updated sendMessage and sendFileMessage methods to support replies.
  • Deprecated sendReplyToMessage, sendThreadMessage, and sendReplyToInsideThreadMessage methods in favor of the new reply handling in sendMessage.

Summary by CodeRabbit

  • New Features
    • Reply to messages and files directly, with proper threading and in-reply-to behavior.
  • Improvements
    • Consistent reply metadata across all message types for more reliable threads and references.
    • Thread messages now require the latest thread reference, improving continuity and context.
    • Unified message-sending flow for replies to reduce inconsistencies.
  • Documentation
    • Added deprecation notes pointing to the new unified reply approach and recommended alternatives.

…ethods

- Introduced a new Reply type to manage different reply scenarios.
- Added buildReplyContent method to construct reply content based on the new Reply type.
- Updated sendMessage and sendFileMessage methods to support replies.
- Deprecated sendReplyToMessage, sendThreadMessage, and sendReplyToInsideThreadMessage methods in favor of the new reply handling in sendMessage.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

The MessageService adds a Reply type and a buildReplyContent helper. sendMessage and sendFileMessage accept an optional reply and merge the generated relates_to content. sendThreadMessage now requires latestThreadEventId and includes both thread identifiers. Reply-oriented wrappers delegate to sendMessage. Deprecation notes were added.

Changes

Cohort / File(s) Summary
Reply plumbing and helpers
packages/federation-sdk/src/services/message.service.ts
Introduced Reply type handling; added private buildReplyContent(reply) to produce Matrix m.relates_to payloads for replies/threads.
Message send APIs updated
packages/federation-sdk/src/services/message.service.ts
sendMessage and sendFileMessage accept optional reply and merge reply content into message/file payloads.
Thread message adjustments
packages/federation-sdk/src/services/message.service.ts
sendThreadMessage now requires latestThreadEventId; includes threadEventId and latestThreadEventId in content.
Wrapper delegation changes
packages/federation-sdk/src/services/message.service.ts
sendReplyToMessage and sendReplyToInsideThreadMessage now delegate to sendMessage using Reply structure instead of inlining relates_to.
Deprecation notes
packages/federation-sdk/src/services/message.service.ts
Added deprecation documentation on select methods indicating preferred alternatives.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant MS as MessageService
  participant H as Homeserver API

  rect rgb(245,248,255)
  note over Client,MS: Send text or file (optionally as reply/thread)
  Client->>MS: sendMessage/sendFileMessage(..., reply?)
  alt reply provided
    MS->>MS: buildReplyContent(reply)
    note right of MS: Merge m.relates_to / m.in_reply_to<br/>and/or m.thread data
  end
  MS->>H: POST /send (content with relates_to)
  H-->>MS: EventID
  MS-->>Client: Result
  end
Loading
sequenceDiagram
  autonumber
  actor Client
  participant MS as MessageService
  participant H as Homeserver API

  rect rgb(245,255,245)
  note over Client,MS: Thread message
  Client->>MS: sendThreadMessage(..., threadRootEventId, latestThreadEventId)
  MS->>MS: Build content with threadEventId + latestThreadEventId
  MS->>H: POST /send (threaded content)
  H-->>MS: EventID
  MS-->>Client: Result
  end
Loading
sequenceDiagram
  autonumber
  actor Client
  participant MS as MessageService

  note over Client,MS: Wrapper delegation
  Client->>MS: sendReplyToMessage(...)
  MS->>MS: sendMessage(..., reply={ replyToEventId })
  Client->>MS: sendReplyToInsideThreadMessage(...)
  MS->>MS: sendMessage(..., reply={ threadEventId, latestThreadEventId, replyToEventId })
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A nibble of threads, a hop of reply,
I stitch little links as messages fly.
With latest and root, I map every trail,
So files don’t wander the main room’s vale.
Thump-thump! says the queue, “All nested, all right!”
A rabbit reviews—then bounds out of sight. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary changes by noting both the addition of reply handling enhancements and the deprecation of legacy methods in the message service, matching the core modifications in the PR.
Linked Issues Check ✅ Passed The changes implement FDR-228 by introducing reply context metadata through buildReplyContent and integrating it into sendFileMessage and sendThreadMessage paths, ensuring file uploads inside threads preserve thread nesting for federated homeservers.
Out of Scope Changes Check ✅ Passed All modifications are confined to the message service and directly relate to reply handling and deprecations specified in FDR-228 without touching unrelated modules or introducing unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/message-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.31%. Comparing base (8bdd4f8) to head (f390299).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #273   +/-   ##
=======================================
  Coverage   60.31%   60.31%           
=======================================
  Files          67       67           
  Lines        6672     6672           
=======================================
  Hits         4024     4024           
  Misses       2648     2648           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ggazzo ggazzo changed the title feat: enhance message service with reply handling and deprecate old m… chore: enhance message service with reply handling and deprecate old m… Oct 10, 2025
@rodrigok rodrigok changed the title chore: enhance message service with reply handling and deprecate old m… chore: enhance message service with reply handling and deprecate old methods Oct 10, 2025
@ggazzo ggazzo marked this pull request as ready for review October 10, 2025 22:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8bdd4f8 and f390299.

📒 Files selected for processing (1)
  • packages/federation-sdk/src/services/message.service.ts (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/federation-sdk/src/services/message.service.ts (2)
packages/room/src/types/_common.ts (3)
  • EventID (8-8)
  • RoomID (16-16)
  • UserID (20-20)
packages/core/src/events/m.room.message.ts (1)
  • FileMessageContent (28-45)

Comment on lines +79 to +99
...(!reply.showInMainChat && { rel_type: 'm.thread' as const }),
is_falling_back: false,
event_id: reply.threadEventId,
'm.in_reply_to': { event_id: reply.replyToEventId },
},
} as const;
}

if (
'threadEventId' in reply &&
reply?.threadEventId &&
'latestThreadEventId' in reply &&
reply?.latestThreadEventId
) {
return {
'm.relates_to': {
...(!reply.showInMainChat && { rel_type: 'm.thread' as const }),
event_id: reply.threadEventId,
is_falling_back: true,
'm.in_reply_to': { event_id: reply.latestThreadEventId },
},
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Always emit rel_type: 'm.thread' for thread replies

When reply.showInMainChat is true we currently drop rel_type, leaving only is_falling_back/event_id. Matrix clients (and remote homeservers) then treat the event as a plain reply, so thread context is lost—precisely the regression we are fixing for FDR-228. The spec requires rel_type: "m.thread" regardless of fallback, even when the event should appear in the main timeline.

Please keep rel_type for all thread variants (and let showInMainChat drive only is_falling_back). A minimal fix:

return {
  'm.relates_to': {
-    ...(!reply.showInMainChat && { rel_type: 'm.thread' as const }),
-    is_falling_back: false,
+    rel_type: 'm.thread' as const,
+    is_falling_back: reply.showInMainChat ?? false,
     event_id: reply.threadEventId,
     'm.in_reply_to': { event_id: reply.replyToEventId },
  },
} as const;

and similarly ensure the second branch keeps rel_type: 'm.thread' while defaulting is_falling_back to reply.showInMainChat ?? true. This preserves thread metadata for both local and federated consumers.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
...(!reply.showInMainChat && { rel_type: 'm.thread' as const }),
is_falling_back: false,
event_id: reply.threadEventId,
'm.in_reply_to': { event_id: reply.replyToEventId },
},
} as const;
}
if (
'threadEventId' in reply &&
reply?.threadEventId &&
'latestThreadEventId' in reply &&
reply?.latestThreadEventId
) {
return {
'm.relates_to': {
...(!reply.showInMainChat && { rel_type: 'm.thread' as const }),
event_id: reply.threadEventId,
is_falling_back: true,
'm.in_reply_to': { event_id: reply.latestThreadEventId },
},
return {
'm.relates_to': {
rel_type: 'm.thread' as const,
is_falling_back: reply.showInMainChat ?? false,
event_id: reply.threadEventId,
'm.in_reply_to': { event_id: reply.replyToEventId },
},
} as const;
🤖 Prompt for AI Agents
In packages/federation-sdk/src/services/message.service.ts around lines 79 to
99, thread replies currently omit rel_type when reply.showInMainChat is true
which causes events to be treated as plain replies; always include rel_type:
'm.thread' in both branches and make is_falling_back driven only by
showInMainChat (e.g., is_falling_back: !!reply.showInMainChat or the inverse
depending on existing logic), and in the second branch ensure is_falling_back
defaults to reply.showInMainChat ?? true while keeping rel_type present so both
local and federated consumers retain thread metadata.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, its a bit different that I read here:

A rich reply without rel_type: m.thread targeting a thread relation must be rendered in the main timeline. This will allow users to advertise threaded messages in the room.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🌐 Web query:

Matrix threading specification rel_type m.thread showInMainChat behavior

💡 Result:

Short answer: rel_type "m.thread" marks an event as belonging to a thread (event_id = thread root). Whether a threaded event is also shown in the room’s main timeline is controlled by the reply/fallback fields — not a separate "showInMainChat" flag. Concretely:

  • An event with "m.relates_to": { "rel_type":"m.thread", "event_id": "$root" } is a thread event and clients that support threads should place it in the thread timeline (not the main timeline). [1][2]
  • To support older/unsupporting clients, threaded events SHOULD include an m.in_reply_to and set is_falling_back = true; clients that don’t understand threads then render the fallback reply in the main timeline so the message is discoverable. If is_falling_back = false (or omitted and treated as false) the in-reply-to is a real reply and clients should show the actual replied-to event in context. [1]
  • Historically, thread-capable clients separate the thread UI (sidebar, separate timeline, thread card, etc.). Thread events are therefore scoped to thread timelines (and read receipts/notification behaviour may be thread-scoped). [1][3]

References:

  1. MSC3440 — “Threading via m.thread” (spec text on rel_type, m.in_reply_to fallback and is_falling_back). [GitHub matrix-spec-proposals/MSC3440]. [1]
  2. Matrix blog / Synapse notes and ecosystem commentary about threads and server/client support. [2]
  3. Client/implementation notes (e.g. Fractal/Element descriptions) showing thread UI is separate from main timeline. [3]

If you want, I can fetch the exact MSC excerpt and show the JSON examples (with is_falling_back cases).


@ggazzo, you're absolutely right, and I apologize for the incorrect suggestion!

The Matrix spec is clear: omitting rel_type: "m.thread" while still referencing the thread root is the correct way to make a message appear in the main timeline. The current implementation is correct:

  • showInMainChat: true → omits rel_type → message appears in main timeline (as a rich reply to the thread root)
  • showInMainChat: false → includes rel_type: "m.thread" → message appears only in thread timeline

For FDR-228 (the file upload issue), the fix should ensure that when files are uploaded inside a thread, they're sent with rel_type: "m.thread" so they appear in the thread timeline on remote homeservers, not the main room timeline. The current code correctly handles this when showInMainChat is false or not specified.

Thank you for catching this!


✏️ Learnings added
Learnt from: ggazzo
PR: RocketChat/homeserver#273
File: packages/federation-sdk/src/services/message.service.ts:79-99
Timestamp: 2025-10-10T22:11:15.001Z
Learning: In Matrix threading: omitting `rel_type: "m.thread"` while targeting a thread root (via `m.in_reply_to` or `event_id`) makes the message appear in the main timeline. Including `rel_type: "m.thread"` confines the message to the thread timeline only. The `showInMainChat` flag in the codebase controls this behavior.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ggazzo, thank you for your patience and for the clarification! 🙏 If you need anything else, feel free to reach out.


🧠 Learnings used
Learnt from: ggazzo
PR: RocketChat/homeserver#273
File: packages/federation-sdk/src/services/message.service.ts:79-99
Timestamp: 2025-10-10T22:11:15.001Z
Learning: In Matrix threading: omitting `rel_type: "m.thread"` while targeting a thread root (via `m.in_reply_to` or `event_id`) makes the message appear in the main timeline. Including `rel_type: "m.thread"` confines the message to the thread timeline only. The `showInMainChat` flag in the codebase controls this behavior.

@ggazzo ggazzo merged commit 1e3b629 into main Oct 10, 2025
3 checks passed
@ggazzo ggazzo deleted the fix/message-handling branch October 10, 2025 22:51
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.

4 participants