feat(chat): mute conversations (Q10)

Per-conversation mute toggle in the thread header. Muted conversations (tracked
by cid in settings, so it persists) are skipped by chatUnreadCount(), so they
neither raise the nav-item unread badge nor the new-message toast — the toast now
gates on a chatUnreadCount() delta across ingest, which already skips muted cids,
so mute is respected for free. "Block" (rejecting a peer's inbound memos) is a
larger ingest-filter change and is intentionally left out of this pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:48:57 -05:00
parent fecc9015fb
commit 24c661f743
4 changed files with 40 additions and 4 deletions

View File

@@ -147,6 +147,11 @@ bool Settings::load(const std::string& path)
loadScalar(j, "language", language_);
loadScalar(j, "skin_id", skin_id_);
loadScalar(j, "chat_reply_zaddr", chat_reply_zaddr_);
if (j.contains("muted_chat_cids") && j["muted_chat_cids"].is_array()) {
muted_chat_cids_.clear();
for (const auto& c : j["muted_chat_cids"])
if (c.is_string()) muted_chat_cids_.push_back(c.get<std::string>());
}
loadScalar(j, "acrylic_enabled", acrylic_enabled_);
loadScalar(j, "acrylic_quality", acrylic_quality_);
loadScalar(j, "blur_multiplier", blur_multiplier_);
@@ -422,6 +427,9 @@ bool Settings::save(const std::string& path)
j["language"] = language_;
j["skin_id"] = skin_id_;
j["chat_reply_zaddr"] = chat_reply_zaddr_;
j["muted_chat_cids"] = json::array();
for (const auto& c : muted_chat_cids_)
j["muted_chat_cids"].push_back(c);
j["acrylic_enabled"] = acrylic_enabled_;
j["acrylic_quality"] = acrylic_quality_;
j["blur_multiplier"] = blur_multiplier_;