atagen

macltok

public
Star0
Watch0

Commit 0e081dee5e28

tarball

atagen <boss@atagen.co>2026-10-25 13:57 UTC

2 files changed+39-3
context: call the start when a backwards page reaches creation or comes back empty

Continuwuity keeps handing out an end token past m.room.create, so a context paged back to the start never said so. The sdk's own back pagination stops on a missing end token or a page of nothing new. Context stops on a missing token, an empty page, or a page holding m.room.create, so it ends where the room timeline does.
Modified file: src/engine/context.rs+35 additions−2 deletions
@@ -404,14 +404,24 @@ async fn fetch_side(
404404 if backwards {
405405 events.reverse();
406406 }
407- let (values, highlights) = events.into_iter().unzip();
407+ let (values, highlights): (Vec<_>, _) = events.into_iter().unzip();
408+ let token = page_token(backwards, &values, messages.end);
408409 Ok(Side {
409410 values,
410411 highlights,
411- token: messages.end,
412+ token,
412413 })
413414 }
414415
416+/// going back, a page that reaches the room's creation or comes back empty is the start, even
417+/// when the server still hands out a token after it, as continuwuity does
418+fn page_token(backwards: bool, values: &[Value], end: Option<String>) -> Option<String> {
419+ let created = values
420+ .iter()
421+ .any(|value| value.get("type").and_then(Value::as_str) == Some("m.room.create"));
422+ end.filter(|_| !(backwards && (values.is_empty() || created)))
423+}
424+
415425 /// a search hit as event context would show it, `values` its original and then its latest edit
416426 pub(crate) async fn preview(
417427 room: &Room,
@@ -613,3 +623,26 @@ fn validate_limit(limit: u16) -> Result<(), Error> {
613623 Ok(())
614624 }
615625 }
626+
627+#[cfg(test)]
628+mod tests {
629+ use serde_json::json;
630+
631+ use super::page_token;
632+
633+ #[test]
634+ fn going_back_past_creation_or_an_empty_page_is_the_start() {
635+ let token = || Some("more".to_owned());
636+ let message = json!({ "type": "m.room.message" });
637+ let create = json!({ "type": "m.room.create" });
638+ assert_eq!(page_token(true, &[message.clone()], token()), token());
639+ assert_eq!(
640+ page_token(true, &[message.clone(), create.clone()], token()),
641+ None
642+ );
643+ assert_eq!(page_token(true, &[], token()), None);
644+ assert_eq!(page_token(true, &[message.clone()], None), None);
645+ assert_eq!(page_token(false, &[], token()), token());
646+ assert_eq!(page_token(false, &[create], token()), token());
647+ }
648+}
Modified file: tests/mentions_inbox.rs+4 additions−1 deletions
@@ -733,7 +733,10 @@ async fn event_context_uses_independent_direction_limits_and_saved_mention_conte
733733 .await
734734 .unwrap();
735735 let context = &session.context;
736- assert_eq!(context.start_token.as_deref(), Some("manual-before-next"));
736+ assert_eq!(
737+ context.start_token, None,
738+ "an empty page going back is the start"
739+ );
737740 assert_eq!(context.end_token.as_deref(), Some("manual-after-next"));
738741 handle
739742 .close_event_context(&session.context_id)