commit 7c7bd5afdaf76f93acd546ed79ed438546e92d03
parent 25994522c8c5245f59fd58dccdb5083694c852b2
Author: kernelkind <kernelkind@gmail.com>
Date: Tue, 24 Feb 2026 22:41:51 -0500
feat(outbox-int): route remote thread subs through scoped subs
Replace direct RelayPool subscribe/unsubscribe calls in thread subscriptions with ScopedSubApi declarations while preserving the existing local remote bookkeeping and depender semantics for now.
This is an intermediate migration step: thread_sub.rs now declares remote reply subscriptions through scoped subs, and thread open/close call paths (actionbar, thread routing cleanup, nav/toolbar cleanup) plumb a ScopedSubApi handle from RemoteApi.
The commit intentionally keeps the remotes map and depender counting in place so the backend swap can be reviewed separately from the upcoming owner-lifecycle rewrite (per-scope owners) and per-account local bookkeeping changes.
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
6 files changed, 72 insertions(+), 35 deletions(-)
diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs
@@ -135,12 +135,13 @@ fn execute_note_action(
tracing::error!("No thread selection for {}?", hex::encode(note_id.bytes()));
break 'ex;
};
+ let mut scoped_subs = remote.scoped_subs(accounts);
timeline_res = threads
.open(
ndb,
txn,
- pool,
+ &mut scoped_subs,
&thread_selection,
preview,
col,
diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs
@@ -304,6 +304,7 @@ fn process_nav_resp(
&mut app.view_state,
ctx.ndb,
ctx.legacy_pool,
+ &mut ctx.remote.scoped_subs(ctx.accounts),
return_type,
col,
);
diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs
@@ -3,7 +3,7 @@ use enostr::{NoteId, Pubkey, RelayPool};
use nostrdb::Ndb;
use notedeck::{
tr, Localization, NoteZapTargetOwned, ReplacementType, ReportTarget, RootNoteIdBuf, Router,
- WalletType,
+ ScopedSubApi, WalletType,
};
use std::ops::Range;
@@ -800,6 +800,7 @@ pub fn cleanup_popped_route(
view_state: &mut ViewState,
ndb: &mut Ndb,
pool: &mut RelayPool,
+ scoped_subs: &mut ScopedSubApi,
return_type: ReturnType,
col_index: usize,
) {
@@ -810,7 +811,7 @@ pub fn cleanup_popped_route(
}
}
Route::Thread(selection) => {
- threads.close(ndb, pool, selection, return_type, col_index);
+ threads.close(ndb, scoped_subs, selection, return_type, col_index);
}
Route::EditProfile(pk) => {
view_state.pubkey_to_profile_state.remove(pk);
diff --git a/crates/notedeck_columns/src/timeline/sub/thread_sub.rs b/crates/notedeck_columns/src/timeline/sub/thread_sub.rs
@@ -1,8 +1,8 @@
use egui_nav::ReturnType;
-use enostr::{NoteId, RelayPool};
+use enostr::NoteId;
use hashbrown::HashMap;
use nostrdb::{Filter, Ndb, Subscription};
-use uuid::Uuid;
+use notedeck::{RelaySelection, ScopedSubApi, ScopedSubIdentity, SubConfig, SubKey, SubOwnerKey};
use crate::timeline::{
sub::{ndb_sub, ndb_unsub},
@@ -22,14 +22,14 @@ type MetaId = usize;
pub struct Remote {
pub filter: Vec<Filter>,
- subid: String,
+ owner: SubOwnerKey,
dependers: usize,
}
impl std::fmt::Debug for Remote {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Remote")
- .field("subid", &self.subid)
+ .field("owner", &self.owner)
.field("dependers", &self.dependers)
.finish()
}
@@ -51,12 +51,12 @@ impl ThreadSubs {
pub fn subscribe(
&mut self,
ndb: &mut Ndb,
- pool: &mut RelayPool,
+ scoped_subs: &mut ScopedSubApi<'_, '_>,
meta_id: usize,
id: &ThreadSelection,
local_sub_filter: Vec<Filter>,
new_scope: bool,
- remote_sub_filter: impl FnOnce() -> Vec<Filter>,
+ remote_sub_filter: Vec<Filter>,
) {
let cur_scopes = self.scopes.entry(meta_id).or_default();
@@ -72,7 +72,12 @@ impl ThreadSubs {
hashbrown::hash_map::RawEntryMut::Vacant(entry) => {
let (_, res) = entry.insert(
NoteId::new(*id.root_id.bytes()),
- sub_remote(pool, remote_sub_filter, id),
+ sub_remote(
+ scoped_subs,
+ &NoteId::new(*id.root_id.bytes()),
+ remote_sub_filter,
+ id,
+ ),
);
res
@@ -92,7 +97,7 @@ impl ThreadSubs {
pub fn unsubscribe(
&mut self,
ndb: &mut Ndb,
- pool: &mut RelayPool,
+ scoped_subs: &mut ScopedSubApi<'_, '_>,
meta_id: usize,
id: &ThreadSelection,
return_type: ReturnType,
@@ -126,8 +131,8 @@ impl ThreadSubs {
.remotes
.remove(&id.root_id.bytes())
.expect("code above should guarentee existence");
- tracing::debug!("Remotely unsubscribed: {}", remote.subid);
- pool.unsubscribe(remote.subid);
+ tracing::debug!("Remotely unsubscribed: {:?}", remote.owner);
+ let _ = scoped_subs.drop_owner(remote.owner);
}
tracing::debug!(
@@ -218,6 +223,23 @@ fn log_scope_root_mismatch(scope: &Scope, id: &ThreadSelection) {
}
}
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+enum ThreadScopedSub {
+ RepliesByRoot,
+}
+
+fn thread_remote_sub_key(root_id: &RootNoteId) -> SubKey {
+ SubKey::builder(ThreadScopedSub::RepliesByRoot)
+ .with(*root_id.bytes())
+ .finish()
+}
+
+fn thread_remote_owner_key(root_id: &RootNoteId) -> SubOwnerKey {
+ SubOwnerKey::builder(ThreadScopedSub::RepliesByRoot)
+ .with(*root_id.bytes())
+ .finish()
+}
+
fn sub_current_scope(
ndb: &mut Ndb,
selection: &ThreadSelection,
@@ -245,25 +267,30 @@ fn sub_current_scope(
}
fn sub_remote(
- pool: &mut RelayPool,
- remote_sub_filter: impl FnOnce() -> Vec<Filter>,
+ scoped_subs: &mut ScopedSubApi<'_, '_>,
+ root_id: &RootNoteId,
+ remote_sub_filter: Vec<Filter>,
id: impl std::fmt::Debug,
) -> Remote {
- let subid = Uuid::new_v4().to_string();
-
- let filter = remote_sub_filter();
-
- let remote = Remote {
- filter: filter.clone(),
- subid: subid.clone(),
- dependers: 0,
- };
+ let filter = remote_sub_filter;
+ let owner = thread_remote_owner_key(root_id);
+ let key = thread_remote_sub_key(root_id);
tracing::debug!("Remote subscribe for {:?}", id);
- pool.subscribe(subid, filter);
+ let identity = ScopedSubIdentity::global(owner, key);
+ let config = SubConfig {
+ relays: RelaySelection::AccountsRead,
+ filters: filter.clone(),
+ use_transparent: false,
+ };
+ let _ = scoped_subs.ensure_sub(identity, config);
- remote
+ Remote {
+ filter,
+ owner,
+ dependers: 0,
+ }
}
fn local_sub_new_scope(
diff --git a/crates/notedeck_columns/src/timeline/thread.rs b/crates/notedeck_columns/src/timeline/thread.rs
@@ -1,9 +1,9 @@
use egui_nav::ReturnType;
use egui_virtual_list::VirtualList;
-use enostr::{NoteId, RelayPool};
+use enostr::NoteId;
use hashbrown::{hash_map::RawEntryMut, HashMap};
use nostrdb::{Filter, Ndb, Note, NoteKey, NoteReplyBuf, Transaction};
-use notedeck::{NoteCache, NoteRef, UnknownIds};
+use notedeck::{NoteCache, NoteRef, ScopedSubApi, UnknownIds};
use crate::{
actionbar::{process_thread_notes, NewThreadNotes},
@@ -66,7 +66,7 @@ impl Threads {
&mut self,
ndb: &mut Ndb,
txn: &Transaction,
- pool: &mut RelayPool,
+ scoped_subs: &mut ScopedSubApi<'_, '_>,
thread: &ThreadSelection,
new_scope: bool,
col: usize,
@@ -113,10 +113,15 @@ impl Threads {
.collect::<Vec<_>>()
});
- self.subs
- .subscribe(ndb, pool, col, thread, local_sub_filter, new_scope, || {
- replies_filter_remote(thread)
- });
+ self.subs.subscribe(
+ ndb,
+ scoped_subs,
+ col,
+ thread,
+ local_sub_filter,
+ new_scope,
+ replies_filter_remote(thread),
+ );
new_notes.map(|notes| NewThreadNotes {
selected_note_id: NoteId::new(*selected_note_id),
@@ -127,13 +132,14 @@ impl Threads {
pub fn close(
&mut self,
ndb: &mut Ndb,
- pool: &mut RelayPool,
+ scoped_subs: &mut ScopedSubApi<'_, '_>,
thread: &ThreadSelection,
return_type: ReturnType,
id: usize,
) {
tracing::info!("Closing thread: {:?}", thread);
- self.subs.unsubscribe(ndb, pool, id, thread, return_type);
+ self.subs
+ .unsubscribe(ndb, scoped_subs, id, thread, return_type);
}
/// Responsible for making sure the chain and the direct replies are up to date
diff --git a/crates/notedeck_columns/src/toolbar.rs b/crates/notedeck_columns/src/toolbar.rs
@@ -103,6 +103,7 @@ fn pop_to_root(app: &mut Damus, ctx: &mut AppContext, col_index: usize) {
&mut app.view_state,
ctx.ndb,
ctx.legacy_pool,
+ &mut ctx.remote.scoped_subs(ctx.accounts),
ReturnType::Click,
col_index,
);