feat: new search experience (#58)

Reviewed-on: reya/coop#58
This commit is contained in:
2026-10-06 03:00:21 +00:00
parent 2a404591d8
commit 2bce050c3b
21 changed files with 2134 additions and 1295 deletions
+21 -2
View File
@@ -88,6 +88,7 @@ pub struct Group {
oldest: Option<Timestamp>,
loading: bool,
loading_more: bool,
active: bool,
refused: Option<String>,
tasks: SmallVec<[Task<()>; 2]>,
}
@@ -115,6 +116,7 @@ impl Group {
oldest: None,
loading: true,
loading_more: false,
active: false,
refused: None,
tasks: smallvec![],
}
@@ -196,6 +198,14 @@ impl Group {
self.loading_more
}
pub(crate) fn is_active(&self) -> bool {
self.active
}
pub(crate) fn activate(&mut self) {
self.active = true;
}
pub fn oldest(&self) -> Option<Timestamp> {
self.oldest
}
@@ -496,16 +506,25 @@ impl Group {
})
}
pub(crate) fn load_envelopes(&self, cx: &App) -> Task<Result<Vec<Event>>> {
pub(crate) fn load_envelopes(&self, cx: &App, state_only: bool) -> Task<Result<Vec<Event>>> {
let cache_tag = self.key.cache_tag();
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
cx.background_spawn(async move {
let filter = Filter::new()
let mut filter = Filter::new()
.kind(Kind::ApplicationSpecificData)
.custom_tag(SingleLetterTag::LOWERCASE_R, cache_tag);
if state_only {
let kinds = STATE_KINDS
.iter()
.chain(MEMBERSHIP_KINDS.iter())
.map(|kind| kind.to_string());
filter = filter.custom_tags(SingleLetterTag::LOWERCASE_K, kinds);
}
let mut events: Vec<Event> = client
.database()
.query(filter)
+77 -4
View File
@@ -549,7 +549,7 @@ impl GroupsRegistry {
);
self.subscribe(key.clone(), cx);
self.reload(key, None, cx);
self.load_state(key, cx);
}
fn subscribe(&mut self, key: GroupKey, cx: &mut Context<Self>) {
@@ -679,7 +679,7 @@ impl GroupsRegistry {
};
let logged = key.clone();
let load = group.read(cx).load_envelopes(cx);
let load = group.read(cx).load_envelopes(cx, false);
self.tasks.push(cx.spawn(async move |_this, cx| {
if let Some(event) = event
@@ -704,6 +704,71 @@ impl GroupsRegistry {
}));
}
/// Load only the group state (metadata, members, membership) from the cache.
fn load_state(&mut self, key: GroupKey, cx: &mut Context<Self>) {
let Some(group) = self.group(&key, cx) else {
return;
};
let Some(me) = NostrRegistry::global(cx).read(cx).current_user() else {
return;
};
let logged = key.clone();
let load = group.read(cx).load_envelopes(cx, true);
self.tasks
.push(cx.spawn(async move |_this, cx| match load.await {
Ok(events) => {
group.update(cx, |group, cx| {
group.hydrate(events, me, cx);
});
}
Err(error) => {
log::warn!("nip29: loading {} state failed: {error}", logged.id());
}
}));
}
/// Cache and absorb a single event without rebuilding the group.
fn absorb(&mut self, key: GroupKey, event: Event, cx: &mut Context<Self>) {
let Some(group) = self.group(&key, cx) else {
return;
};
let Some(me) = NostrRegistry::global(cx).read(cx).current_user() else {
return;
};
self.tasks.push(cx.spawn(async move |_this, cx| {
if let Err(error) = group
.read_with(cx, |group, cx| group.save_envelope(event.clone(), cx))
.await
{
log::warn!("nip29: caching failed: {error}");
return;
}
group.update(cx, |group, cx| {
group.hydrate(vec![event], me, cx);
});
}));
}
/// Load the full group content on first open.
pub fn activate(&mut self, key: &GroupKey, cx: &mut Context<Self>) {
let Some(group) = self.group(key, cx) else {
return;
};
if group.read(cx).is_active() {
return;
}
group.update(cx, |group, _cx| group.activate());
self.reload(key.clone(), None, cx);
}
fn forget_event(&mut self, group: Entity<Group>, event: Event, cx: &mut Context<Self>) {
let Some(target) = event.tags.event_ids().next() else {
return;
@@ -761,7 +826,15 @@ impl GroupsRegistry {
return;
};
self.reload(key, Some(event), cx);
let active = self
.group(&key, cx)
.is_some_and(|group| group.read(cx).is_active());
if active {
self.reload(key, Some(event), cx);
} else {
self.absorb(key, event, cx);
}
}
fn absorb_candidate(&mut self, id: &SubscriptionId, event: &Event, cx: &mut Context<Self>) {
@@ -819,7 +892,7 @@ impl GroupsRegistry {
}
if id == fork_subscription_id() {
log::warn!("nip29: the admins' group lists were refused: {reason}");
log::debug!("nip29: the admins' group lists were refused: {reason}");
return;
}