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:
@@ -16,6 +16,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
struct sqlite3;
|
||||
@@ -54,8 +55,20 @@ public:
|
||||
|
||||
void clearWallet(); // delete the unlocked wallet's rows
|
||||
|
||||
// Per-conversation local delete. Removes the given messages' rows; when `tombstone` is true it also
|
||||
// records their (txid,position) dedup keys so a chain re-scan never re-imports them — this backs the
|
||||
// "delete, but a NEW message revives the thread" path. With `tombstone` false the rows are simply
|
||||
// removed (used by "delete & block", where a settings-level cid block suppresses re-import until the
|
||||
// user unblocks, at which point the history re-imports from chain). Atomic; no-op while locked.
|
||||
bool deleteMessages(const std::vector<ChatMessage>& messages, bool tombstone);
|
||||
|
||||
// True if this message's (txid,position) was locally deleted with a tombstone. Checked against an
|
||||
// in-memory cache loaded on unlock — O(1), no SQL. False while locked.
|
||||
bool isTombstoned(const ChatMessage& message) const;
|
||||
|
||||
private:
|
||||
bool ensureOpen();
|
||||
void loadTombstones(); // populate tombstones_ from chat_deleted for the unlocked wallet
|
||||
bool exec(const char* sql);
|
||||
bool createSchema();
|
||||
std::string dedupHash(const std::string& txid, std::size_t position) const;
|
||||
@@ -74,6 +87,7 @@ private:
|
||||
std::array<unsigned char, 32> key_{}; // AEAD storage key (seed-derived)
|
||||
std::string wallet_tag_; // seed-derived row partition (a keyed hash, hex)
|
||||
bool key_ready_ = false;
|
||||
std::unordered_set<std::string> tombstones_; // dedup_hash cache of locally-deleted messages
|
||||
};
|
||||
|
||||
} // namespace dragonx::chat
|
||||
|
||||
Reference in New Issue
Block a user