From 5dbb5b015c2aa3ac142bb77a86ac2b6a4e749444 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Sun, 10 Aug 2025 12:40:29 +0100 Subject: [PATCH] fix(relations): Fix duplicate messages in thread pagination The /relations endpoint was incorrectly modifying the pagination starting point with from.saturating_inc(dir), causing duplicate events to appear when scrolling up in message threads. This was particularly noticeable when viewing replies in Element. The fix removes the incorrect increment/decrement logic and adds proper filtering to exclude the starting event itself from results. The endpoint now uses ShortEventId directly as the pagination token, avoiding issues with PduCount in federated environments where events can arrive out of order. Fixes: #814 --- src/api/client/relations.rs | 14 ++++++-------- src/service/rooms/pdu_metadata/data.rs | 14 +++++++++++++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/api/client/relations.rs b/src/api/client/relations.rs index 1aa34ada..1376609f 100644 --- a/src/api/client/relations.rs +++ b/src/api/client/relations.rs @@ -109,13 +109,13 @@ async fn paginate_relations_with_filter( recurse: bool, dir: Direction, ) -> Result { - let start: PduCount = from - .map(str::parse) - .transpose()? - .unwrap_or_else(|| match dir { + let start: PduCount = from.and_then(|s| s.parse::().ok()).map_or_else( + || match dir { | Direction::Forward => PduCount::min(), | Direction::Backward => PduCount::max(), - }); + }, + PduCount::Normal, + ); let to: Option = to.map(str::parse).flat_ok(); @@ -156,9 +156,7 @@ async fn paginate_relations_with_filter( | Direction::Forward => events.last(), | Direction::Backward => events.first(), } - .map(at!(0)) - .as_ref() - .map(ToString::to_string); + .map(|(count, _)| count.into_unsigned().to_string()); Ok(get_relating_events::v1::Response { next_batch, diff --git a/src/service/rooms/pdu_metadata/data.rs b/src/service/rooms/pdu_metadata/data.rs index c1376cb0..f9cc80a0 100644 --- a/src/service/rooms/pdu_metadata/data.rs +++ b/src/service/rooms/pdu_metadata/data.rs @@ -61,9 +61,10 @@ impl Data { from: PduCount, dir: Direction, ) -> impl Stream + Send + '_ { + let from_unsigned = from.into_unsigned(); let mut current = ArrayVec::::new(); current.extend(target.to_be_bytes()); - current.extend(from.saturating_inc(dir).into_unsigned().to_be_bytes()); + current.extend(from_unsigned.to_be_bytes()); let current = current.as_slice(); match dir { | Direction::Forward => self.tofrom_relation.raw_keys_from(current).boxed(), @@ -73,6 +74,17 @@ impl Data { .ready_take_while(move |key| key.starts_with(&target.to_be_bytes())) .map(|to_from| u64_from_u8(&to_from[8..16])) .map(PduCount::from_unsigned) + .ready_filter(move |count| { + if from == PduCount::min() || from == PduCount::max() { + true + } else { + let count_unsigned = count.into_unsigned(); + match dir { + | Direction::Forward => count_unsigned > from_unsigned, + | Direction::Backward => count_unsigned < from_unsigned, + } + } + }) .wide_filter_map(move |shorteventid| async move { let pdu_id: RawPduId = PduId { shortroomid, shorteventid }.into();