feat: auto-unpin deleted posts from NIP-51 pin list

When a kind-5 deletion event arrives for a note that is currently pinned,
rewrite the user's PinListEvent to drop the deleted entries and broadcast
the new list. Mirrors the existing deletedNotes() pattern used by
peopleLists, followLists, and labeledBookmarkLists in Account.

Previously the Pinned Notes screen silently hid deleted posts via the
FeedContentState deletion filter, leaving orphan entries in the pin list
that the user had no way to clean up.
This commit is contained in:
Claude
2026-04-18 21:07:16 +00:00
parent 8c71d490a7
commit 6dc723ae2c
2 changed files with 26 additions and 0 deletions
@@ -2293,6 +2293,13 @@ class Account(
}
}
suspend fun removeDeletedPins(deletedNotes: Set<Note>) {
if (!isWriteable()) return
val event = pinState.removeDeletedPins(deletedNotes) ?: return
sendMyPublicAndPrivateOutbox(event)
}
suspend fun createAddPinEvent(note: Note): Pair<Event, Set<NormalizedRelayUrl>>? {
if (!isWriteable() || note.isDraft()) return null
@@ -2674,6 +2681,7 @@ class Account(
peopleLists.deletedNotes(deletedNotes)
followLists.deletedNotes(deletedNotes)
labeledBookmarkLists.deletedNotes(deletedNotes)
removeDeletedPins(deletedNotes)
}
}
}
@@ -127,4 +127,22 @@ class PinListState(
signer = signer,
)
}
suspend fun removeDeletedPins(deletedNotes: Set<Note>): PinListEvent? {
val currentList = getPinList() ?: return null
val deletedIds = deletedNotes.mapTo(HashSet()) { it.idHex }
val pinsToRemove = currentList.pinnedEvents().filter { it.eventId in deletedIds }
if (pinsToRemove.isEmpty()) return null
var working: PinListEvent = currentList
for (pin in pinsToRemove) {
working =
PinListEvent.remove(
earlierVersion = working,
pin = pin,
signer = signer,
)
}
return working
}
}