atagen

macltok

public
Star0
Watch0

Commit 93d4735d70cf

tarball

atagen <boss@atagen.co>2026-10-26 01:37 UTC

2 files changed+80-1
mentions: keep a captured profile when observing an event again

Each sync in a room observes its saved entries again from raw, and a fresh normalisation carries no profile, so it replaced the captured sender, reset the capture to Pending and fetched the context once more. In a busy room the ping spent most of its life without a profile, and the neighbours lost theirs on the re-observe right after the capture.
Modified file: src/mentions/service.rs+5 additions−1 deletions
@@ -914,6 +914,9 @@ impl Service {
914914 };
915915 resolve_replies(std::iter::once(&mut **event), &targets);
916916 }
917+ if let Some(saved) = data.events.get(&identity) {
918+ improve(&mut item, &saved.item);
919+ }
917920 self.supplement(data, &key, &item, &media, &value);
918921 let (reasons, suppressed, unresolved) = self.classify(data, room_id, &value).await;
919922 if unresolved {
@@ -1680,7 +1683,8 @@ fn retry_at(error: &Error, fallback_ms: u64) -> u64 {
16801683 }
16811684
16821685 /// the capture fetched what observing at sync time couldn't, so its profile and reply win
1683-/// over the saved ones, while the saved content keeps its own edits and redactions
1686+/// over the saved ones, while the saved content keeps its own edits and redactions. observing
1687+/// again calls it the other way round, or every sync in the room wipes the profile
16841688 fn improve(saved: &mut TimelineItem, captured: &TimelineItem) {
16851689 let (TimelineItem::Event(saved), TimelineItem::Event(captured)) = (saved, captured) else {
16861690 return;
Modified file: src/mentions/service_tests.rs+75 additions
@@ -696,3 +696,78 @@ async fn explicit_recovery_rechecks_server_support() {
696696 assert!(service.data.coverage.recovering);
697697 assert_eq!(service.recovery_budget, 20);
698698 }
699+
700+#[tokio::test]
701+async fn observing_again_keeps_the_profiles_a_capture_fetched() {
702+ let server = MatrixMockServer::new().await;
703+ let room_id = room_id!("!mentions:localhost");
704+ let (client, own_user) = joined_client(&server, room_id).await;
705+ let raw = |event: matrix_sdk::ruma::serde::Raw<_>, timestamp: u64| {
706+ let mut value: serde_json::Value = serde_json::from_str(event.json().get()).unwrap();
707+ value["origin_server_ts"] = json!(timestamp);
708+ value
709+ };
710+ let factory = EventFactory::new().room(room_id).sender(user_id!("@bob:localhost"));
711+ let mention = raw(
712+ factory
713+ .text_msg("ping")
714+ .mentions(Mentions::with_user_ids([own_user]))
715+ .event_id(event_id!("$ping:localhost"))
716+ .into_raw_sync(),
717+ 10,
718+ );
719+ let following = raw(
720+ factory
721+ .text_msg("following")
722+ .event_id(event_id!("$following:localhost"))
723+ .into_raw_sync(),
724+ 11,
725+ );
726+ let service = service(&client, None).await;
727+ let room_id = room_id.to_owned();
728+ let mut data = Data::default();
729+ service.observe(&mut data, &room_id, mention.clone()).await;
730+ let ping = format!("{room_id}\n$ping:localhost");
731+ let following_id = format!("{room_id}\n$following:localhost");
732+
733+ let profiled = |item: &TimelineItem| {
734+ let mut item = item.clone();
735+ if let TimelineItem::Event(event) = &mut item {
736+ event.sender.display_name = Some("Bob".to_owned());
737+ }
738+ item
739+ };
740+ let target = profiled(&data.entries[&ping].event);
741+ let mut neighbour = normalise_observed(&following, None, &following_id).unwrap().0;
742+ neighbour = profiled(&neighbour);
743+ let revision = data.entries[&ping].revision;
744+ service.captured(
745+ &mut data,
746+ ping.clone(),
747+ revision,
748+ Ok((
749+ crate::EventContext {
750+ events_before: Vec::new(),
751+ event: target,
752+ events_after: vec![neighbour],
753+ start_token: None,
754+ end_token: None,
755+ },
756+ Vec::new(),
757+ Vec::new(),
758+ )),
759+ );
760+ let capture = data.entries[&ping].capture.clone();
761+
762+ service.observe(&mut data, &room_id, following).await;
763+ service.observe(&mut data, &room_id, mention).await;
764+
765+ let name = |item: &TimelineItem| match item {
766+ TimelineItem::Event(event) => event.sender.display_name.clone(),
767+ _ => None,
768+ };
769+ assert_eq!(name(&data.entries[&ping].event).as_deref(), Some("Bob"));
770+ assert_eq!(name(&data.events[&ping].item).as_deref(), Some("Bob"));
771+ assert_eq!(name(&data.events[&following_id].item).as_deref(), Some("Bob"));
772+ assert_eq!(data.entries[&ping].capture, capture);
773+}