feat(chat): per-conversation delete (revive + block); memoize chat/badge render
Adds a per-conversation "delete" with two modes, and removes the per-frame rescans of the chat history that shared these files. Delete conversation (header trash icon → confirm dialog): - Delete (revive-on-new-message): clears local history and tombstones the messages (new chat_deleted table, keyed dedup hashes) so the every-few- seconds memo re-scan can't re-import them; a genuinely NEW message (new txid) revives the thread. - Delete & block: removes history WITHOUT a tombstone and records the cid as blocked (settings); ChatService::ingest drops that conversation's messages — old and future — until unblocked from the "Blocked" manager, which then re-imports the conversation from chain. - Local-only (messages remain on-chain; the peer keeps their copy). deleteConversation() deletes the DB rows FIRST and only then mutates the store, so a failed write can't leave the two diverged. Unit-tested (revive / tombstone-survives-reload / block / unblock) and adversarially reviewed (store/DB divergence, half-open DB, revive-unread). Performance (chat + badge hot paths, from the perf audit): - ChatStore gains revision(); the Chat unread badge is now a single O(N) no-alloc pass cached on it (was O(conversations x messages) copy+sort every frame). The conversation list and open thread are memoized on revision() (+ show-hidden and AddressBook::revision() for peer-name resolution). - AddressBook gains revision() so an in-place contact rename invalidates the chat memo (an edit keeps entries().size() constant). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "chat_store.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -55,6 +56,19 @@ public:
|
||||
// the seed-derived key) into the in-memory store. No-op without an unlocked database.
|
||||
void loadFromDatabase();
|
||||
|
||||
// Set a predicate returning true for a BLOCKED conversation id. ingest() drops those messages
|
||||
// entirely (they are never stored or persisted) until the predicate stops returning true — this is
|
||||
// how "delete & block" suppresses old and future messages. Typically wired to Settings::isChatBlocked.
|
||||
void setBlockedPredicate(std::function<bool(const std::string&)> pred) { blocked_pred_ = std::move(pred); }
|
||||
|
||||
// Locally delete a conversation: remove its messages from the store and the database. When `block`
|
||||
// is false, the removed messages are tombstoned so a chain re-scan won't re-import them, but a
|
||||
// genuinely NEW message (new txid) revives the thread. When `block` is true, the rows are removed
|
||||
// WITHOUT a tombstone (the caller also records the cid as blocked via the predicate above); unblocking
|
||||
// later lets the conversation re-import from chain. Returns false (leaving the store untouched) if the
|
||||
// persisted rows couldn't be removed, so the caller can avoid a store/DB divergence.
|
||||
bool deleteConversation(const std::string& conversationId, bool block);
|
||||
|
||||
// --- Outgoing (compose) ---
|
||||
// My chat public key (hex), or "" without an identity — goes in an outgoing header's "p".
|
||||
std::string identityPublicKeyHex() const;
|
||||
@@ -90,6 +104,7 @@ private:
|
||||
bool has_identity_ = false;
|
||||
ChatStore store_;
|
||||
ChatDatabase* db_ = nullptr; // optional; not owned
|
||||
std::function<bool(const std::string&)> blocked_pred_; // true => cid is blocked (drop its messages)
|
||||
};
|
||||
|
||||
} // namespace dragonx::chat
|
||||
|
||||
Reference in New Issue
Block a user