feat(chat): unread count + sidebar badge (Q1)

Track a per-conversation "last seen" watermark (message timestamp) on App:
- chatUnreadCount() sums incoming messages newer than each conversation's
  watermark; surfaced as SidebarStatus.chatUnreadCount → a badge on the Chat nav
  item (mirrors the History/Peers badges).
- Viewing a thread marks it seen (markChatConversationSeen while displayed).
- Baseline on load: existing stored messages are marked seen, so only messages
  that arrive while the app is open badge as unread.
- Wiped in resetChatSession so unread state never leaks across a wallet switch.
In-memory only (resets on app restart); persistence is a later refinement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 15:56:58 -05:00
parent 055685a4c4
commit 100ed01469
5 changed files with 44 additions and 0 deletions

View File

@@ -679,6 +679,17 @@ private:
// no longer matches — so wallet A's seed can't be provisioned under wallet B via a stale job.
int chat_session_generation_ = 0;
bool chat_identity_unavailable_ = false; // provisioning failed definitively (e.g. non-mnemonic wallet)
// Per-conversation "last seen" watermark (message timestamp) for unread tracking (Q1). Updated when a
// thread is viewed (markChatConversationSeen); wiped in resetChatSession so unread doesn't leak across
// wallets. In-memory only (resets on app restart).
std::map<std::string, std::int64_t> chat_seen_watermark_;
public:
// Total unread incoming chat messages across all conversations (for the sidebar badge). 0 when the
// feature is off / no identity.
int chatUnreadCount() const;
// Mark a conversation read up to latestTs (called by the Chat tab while a thread is displayed).
void markChatConversationSeen(const std::string& cid, std::int64_t latestTs);
private:
// Provision the chat identity once the wallet seed is reachable+unlocked (per-tick, both
// variants); derives via deriveChatIdentityFromSecret and wipes the secret. No-op when the
// feature is off, already provisioned, in flight, or unavailable.