Skip to content

[meta] Threads #4869

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

Open
2 of 19 tasks
bnjbvr opened this issue Mar 31, 2025 · 0 comments
Open
2 of 19 tasks

[meta] Threads #4869

bnjbvr opened this issue Mar 31, 2025 · 0 comments
Assignees

Comments

@bnjbvr
Copy link
Member

bnjbvr commented Mar 31, 2025

The SDK is ripe for supporting threads, which allows parallel topical discussions and reduce confusion. And, it will make it possible to add support for it in embeddings, notably in ElementX, closing the gap with the Element app's feature set.

We'll update this issue as soon as we'll identify the next steps.
Here is the high-level overview of what we're planning to implement:

Interacting with threads

Thread-focused timeline

A new timeline focus will be introduced that will represent a thread. Threads will include message-like events, and will be updated in real-time.

Thread data model

Make it possible to save threads events in the event cache (even if they haven't been received from sync). We also want to persist and restore a thread's summary to/from persisted storage.

Some developments are happening here.

Core types

  • a thread may have a ThreadSummary:
    • count (likely not the raw event count, but renderable event count)
    • latest event id (observers can use the load_or_fetch_event() method to retrieve it from the cache when needed)
    • participated: TBD
    • maybe_outdated: bool indicating whether the summary might be outdated, after a gappy sync
  • for each thread (identified by room id + root event id), a ThreadEventCache, which contains a linked chunk + the thread summary.
  • for each room, a RoomThreadCache, which contains a mapping of all the threads roots to their ThreadEventCache.
  • the RoomEventCache contains exactly one RoomThreadCache. Separating them helps separating concerns + testing threads in isolation.

Tasks

  • Reconcile or unify RoomEvents event chunks with AllEventsCache events #3886
  • event cache: update storage to store the events' content in a separate table #4841
  • Extract the bundled aggregation from a thread root, and feed it into the RoomEventCache, which forwards it to the ThreadEventCache ONLY if the thread was not known beforehand
    • Save the bundled thread's latest event into the event cache (don't insert into the thread linked chunk, only save it into the events table with save_event())
  • Have a linked chunk per thread.
    • RoomEventCache gets a mapping of thread root id to ThreadEventCache { events: LinkedChunk<...>, } (consider using a specialized gap of ThreadGap { prev_token: Option<String>, next_token: Option<String> }).
    • Sync appends new events into the thread linked chunk in real-time.
    • Room back-pagination does NOT propagate events into the thread linked chunk. Otherwise, as soon as we have a gap in the thread, we don't know where the room-backpaginated events should go in the thread.
    • clear all the ThreadEventCaches upon gappy sync. Indeed we don't know if we missed events in that specific threads, so we don't really have a better choice at the moment (until we get an SSS extension with the list of threads that had updates)
  • Allow subscribing to a thread by receiving the initial events in that thread, if known, and a stream of VectorDiff<Event> (this will power the thread-focused timeline).
  • implement back-pagination at the ThreadEventCache level; this will be used by the thread-focused timeline paginate_backwards() method.

Thread persistent storage

  • Generalize the SQLite backend to hold thread linked chunk. It might be sufficient that a linked chunk's key isn't a room anymore, but a generalized enum LinkedChunkKey { Room(OwnedRoomId), Thread(OwnedRoomid, OwnedEventId) }. Migration = clear all the chunks?
  • Add a table in storage to store thread summaries separately from the root event; or instead of storing TimelineEvent into storage, store an augmented data structure that also includes a ThreadSummary (⚠ this second alternative would require a migration).
    • Add a method in the event cache store to upsert_thread_summary
    • Keep participated in clear, so that we can query for all the threads we've participated to.
  • For each event that had more than one edit (>1 or >=1 TBD), introduce an edit linked list, to order them relative to each other, and figure out which is the actual latest.
  • What to do after a gappy sync?
    • Thread summary
      • Drop them all upon gappy sync (+ notify)
      • Refresh all with /context (with 0 events before/after)
      • ❓ What about events which were not thread roots, but may be now?
    • Thread linked chunk
      • In a non-persisted world, we can clear the linked chunk of a gappy thread immediately.
      • Upon gappy sync, do a /threads query to retrieve all the threads and their latest event; if it matches what we knew from the thread linked chunk, mark it as NOT maybe_outdated. If not, start back-pagination until all the thread events are known.
        • This has been deemed impractical, because /threads can be super slow for large rooms like MatrixHQ. Instead, we could, for each known thread, run a single /relations query from the back, and keep on back-paginating until we know all the events; or let the embedder manually request to back-paginate, like we do for live main timelines.
        • Likely recompute the thread summary afterwards 👀 (Think about conflicts with sync.)

Read receipts

We want to support thread read receipts. This will not interact with unread status on rooms, yet.

stefanceriu added a commit that referenced this issue May 14, 2025
…loader (#5032)

… that allows building a timeline instance specific to a particular
thread root.

Creating a timeline in this mode will start by backpaginating root event
relations with `num_events` and automatically insert the thread root
event when reaching the end. It will include
`RelationsOfType(RelationType::Thread)` but also recurse over the
retrieved events to fetch reactions.
It will not however react to new events received over sync or that the
user sends (for now).

This patch will also help incrementally deliver the upstream client
support for creating such a timeline.

Part of #4833 (meta #4869).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants