commit 22483f23fe277577b089399b1fcac17c3ed3b0d3
parent 7c970b5fbfcdbf7468ef2cc15a01d07ba5b719a6
Author: e <e@*.lan>
Date: Sun, 28 Dec 2025 09:33:25 -0600
timeline: add reset methods for subscription and view cleanup
Add methods to properly reset timeline state when filters need to be
rebuilt (e.g., after follow/unfollow actions).
Changes:
- TimelineSub::reset(): Unsubscribes from ndb and relay pool before
resetting state. Preserves depender count for reference counting.
- TimelineTab::reset(): Clears cached notes and resets virtual list.
- Timeline::reset_views(): Resets all view tabs.
These methods ensure no subscription leaks when rebuilding timelines,
and that stale notes from unfollowed accounts are cleared.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: e <e@*.lan>
Signed-off-by: alltheseas
Diffstat:
2 files changed, 59 insertions(+), 0 deletions(-)
diff --git a/crates/notedeck_columns/src/multi_subscriber.rs b/crates/notedeck_columns/src/multi_subscriber.rs
@@ -307,6 +307,45 @@ impl Default for TimelineSub {
}
impl TimelineSub {
+ /// Reset the subscription state, properly unsubscribing from ndb and
+ /// relay pool before clearing.
+ ///
+ /// Used when the contact list changes and we need to rebuild the
+ /// timeline with a new filter. Preserves the depender count so that
+ /// shared subscription reference counting remains correct.
+ pub fn reset(&mut self, ndb: &mut Ndb, pool: &mut RelayPool) {
+ let before = self.state.clone();
+
+ let dependers = match &mut self.state {
+ SubState::NoSub { dependers } => *dependers,
+
+ SubState::LocalOnly { local, dependers } => {
+ if let Err(e) = ndb.unsubscribe(*local) {
+ tracing::error!("TimelineSub::reset: failed to unsubscribe from ndb: {e}");
+ }
+ *dependers
+ }
+
+ SubState::RemoteOnly { remote, dependers } => {
+ pool.unsubscribe(remote.to_owned());
+ *dependers
+ }
+
+ SubState::Unified { unified, dependers } => {
+ pool.unsubscribe(unified.remote.to_owned());
+ if let Err(e) = ndb.unsubscribe(unified.local) {
+ tracing::error!("TimelineSub::reset: failed to unsubscribe from ndb: {e}");
+ }
+ *dependers
+ }
+ };
+
+ self.state = SubState::NoSub { dependers };
+ self.filter = None;
+
+ tracing::debug!("TimelineSub::reset: {:?} => {:?}", before, self.state);
+ }
+
pub fn try_add_local(&mut self, ndb: &Ndb, filter: &HybridFilter) {
let before = self.state.clone();
match &mut self.state {
diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs
@@ -150,6 +150,16 @@ impl TimelineTab {
}
}
+ /// Reset the tab to an empty state, clearing all cached notes.
+ ///
+ /// Used when the contact list changes and we need to rebuild
+ /// the timeline with a new filter.
+ pub fn reset(&mut self) {
+ self.units = TimelineUnits::with_capacity(1000);
+ self.selection = 0;
+ self.list.borrow_mut().reset();
+ }
+
fn insert<'a>(
&mut self,
payloads: Vec<&'a NotePayload>,
@@ -352,6 +362,16 @@ impl Timeline {
self.views.iter_mut().find(|tab| tab.filter == view)
}
+ /// Reset all views to an empty state, clearing all cached notes.
+ ///
+ /// Used when the contact list changes and we need to rebuild
+ /// the timeline with a new filter.
+ pub fn reset_views(&mut self) {
+ for view in &mut self.views {
+ view.reset();
+ }
+ }
+
/// Initial insert of notes into a timeline. Subsequent inserts should
/// just use the insert function
pub fn insert_new(