diff --git a/src/main.rs b/src/main.rs index 44058b2..df7d6df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -958,23 +958,50 @@ async fn compute_read_markers( if user == own_user { continue; } - let receipt_eid = match room + let (receipt_eid, receipt_ts) = match room .load_user_receipt(ReceiptType::Read, ReceiptThread::Unthreaded, user) .await { - Ok(Some((eid, _))) => eid, + Ok(Some((eid, r))) => { + let ts = r.ts.map(|t| ts_secs_from(t.0)).unwrap_or(0); + (eid, ts) + } _ => continue, }; - let Some(&user_pos) = positions.get(&receipt_eid) else { - continue; + + // Three cases: + // 1. Receipt event is in our window → mark messages at idx <= user_pos + // 2. Receipt is for an event newer than our window (timestamp-based) + // → mark every message as read + // 3. Receipt is older than our window → mark nothing + let user_msg_idx_inclusive: Option = if let Some(&p) = positions.get(&receipt_eid) { + Some(p) + } else { + // Compare receipt ts with the newest message ts + let newest_msg_ts = timeline + .iter() + .rev() + .find_map(|t| match t { + TimelineItem::Message { ts, .. } => Some(*ts), + _ => None, + }) + .unwrap_or(0); + if receipt_ts > 0 && receipt_ts >= newest_msg_ts { + // User has read past our entire window + Some(timeline.len().saturating_sub(1)) + } else { + None + } }; - // Mark this user as a reader for all messages at index <= user_pos - for item in timeline.iter().take(user_pos + 1) { - if let TimelineItem::Message { event_id, .. } = item { - readers - .entry(event_id.clone()) - .or_default() - .push(user.clone()); + + if let Some(up_to) = user_msg_idx_inclusive { + for item in timeline.iter().take(up_to + 1) { + if let TimelineItem::Message { event_id, .. } = item { + readers + .entry(event_id.clone()) + .or_default() + .push(user.clone()); + } } } }