From 1a3c9d93ee10a8fd8edb116033065a4154794c53 Mon Sep 17 00:00:00 2001 From: Jon Bailey <297513015+Pitchfork-and-Torch@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:08:20 -0400 Subject: [PATCH] Recompute mutual-follow bits on cached For You posts BidirectionalFollowHydrator skipped cache hits, so RankingScorer kept applying the default +15 reply-head boost from a stale is_mutual_follow_author bit for up to 180s. --- .../bidirectional_follow_hydrator.rs | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/home-mixer/candidate_hydrators/bidirectional_follow_hydrator.rs b/home-mixer/candidate_hydrators/bidirectional_follow_hydrator.rs index 139effea..3d5c9e69 100644 --- a/home-mixer/candidate_hydrators/bidirectional_follow_hydrator.rs +++ b/home-mixer/candidate_hydrators/bidirectional_follow_hydrator.rs @@ -14,7 +14,7 @@ pub struct BidirectionalFollowHydrator { #[async_trait] impl Hydrator for BidirectionalFollowHydrator { fn enable(&self, query: &ScoredPostsQuery) -> bool { - query.params.get(EnableBidirectionalFollowHydration) && !query.has_cached_posts + query.params.get(EnableBidirectionalFollowHydration) } async fn hydrate( @@ -205,11 +205,70 @@ mod tests { } #[test] - fn disabled_for_cached_posts() { + fn enabled_on_cache_hit_and_miss() { let hydrator = hydrator(vec![]); let mut q = query(vec![1], true); assert!(hydrator.enable(&q)); q.has_cached_posts = true; - assert!(!hydrator.enable(&q)); + assert!(hydrator.enable(&q)); + } + + fn apply_update( + hydrator: &BidirectionalFollowHydrator, + candidate: &mut PostCandidate, + hydrated: PostCandidate, + ) { + hydrator.update(candidate, hydrated); + } + + #[tokio::test] + async fn cache_hit_clears_stale_mutual_when_author_unfollowed_viewer() { + let hydrator = hydrator(vec![]); + let mut q = query(vec![2], true); + q.has_cached_posts = true; + let mut cached = PostCandidate { + author_id: 2, + is_mutual_follow_author: Some(true), + ..Default::default() + }; + + let out = hydrator.hydrate(&q, &[cached.clone()]).await; + apply_update(&hydrator, &mut cached, out[0].as_ref().unwrap().clone()); + + assert_eq!(cached.is_mutual_follow_author, Some(false)); + } + + #[tokio::test] + async fn cache_hit_clears_stale_mutual_when_viewer_unfollowed() { + let hydrator = hydrator(vec![2]); + let mut q = query(vec![], true); + q.has_cached_posts = true; + let mut cached = PostCandidate { + author_id: 2, + is_mutual_follow_author: Some(true), + ..Default::default() + }; + + let out = hydrator.hydrate(&q, &[cached.clone()]).await; + apply_update(&hydrator, &mut cached, out[0].as_ref().unwrap().clone()); + + assert_eq!(cached.is_mutual_follow_author, Some(false)); + } + + #[tokio::test] + async fn cache_hit_sets_newly_mutual_follow_bit() { + let hydrator = hydrator(vec![2]); + let mut q = query(vec![2], true); + q.has_cached_posts = true; + let mut cached = PostCandidate { + author_id: 2, + is_mutual_follow_author: Some(false), + ..Default::default() + }; + + let out = hydrator.hydrate(&q, &[cached.clone()]).await; + apply_update(&hydrator, &mut cached, out[0].as_ref().unwrap().clone()); + + assert_eq!(cached.is_mutual_follow_author, Some(true)); } }