-
Notifications
You must be signed in to change notification settings - Fork 399
MSC3440: Threading via m.thread
relation
#3440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 35 commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
beda89e
Threading via relation
germain-gg 2fa27ac
Add explainer on how to handle m.in_reply_to
germain-gg d8a0a94
Clarify wording on threading MSC
germain-gg 7d39887
Mention MSC3051 in the alternative section of MSC3440
germain-gg 6e37911
Clarify updates to MSC2675 for MSC3440
germain-gg c142b17
Line wrap the MSC
germain-gg c578f75
More line wrapping for MSC3440
germain-gg 33acdf4
Clarify single-layer event aggregation section
germain-gg 7102165
Update thread-as-rooms advantages
germain-gg f84f949
Clarify backwards compatibility and incremental support
germain-gg f02dc8d
Clarify wording and correct typos
germain-gg 3e46728
Splitting Cerulean and MSC2836 in alternatives section
germain-gg 44e967f
Add dependencies for threads MSC
germain-gg 65d0d55
Clarify intro to threads as rooms
germain-gg 2b76a6e
Add currentUserParticipated flag
germain-gg 99c5b2e
snake_case over camelCase
germain-gg 4ee42b1
Adding dependency to MSC3567
fc81bbd
Add threads capability
91e6ec7
Merge branch 'main' into gsouquet/threading-via-relations
germain-gg eaeef00
Fix typo
germain-gg 26fb5f2
Update syntax highlighting to use jsonc
germain-gg a23c795
Add limitations when fetching thread content by relation type
germain-gg 6b1a368
Add reply chain fallback via m.in_reply_to
germain-gg f227592
Clarity in wording and fix typo
b493f21
Cosmetic changes based on pull request feedback
germain-gg 46e1e9b
Add note to allow clients to omit fallback for rich replies
germain-gg e40efa0
fix typo
germain-gg 23928e7
Clarify wording to not confuse thread answers with quote-replies
germain-gg 0880a86
move relations justification to alternatives section
germain-gg 1bbb021
Clarify handling of m.in_reply_to missing rel_type:m.thread
germain-gg 3c977f7
Fix typo
germain-gg 0140454
Fix typo
germain-gg 700464c
Declare MSC2781 as a dependency
germain-gg 0035202
Use rich reply over quote reply
germain-gg e3cb699
Depend on MSC3676 rather than MSC2781
ara4n 847f468
Remove full stop typo
germain-gg c8ffa62
Clarify new filtering parameters.
clokep a7cbf8d
Fix typo.
clokep 5896d69
Update wording for client side considerations
germain-gg ee5df80
Add m.in_reply_to mixin to thread fallback
germain-gg 00daf64
Add guidance for clients and servers for thread invalid relations
germain-gg a5d8aab
update thread root wording
germain-gg d7ed3c4
Add better definition to reply target event
germain-gg 5c04906
Add note regarding forward compatibility
germain-gg d667a0b
link to MSC2674
richvdh b157dfd
Update proposals/3440-threading-via-relations.md
germain-gg 3162bea
Clarification on responsibilities for the reply fallback
germain-gg fa232f4
Update `/messages` API endpoint version on example
germain-gg 68d9c42
Apply wording suggestions from code review
germain-gg 5bbb015
Add notes on server-side invalid relation filtering
germain-gg 707af2b
Fix typo
germain-gg b28a365
reword paragraph about forwarding m.thread relation
germain-gg 8f82dfa
Add unstable prefix for capability endpoint
germain-gg 8f8be64
Re-order alternatives to match intro paragraph
germain-gg b6d8076
rework relation_senders and relation_types definition
germain-gg cd671ef
Apply wording suggestions from code review
germain-gg a61c01e
Clarify fallback mechanism
germain-gg 362e661
Rename filter property names
germain-gg b831fb3
Change m.render_in to m.display_reply_fallback
germain-gg e2dde8e
Clarify what endpoints support the new filter
germain-gg e640f6b
Switch from /capabilities to /versions
germain-gg bda3a1e
remove references to Cerulean
germain-gg 89c4b5e
Update latest_event description
germain-gg 61bb518
Clarity in wording and fix typo
germain-gg 9159a5a
rename m.display_reply_fallback to hide_reply
germain-gg a97307a
remove redundant paragraph about forward compat
germain-gg f541dab
Improve bundled relationship example
germain-gg 82b4c62
Explain context on why a thread-unaware client might want to send m.t…
germain-gg 75f4cb2
Clarify `hide_reply`
germain-gg 54ce185
Rename hide_reply to show_reply
germain-gg 6d6baa2
rename show_reply to is_falling_back
germain-gg 893cf1f
Add note about stable support.
clokep 641e326
Update proposals/3440-threading-via-relations.md
germain-gg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
# MSC3440 Threading via `m.thread` relation | ||
|
||
## Problem | ||
|
||
Threading allows users to branch out a new conversation from the main timeline of a room. | ||
to each other. This is particularly useful in high traffic rooms where multiple | ||
conversations can happen in parallel or when a single discussion might stretch | ||
over a very long period of time. | ||
|
||
The main goal of implementing threads is to facilitate conversations that are easier | ||
to follow and smoother to read. | ||
Threading is very clearly a core requirement for any modern messaging | ||
solution, and Matrix uptake is suffering due to the lack of progress. | ||
|
||
## Proposal | ||
|
||
### Event format | ||
|
||
A new relation type m.thread expresses that an event belongs to a thread. | ||
|
||
```json | ||
"m.relates_to": { | ||
"rel_type": "m.thread", | ||
"event_id": "$thread_root" | ||
} | ||
``` | ||
Where $thread_root is the event ID of the root message in the thread. | ||
|
||
When a thread head is aggregated (as in MSC2675), it returns a summary of the thread: | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
the latest message, a list of participants and the total count of messages. | ||
I.e. in places which include bundled relations (per | ||
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675)), the thread root | ||
would include additional information in the `unsigned` field: | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```jsonc | ||
{ | ||
"latest_event": { | ||
"content": { | ||
// ... | ||
}, | ||
// ... | ||
}, | ||
"count": 7, | ||
"current_user_participated": true | ||
} | ||
``` | ||
|
||
* `latest_event`: A reference to the last `m.thread` relation part of the thread | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* `count`: An integer counting the number of `m.thread` events | ||
* `current_user_participated`: A flag set to `true` if the current logged in user | ||
has participated in the thread | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### Rich replies in a thread | ||
|
||
Rich replies are still handled via the `m.in_reply_to` field of `m.relates_to`. | ||
However clients should fill in the new `render_in` field with `m.thread` in order | ||
to display that in a thread context. | ||
|
||
```json | ||
"m.relates_to": { | ||
"rel_type": "m.thread", | ||
"event_id": "$thread_root", | ||
"m.in_reply_to": { | ||
"event_id": "$event_target", | ||
"render_in": ["m.thread"] | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
``` | ||
|
||
It is possible that an `m.in_reply_to` event targets an event that is outside the | ||
related thread. Clients should always do their utmost to display the rich reply | ||
and when clicked, the event should be displayed and highlighted in its original context. | ||
|
||
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 | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
in the room. | ||
|
||
### Backwards compatibility | ||
|
||
A thread will be displayed as a chain of replies on clients unaware of threads. | ||
|
||
Thread-ready clients should attach a `m.in_reply_to` mixin to the event source. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
It should always reference the latest event in the thread unless a user is | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
explicitly replying to another event. | ||
The rich reply fallback should be hidden in a thread context unless it contains | ||
the new `render_in` field as described in the previous section. | ||
|
||
```jsonc | ||
"m.relates_to": { | ||
"rel_type": "m.thread", | ||
"event_id": "ev1", | ||
"m.in_reply_to": { | ||
"event_id": "last_event_id_in_thread", | ||
} | ||
} | ||
``` | ||
|
||
Historically replies have been limited to text messages due to the legacy fallback | ||
prepended to `formatted_body`. This MSC is dependant on | ||
[MSC3676](https://github.com/matrix-org/matrix-doc/pull/3676) which strips that | ||
requirement to unlock use of any event type in this context. | ||
|
||
### Fetch all relations to a thread root | ||
|
||
To fetch an entire thread, the `/relations` API can be used as defined in | ||
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675) | ||
|
||
``` | ||
GET /_matrix/client/unstable/rooms/!room_id:domain/relations/$thread_root/m.thread | ||
``` | ||
novocaine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Where `$thread_root` is the event ID of the root message in the thread. | ||
|
||
> Any API which receives events should bundle relations (apart from non-gappy | ||
incremental syncs), for instance: initial sync, gappy incremental sync, | ||
/messages and /context. | ||
|
||
### Fetch all threads in a room | ||
|
||
To fetch all threads in a room, use the | ||
[`/messages`](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-rooms-roomid-messages) | ||
API and expand the room event filtering to include relations. The `RoomEventFilter` | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* `relation_types`: A list of relation types which must exist pointing to the event | ||
being filtered. If this list is absent then no filtering is done on relation types. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* `relation_senders`: A list of senders of relations which must exist pointing to | ||
the event being filtered. If this list is absent then no filtering is done on relation types. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This can also be combined with the `sender` field to search for threads which a | ||
user has participated in (or not participated in). | ||
|
||
``` | ||
GET /_matrix/client/unstable/rooms/!room_id:domain/messages?filter=... | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
The filter string includes the new fields, above. In this example, the URL | ||
encoded JSON is presented unencoded and formatted for legibility: | ||
|
||
```jsonc | ||
{ | ||
"types": ["m.room.message"], | ||
"relation_senders": [ | ||
// ... | ||
], | ||
"relation_types": ["m.thread"] | ||
} | ||
``` | ||
|
||
|
||
|
||
|
||
### Server capabilities | ||
|
||
Threads might have sporadic support across servers, to simplify feature | ||
detections for clients, a homeserver must return a capability entry for threads. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```jsonc | ||
{ | ||
"capabilities": { | ||
// ... | ||
"m.thread": { | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"enabled": true | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Limitations | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### Read receipts | ||
|
||
Read receipts and read markers assume a single chronological timeline. Threading | ||
changes that assumption making the current API not very practical. | ||
|
||
Clients can synthesize read receipts but it is possible that some notifications get | ||
lost on a fresh start where the clients have to start off the `m.read` | ||
information received from the homeserver. | ||
|
||
Synchronising the synthesized notification count across devices is out of scope and deferred to a later MSC. | ||
|
||
#### Single-layer event aggration | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This MSC does not include support for nested threads. | ||
|
||
Nested threading is out of scope for this proposal and would be the subject of | ||
a different MSC. | ||
A `m.thread` event can only reference events that do not have a `rel_type` | ||
|
||
```jsonc | ||
[ | ||
{ | ||
"event_id": "ev1", | ||
// ... | ||
}, | ||
{ | ||
"event_id": "ev2", | ||
// ... | ||
"m.relates_to": { | ||
"rel_type": "m.thread", | ||
"event_id": "ev1", | ||
"m.in_reply_to": { | ||
"event_id": "ev1", | ||
"render_in": ["m.thread"], | ||
} | ||
} | ||
}, | ||
{ | ||
"event_id": "ev3", | ||
// ... | ||
"m.relates_to": { | ||
"rel_type": "m.annotation", | ||
"event_id": "ev1", | ||
"key": "✅" | ||
} | ||
} | ||
] | ||
``` | ||
|
||
Given the above list of events, only `ev1` would be a valid target for an `m.thread` | ||
relation event. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
|
||
#### | ||
### Client considerations | ||
|
||
#### Sending `m.thread` before fully implementing threads | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Clients that do not support threads yet should include a `m.thread` relation to the | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
event body if a user is replying to an event that has an `m.thread` relation type | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This is done so that clients that support threads can render the event in the most | ||
relevant context. | ||
|
||
If a client does not include that relation type to the outgoing event, it will be | ||
rendered in the room timeline with a rich reply that should open and highlight the | ||
event in the thread context when clicked. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
When replying to the following event, a client that does not support thread should | ||
copy in `rel_type` and `event_id` properties in their reply mixin. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```jsonc | ||
{ | ||
// ... | ||
"m.relates_to": { | ||
"rel_type": "m.thread", | ||
"event_id": "ev1" | ||
} | ||
} | ||
``` | ||
|
||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
## Alternatives | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[MSC2836](https://github.com/matrix-org/matrix-doc/pull/2836), "Threading as rooms", and | ||
building on `m.in_reply_to` are the main alternatives here. The first two are | ||
non-overlapping with this MSC. | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
It is also worth noting that relations in this MSC could be expressed using the | ||
scalable relation format described in [MSC3051](https://github.com/matrix-org/matrix-doc/pull/3051). | ||
|
||
### Threads as rooms | ||
|
||
Threads as rooms could provide full server-side APIs for navigating trees of events, | ||
and could be considered an extension of this MSC for scenarios which require that | ||
capability (e.g. Twitter-style microblogging as per | ||
[Cerulean](https://matrix.org/blog/2020/12/18/introducing-cerulean), or building | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
an NNTP or IMAP or Reddit style threaded UI) | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
"Threads as rooms" is the idea that each thread could just get its own Matrix room.. | ||
|
||
Advantages to "Threads as rooms" include: | ||
* May be simpler for client implementations | ||
* Restricting events visibility as the room creator | ||
* Ability to create read-only threads | ||
|
||
Disadvantages include: | ||
* Access control, membership, history visibility, room versions etc needs to be | ||
synced between the thread-room and the parent room | ||
* Harder to control lifetime of threads in the context of the parent room if | ||
they're completely split off | ||
* Clients which aren't aware of them are going to fill up with a lot of rooms. | ||
* Bridging to non-threaded chat systems is trickier as you may have to splice | ||
together rooms | ||
|
||
### Threads via serverside traversal of relationships MSC2836 | ||
|
||
Advantages include: | ||
* Fits other use cases than instant messaging | ||
* Simple possible API shape to implement threading in a useful way | ||
|
||
Disadvantages include: | ||
* Relationships are queried using `/event_relationships` which is outside the | ||
bounds of the `/sync` API so lacks the nice things /sync gives you (live updates). | ||
That being said, the event will come down `/sync`, you just may not have the | ||
context required to see parents/siblings/children. | ||
* Threads can be of arbitrary width (unlimited direct replies to a single message) | ||
and depth (unlimited chain of replies) which complicates UI design when you just | ||
want "simple" threading. | ||
* Does not consider use cases like editing or reactions | ||
|
||
### Threads via m.in_reply_to | ||
|
||
The rationale for using a new relation type instead of building on `m.in_reply_to` | ||
is to re-use the event relationship APIs provided by | ||
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675). The MSC3267 definition | ||
of `m.reference` relationships could be updated to mention threads (perhaps by | ||
using the key field from [MSC2677](https://github.com/matrix-org/matrix-doc/pull/2677) | ||
as the thread ID), but it is clearer to define a new relation type. It is unclear | ||
what impact this would have on [MSC3267](https://github.com/matrix-org/matrix-doc/pull/3267), | ||
but that is unimplemented by clients. | ||
|
||
A big advantage of relations over rich replies is that they can be server-side | ||
aggregated. It means that a client is not bound to download the entire history of | ||
a room to have a comprehensive list of events being part of a thread. | ||
|
||
## Security considerations | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
None | ||
|
||
## Unstable prefix | ||
|
||
Clients and servers should use list of unstable prefixes listed below while this | ||
MSC has not been included in a spec release. | ||
|
||
* `io.element.thread` should be used in place of `m.thread` as relation type | ||
* `io.element.relation_senders` should be used in place of `relation_senders` | ||
in the `RoomEventFilter` | ||
* `io.element.relation_types` should be used in place of `relation_types` | ||
in the `RoomEventFilter` | ||
germain-gg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Dependencies | ||
|
||
This MSC builds on [MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674), | ||
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675), | ||
[MSC3567](https://github.com/matrix-org/matrix-doc/pull/3567) and, | ||
[MSC3676](https://github.com/matrix-org/matrix-doc/pull/3676) (which at the | ||
time of writing have not yet been accepted into the spec). |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.