|
| 1 | +# MSC3773: Notifications for threads |
| 2 | + |
| 3 | +Since the unread notification count does not consider threads, a client is unable |
| 4 | +to separate the unread message counts into threads (as defined by |
| 5 | +[MSC3440](https://github.com/matrix-org/matrix-doc/pull/3440)) |
| 6 | +without iterating over every missing message. Without this, clients are unable to: |
| 7 | + |
| 8 | +* Let users know that a thread has new messages since they last read it. |
| 9 | +* Accurately display a count of unread messages in a room (or a thread). |
| 10 | + |
| 11 | +## Proposal |
| 12 | + |
| 13 | +### Modification to push rule processing |
| 14 | + |
| 15 | +When an event which is part of a thread matches a push rule which results in a |
| 16 | +`notify` action then the homeserver should partition the resulting notification |
| 17 | +count per-thread. (This is needed for the |
| 18 | +[proposed `/sync` changes](#unread-thread-notifications-in-the-sync-response)). |
| 19 | + |
| 20 | +It is recommended that at least 3 relations are traversed when attempting to |
| 21 | +find a thread, implementations should be careful to not infinitely recurse.[^1] |
| 22 | + |
| 23 | +Similar behavior should be applied for an event which results in `notify` action |
| 24 | +with a `highlight` tweak set. |
| 25 | + |
| 26 | +This MSC does not propose any changes to the payload sent to push gateways. |
| 27 | + |
| 28 | +### Unread thread notifications in the sync response |
| 29 | + |
| 30 | +Threaded clients can opt into receiving unread thread notifications by passing |
| 31 | +a new `unread_thread_notifications` parameter |
| 32 | +[as part of the `RoomEventFilter`](https://spec.matrix.org/v1.2/client-server-api/#filtering). |
| 33 | +(This is [similar to `lazy_load_members`](https://spec.matrix.org/v1.2/client-server-api/#lazy-loading-room-members), |
| 34 | +but only applies to the `/sync` endpoint.): |
| 35 | + |
| 36 | +* `unread_thread_notifications`: If `true`, enables partitioning of unread notification |
| 37 | + counts by thread. Defaults to false. |
| 38 | + |
| 39 | +If this flag is set to `true`, for each ["Joined Room" in the `/sync` response](https://spec.matrix.org/v1.3/client-server-api/#get_matrixclientv3sync) |
| 40 | +a new field is added: |
| 41 | + |
| 42 | +* `unread_thread_notifications`: Counts of unread thread notifications for this |
| 43 | + room, an object which maps thread ID (the parent event ID) to |
| 44 | + `Unread Notification Counts`. |
| 45 | + |
| 46 | +Additionally, the `unread_notifications` dictionary is modified to only include |
| 47 | +unread notifications from events which are not part of a thread. |
| 48 | + |
| 49 | +An example of a joined room from a sync response: |
| 50 | + |
| 51 | +```json5 |
| 52 | +{ |
| 53 | + "account_data": { |
| 54 | + // ... |
| 55 | + }, |
| 56 | + "ephemeral": { |
| 57 | + // ... |
| 58 | + }, |
| 59 | + "state": { |
| 60 | + // ... |
| 61 | + }, |
| 62 | + "summary": { |
| 63 | + // ... |
| 64 | + }, |
| 65 | + "timeline": { |
| 66 | + "events": [ |
| 67 | + { |
| 68 | + "event_id": "$143273582443PhrSn:example.org", |
| 69 | + // other fields ... |
| 70 | + }, |
| 71 | + { |
| 72 | + "event_id": "$SGNxGPGUopcPBUoTTL:example.org", |
| 73 | + "m.relates_to": { |
| 74 | + "event_id": "$143273582443PhrSn:example.org", |
| 75 | + "rel_type": "m.thread" |
| 76 | + } |
| 77 | + // other fields ... |
| 78 | + } |
| 79 | + ] |
| 80 | + }, |
| 81 | + "unread_notifications": { |
| 82 | + "highlight_count": 2, |
| 83 | + "notification_count": 18 |
| 84 | + }, |
| 85 | + "unread_thread_notifications": { |
| 86 | + "$143273582443PhrSn:example.org": { |
| 87 | + "highlight_count": 0, |
| 88 | + "notification_count": 1 |
| 89 | + } |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +## Potential issues |
| 95 | + |
| 96 | +### Scalability |
| 97 | + |
| 98 | +Rooms with many unread threads could cause some downsides: |
| 99 | + |
| 100 | +* The size of the `/sync` response would increase without bound. |
| 101 | +* The effort to generate and process the receipts for each room would increase |
| 102 | + without bound. |
| 103 | + |
| 104 | +This is not dissimilar to rooms which are never read, however, as their unread |
| 105 | +counts are continually tracked and returned as part of the `/sync` response. |
| 106 | + |
| 107 | +### Clearing unread notifications |
| 108 | + |
| 109 | +This MSC does not attempt to modify how unread notifications (for a thread or |
| 110 | +otherwise) are cleared. It currently assumes the rules set forth by |
| 111 | +[read receipts](https://spec.matrix.org/v1.3/client-server-api/#receiving-notifications) |
| 112 | +still apply. This will cause some flakiness with unread notifications, as the current |
| 113 | +receipt infrastructure assumes that a room's timeline is linear, which is no |
| 114 | +longer true. |
| 115 | + |
| 116 | +[MSC3771](https://github.com/matrix-org/matrix-spec-proposals/pull/3771) is a |
| 117 | +potential solution for this. |
| 118 | + |
| 119 | +## Alternatives |
| 120 | + |
| 121 | +### Using push rules |
| 122 | + |
| 123 | +It might seem that a new push rule `action` (or `tweak`) should be used to control |
| 124 | +the behavior of whether an event generates a notification for a thread or the |
| 125 | +room itself. There are issues with either approach though: |
| 126 | + |
| 127 | +A new `action` (e.g. `notify_thread`) would mean that additional logic would |
| 128 | +need to be defined and added for events which aren't part of a thread but attempt |
| 129 | +to use this action. It also conflicts with [MSC3768](https://github.com/matrix-org/matrix-spec-proposals/pull/3768), |
| 130 | +which attempts to define another `action` which should also work fine for threads. |
| 131 | + |
| 132 | +A new `tweak` (e.g. `threaded`) was discarded as an option since there is no need to |
| 133 | +pass this through to the push server, which is at odds with the current `tweaks` |
| 134 | +mechanism. |
| 135 | + |
| 136 | +Regardless, the main issue with using push rules is that it becomes necessary to |
| 137 | +define rules which match threaded events. Whenever adding a new rule, matching rules |
| 138 | +would need to be added, but as a thread-specific version. |
| 139 | + |
| 140 | +## Security considerations |
| 141 | + |
| 142 | +N/A |
| 143 | + |
| 144 | +## Unstable prefix |
| 145 | + |
| 146 | +While this feature is in development the following unstable prefixes should be used: |
| 147 | + |
| 148 | +* `unread_thread_notifications` --> `org.matrix.msc3773.unread_thread_notifications` |
| 149 | + |
| 150 | +To detect server support, clients can either rely on the spec version (when stable) |
| 151 | +or the presence of a `org.matrix.msc3773` flag in `unstable_features` on `/versions`. |
| 152 | + |
| 153 | +## Dependencies |
| 154 | + |
| 155 | +N/A |
| 156 | + |
| 157 | +[^1]: Three relations is relatively arbitrary, but is meant to cover an edit or |
| 158 | +reaction to a thread (to an event with no relations, i.e. the root of a thread): |
| 159 | +`A<--[m.thread]--B<--[m.annotation]--C`. |
| 160 | +With an additional leftover for future improvements. This is considered reasonable |
| 161 | +since threads cannot fork, edits cannot modify relation information, and generally |
| 162 | +annotations to annotations are ignored by user interfaces. |
0 commit comments