handle group delete events by purging local state

This commit is contained in:
2026-09-30 14:21:54 +07:00
parent cb10c2d26e
commit dbdfa8ad67
4 changed files with 61 additions and 10 deletions
+12
View File
@@ -485,6 +485,18 @@ impl Group {
cx.notify(); cx.notify();
} }
pub(crate) fn forget(&mut self, id: EventId, cx: &mut Context<Self>) {
self.window.remove(id);
if !self.index.remove(&id) {
return;
}
self.messages.retain(|message| message.id != id);
cx.emit(GroupEvent::Updated);
cx.notify();
}
pub(crate) fn eose(&mut self, cx: &mut Context<Self>) { pub(crate) fn eose(&mut self, cx: &mut Context<Self>) {
self.loading = false; self.loading = false;
cx.emit(GroupEvent::Eose); cx.emit(GroupEvent::Eose);
+38
View File
@@ -506,9 +506,13 @@ impl GroupsRegistry {
key.clone(), key.clone(),
cx.subscribe(&group, |this, group, event, cx| match event { cx.subscribe(&group, |this, group, event, cx| match event {
GroupEvent::Sent(event) => { GroupEvent::Sent(event) => {
if event.kind == Kind::GroupDeleteEvent {
this.forget_event(group, event.clone(), cx);
} else {
let key = group.read(cx).key().clone(); let key = group.read(cx).key().clone();
this.reload(key, Some(event.clone()), cx); this.reload(key, Some(event.clone()), cx);
} }
}
GroupEvent::Updated => { GroupEvent::Updated => {
this.refresh_entry(group, cx); this.refresh_entry(group, cx);
this.sync_forks(false, cx); this.sync_forks(false, cx);
@@ -676,6 +680,40 @@ impl GroupsRegistry {
})); }));
} }
fn forget_event(&mut self, group: Entity<Group>, event: Event, cx: &mut Context<Self>) {
let Some(target) = event.tags.event_ids().next() else {
return;
};
let key = group.read(cx).key().clone();
let cache_tag = key.cache_tag();
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let purge = cx.background_spawn(async move {
let filter = Filter::new()
.kind(Kind::ApplicationSpecificData)
.identifier(target.to_hex())
.custom_tag(SingleLetterTag::LOWERCASE_R, cache_tag);
client.database().delete(filter).await
});
self.tasks.push(cx.spawn(async move |this, cx| {
if let Err(error) = purge.await {
log::warn!("nip29: dropping the deleted envelope failed: {error}");
}
this.update(cx, |this, cx| {
group.update(cx, |group, cx| {
group.forget(target, cx);
});
this.reload(key, Some(event), cx);
})
.ok();
}));
}
fn handle_event(&mut self, id: SubscriptionId, event: Event, cx: &mut Context<Self>) { fn handle_event(&mut self, id: SubscriptionId, event: Event, cx: &mut Context<Self>) {
if id == list_subscription_id() { if id == list_subscription_id() {
match GroupList::parse(&event) { match GroupList::parse(&event) {
+4
View File
@@ -819,6 +819,10 @@ impl TimelineWindow {
self.0.truncate(TIMELINE_WINDOW); self.0.truncate(TIMELINE_WINDOW);
} }
pub fn remove(&mut self, id: EventId) {
self.0.retain(|event| event.id != id);
}
pub fn previous_tag(&self, me: PublicKey) -> Option<Tag> { pub fn previous_tag(&self, me: PublicKey) -> Option<Tag> {
let references: Vec<String> = self let references: Vec<String> = self
.0 .0
+5 -8
View File
@@ -353,10 +353,9 @@ reject anything it does not want.
must carry every child tag, because a `9002` that drops one is rejected by the must carry every child tag, because a `9002` that drops one is rejected by the
relay; the builder re-emits them from the struct. relay; the builder re-emits them from the struct.
`delete_event` is a **relay** instruction only. The relay stops serving the event, `delete_event` sends `9005`, and once the relay accepts it the registry drops the
but the local timeline keeps it: the backend does not evict the envelope or the target's cached envelope, evicts the row and re-reads the group, so the timeline stops
message, so a deletion the relay accepted is still visible locally until the UI showing it. Remote deletions are not folded in: the standing REQ does not carry `9005`.
phase drops it. That is the one moderation write left incomplete (see below).
## Pins ## Pins
@@ -551,10 +550,8 @@ before they reach the consumer, so a replay cannot double-hydrate.
`chat::Message` rendering lifted (or copied) out of `ChatPanel`: the data type `chat::Message` rendering lifted (or copied) out of `ChatPanel`: the data type
is already shared, only the row rendering is not. `crates/workspace/src/panels/browse.rs` is already shared, only the row rendering is not. `crates/workspace/src/panels/browse.rs`
is the stub for browsing. is the stub for browsing.
- **`9005` delete does not touch local state.** The relay stops serving the - **Remote deletions are not folded in.** A `9005` from another admin never reaches the
event, but the envelope and the row stay until a reload. Dropping it needs a standing REQ, so only our own accepted deletes evict the row.
filter on the target's `d` tag plus the group's `r` cache tag, and an eviction
from `messages` and the id index; nothing does that today.
- **The LiveKit response shape is a guess.** The NIP names no fields, so - **The LiveKit response shape is a guess.** The NIP names no fields, so
`LivekitSession::parse` reads `token` (or `jwt`) and `url` (or `server_url`) and `LivekitSession::parse` reads `token` (or `jwt`) and `url` (or `server_url`) and
fails loudly on anything else. Re-check it against the relay actually targeted fails loudly on anything else. Re-check it against the relay actually targeted