feat(chat): reduce on-chain Control memos into per-conversation hide state
ingest() now routes Control memos (self-addressed, so processed ahead of the "skip our own memos" guard) into a per-cid reducer that keeps the latest op by (compose-time, txid) — monotonic, so a replayed older op never flips state. A conversation whose latest op is "del" is hidden: its messages are dropped from ingest and erased from the store view (even rows loaded from the DB before the signal was reduced), on every device and on a fresh seed import, until a later "res" restores it. The DB rows are left intact so restore (or a rescan) brings the thread back. State is re-derived from chain each session; no local persistence. Adds conversationHiddenOnChain() + onChainDeletedConversations() (the restore picker's source), fully reducer-tested. Phase A, step 3 (reducer) of on-chain chat delete/restore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace dragonx::chat {
|
||||
@@ -70,6 +71,16 @@ public:
|
||||
// persisted rows couldn't be removed, so the caller can avoid a store/DB divergence.
|
||||
bool deleteConversation(const std::string& conversationId, bool block);
|
||||
|
||||
// On-chain control signals (self-addressed Control memos) reduced per conversation: the latest op,
|
||||
// by sender compose time with a txid tiebreak, wins. A conversation whose latest op is "del" is hidden
|
||||
// on-chain — ingest() drops its messages and erases it from the store view, on every device and on a
|
||||
// fresh seed import — until a later "res" restores it. The state is re-derived from chain each session
|
||||
// (the control memos re-harvest on every scan), so it needs no local persistence.
|
||||
bool conversationHiddenOnChain(const std::string& conversationId) const;
|
||||
// Conversations whose latest on-chain control op is "del" (for the restore picker), each paired with
|
||||
// the label carried in its control memo (the peer address / display name at delete time).
|
||||
std::vector<std::pair<std::string, std::string>> onChainDeletedConversations() const;
|
||||
|
||||
// --- Outgoing (compose) ---
|
||||
// My chat public key (hex), or "" without an identity — goes in an outgoing header's "p".
|
||||
std::string identityPublicKeyHex() const;
|
||||
@@ -112,6 +123,18 @@ private:
|
||||
ChatStore store_;
|
||||
ChatDatabase* db_ = nullptr; // optional; not owned
|
||||
std::function<bool(const std::string&)> blocked_pred_; // true => cid is blocked (drop its messages)
|
||||
|
||||
// Reduced on-chain control state, cid -> latest signal. Not persisted (re-derived from chain).
|
||||
struct ControlState {
|
||||
std::string op; // "del" / "res" / future ops
|
||||
std::string label; // the control memo's payload hint (peer address / display name)
|
||||
std::int64_t timestamp = 0;
|
||||
std::string txid;
|
||||
};
|
||||
std::unordered_map<std::string, ControlState> control_;
|
||||
// Monotonic reduce: a later (timestamp, txid) wins; a replay of an older op never flips the state.
|
||||
void applyControl(const std::string& cid, const std::string& op, const std::string& label,
|
||||
std::int64_t timestamp, const std::string& txid);
|
||||
};
|
||||
|
||||
} // namespace dragonx::chat
|
||||
|
||||
Reference in New Issue
Block a user