diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 222644dc3..f9a0dcaa5 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_photo.h"
 #include "data/data_web_page.h"
 #include "data/data_folder.h"
+#include "data/data_forum_topic.h"
 #include "data/data_media_types.h"
 #include "data/data_sparse_ids.h"
 #include "data/data_search_controller.h"
@@ -150,7 +151,7 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
 , _dialogsLoadState(std::make_unique<DialogsLoadState>())
 , _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
 , _topPromotionTimer([=] { refreshTopPromotion(); })
-, _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); })
+, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); })
 , _authorizations(std::make_unique<Api::Authorizations>(this))
 , _attachedStickers(std::make_unique<Api::AttachedStickers>(this))
 , _blockedPeers(std::make_unique<Api::BlockedPeers>(this))
@@ -1323,27 +1324,25 @@ void ApiWrap::deleteAllFromParticipantSend(
 
 void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
 	if (!_stickerSetRequests.contains(setId)) {
-		_stickerSetRequests.insert(setId, qMakePair(access, 0));
+		_stickerSetRequests.emplace(setId, StickerSetRequest{ access });
 	}
 }
 
 void ApiWrap::requestStickerSets() {
-	for (auto i = _stickerSetRequests.begin(), j = i, e = _stickerSetRequests.end(); i != e; i = j) {
-		++j;
-		if (i.value().second) continue;
-
-		auto waitMs = (j == e) ? 0 : kSmallDelayMs;
-		const auto id = MTP_inputStickerSetID(
-			MTP_long(i.key()),
-			MTP_long(i.value().first));
-		i.value().second = request(MTPmessages_GetStickerSet(
-			id,
+	for (auto &[id, info] : _stickerSetRequests) {
+		if (info.id) {
+			continue;
+		}
+		info.id = request(MTPmessages_GetStickerSet(
+			MTP_inputStickerSetID(
+				MTP_long(id),
+				MTP_long(info.accessHash)),
 			MTP_int(0) // hash
-		)).done([=, setId = i.key()](const MTPmessages_StickerSet &result) {
+		)).done([=, setId = id](const MTPmessages_StickerSet &result) {
 			gotStickerSet(setId, result);
-		}).fail([=, setId = i.key()] {
+		}).fail([=, setId = id] {
 			_stickerSetRequests.remove(setId);
-		}).afterDelay(waitMs).send();
+		}).afterDelay(kSmallDelayMs).send();
 	}
 }
 
@@ -1671,7 +1670,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
 			_channelAmInRequests.remove(channel);
 		}).send();
 
-		_channelAmInRequests.insert(channel, requestId);
+		_channelAmInRequests.emplace(channel, requestId);
 	}
 }
 
@@ -1690,44 +1689,48 @@ void ApiWrap::leaveChannel(not_null<ChannelData*> channel) {
 			_channelAmInRequests.remove(channel);
 		}).send();
 
-		_channelAmInRequests.insert(channel, requestId);
+		_channelAmInRequests.emplace(channel, requestId);
 	}
 }
 
 void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
-	const auto key = [&] {
-		switch (peer.type()) {
-		case mtpc_inputNotifyUsers: return peerFromUser(0);
-		case mtpc_inputNotifyChats: return peerFromChat(0);
-		case mtpc_inputNotifyBroadcasts: return peerFromChannel(0);
-		case mtpc_inputNotifyPeer: {
-			const auto &inner = peer.c_inputNotifyPeer().vpeer();
-			switch (inner.type()) {
-			case mtpc_inputPeerSelf:
-				return _session->userPeerId();
-			case mtpc_inputPeerEmpty:
-				return PeerId(0);
-			case mtpc_inputPeerChannel:
-				return peerFromChannel(
-					inner.c_inputPeerChannel().vchannel_id());
-			case mtpc_inputPeerChat:
-				return peerFromChat(inner.c_inputPeerChat().vchat_id());
-			case mtpc_inputPeerUser:
-				return peerFromUser(inner.c_inputPeerUser().vuser_id());
-			}
+	const auto peerFromInput = [&](const MTPInputPeer &inputPeer) {
+		return inputPeer.match([&](const MTPDinputPeerSelf &) {
+			return _session->userPeerId();
+		}, [](const MTPDinputPeerEmpty &) {
+			return PeerId(0);
+		}, [](const MTPDinputPeerChannel &data) {
+			return peerFromChannel(data.vchannel_id());
+		}, [](const MTPDinputPeerChat &data) {
+			return peerFromChat(data.vchat_id());
+		}, [](const MTPDinputPeerUser &data) {
+			return peerFromUser(data.vuser_id());
+		}, [](const auto &) -> PeerId {
 			Unexpected("Type in ApiRequest::requestNotifySettings peer.");
-		} break;
-		}
-		Unexpected("Type in ApiRequest::requestNotifySettings.");
-	}();
-	if (_notifySettingRequests.find(key) != end(_notifySettingRequests)) {
+		});
+	};
+	const auto key = peer.match([](const MTPDinputNotifyUsers &) {
+		return NotifySettingsKey{ peerFromUser(1) };
+	}, [](const MTPDinputNotifyChats &) {
+		return NotifySettingsKey{ peerFromChat(1) };
+	}, [](const MTPDinputNotifyBroadcasts &) {
+		return NotifySettingsKey{ peerFromChannel(1) };
+	}, [&](const MTPDinputNotifyPeer &data) {
+		return NotifySettingsKey{ peerFromInput(data.vpeer()) };
+	}, [&](const MTPDinputNotifyForumTopic &data) {
+		return NotifySettingsKey{
+			peerFromInput(data.vpeer()),
+			data.vtop_msg_id().v,
+		};
+	});
+	if (_notifySettingRequests.contains(key)) {
 		return;
 	}
 	const auto requestId = request(MTPaccount_GetNotifySettings(
 		peer
 	)).done([=](const MTPPeerNotifySettings &result) {
 		applyNotifySettings(peer, result);
-		_notifySettingRequests.erase(key);
+		_notifySettingRequests.remove(key);
 	}).fail([=] {
 		applyNotifySettings(
 			peer,
@@ -1741,34 +1744,50 @@ void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
 				MTPNotificationSound()));
 		_notifySettingRequests.erase(key);
 	}).send();
-
 	_notifySettingRequests.emplace(key, requestId);
 }
 
-void ApiWrap::updateNotifySettingsDelayed(not_null<const PeerData*> peer) {
-	_updateNotifySettingsPeers.emplace(peer);
-	_updateNotifySettingsTimer.callOnce(kNotifySettingSaveTimeout);
+void ApiWrap::updateNotifySettingsDelayed(
+		not_null<const Data::ForumTopic*> topic) {
+	if (_updateNotifyTopics.emplace(topic).second) {
+		topic->destroyed(
+		) | rpl::start_with_next([=] {
+			_updateNotifyTopics.remove(topic);
+		}, _updateNotifyQueueLifetime);
+		_updateNotifyTimer.callOnce(kNotifySettingSaveTimeout);
+	}
 }
 
-void ApiWrap::updateDefaultNotifySettingsDelayed(Data::DefaultNotify type) {
-	_updateNotifySettingsDefaults.emplace(type);
-	_updateNotifySettingsTimer.callOnce(kNotifySettingSaveTimeout);
+void ApiWrap::updateNotifySettingsDelayed(not_null<const PeerData*> peer) {
+	if (_updateNotifyPeers.emplace(peer).second) {
+		_updateNotifyTimer.callOnce(kNotifySettingSaveTimeout);
+	}
+}
+
+void ApiWrap::updateNotifySettingsDelayed(Data::DefaultNotify type) {
+	if (_updateNotifyDefaults.emplace(type).second) {
+		_updateNotifyTimer.callOnce(kNotifySettingSaveTimeout);
+	}
 }
 
 void ApiWrap::sendNotifySettingsUpdates() {
-	while (!_updateNotifySettingsPeers.empty()) {
-		const auto peer = *_updateNotifySettingsPeers.begin();
-		_updateNotifySettingsPeers.erase(_updateNotifySettingsPeers.begin());
+	_updateNotifyQueueLifetime.destroy();
+	for (const auto topic : base::take(_updateNotifyTopics)) {
+		request(MTPaccount_UpdateNotifySettings(
+			MTP_inputNotifyForumTopic(
+				topic->channel()->input,
+				MTP_int(topic->rootId())),
+			topic->notify().serialize()
+		)).afterDelay(kSmallDelayMs).send();
+	}
+	for (const auto peer : base::take(_updateNotifyPeers)) {
 		request(MTPaccount_UpdateNotifySettings(
 			MTP_inputNotifyPeer(peer->input),
-			peer->notifySerialize()
-		)).afterDelay(_updateNotifySettingsPeers.empty() ? 0 : 10).send();
+			peer->notify().serialize()
+		)).afterDelay(kSmallDelayMs).send();
 	}
 	const auto &settings = session().data().notifySettings();
-	while (!_updateNotifySettingsDefaults.empty()) {
-		const auto type = *_updateNotifySettingsDefaults.begin();
-		_updateNotifySettingsDefaults.erase(
-			_updateNotifySettingsDefaults.begin());
+	for (const auto type : base::take(_updateNotifyDefaults)) {
 		const auto input = [&] {
 			switch (type) {
 			case Data::DefaultNotify::User: return MTP_inputNotifyUsers();
@@ -1781,8 +1800,9 @@ void ApiWrap::sendNotifySettingsUpdates() {
 		request(MTPaccount_UpdateNotifySettings(
 			input,
 			settings.defaultSettings(type).serialize()
-		)).afterDelay(_updateNotifySettingsDefaults.empty() ? 0 : 10).send();
+		)).afterDelay(kSmallDelayMs).send();
 	}
+	session().mtp().sendAnything();
 }
 
 void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
@@ -2147,7 +2167,9 @@ void ApiWrap::applyNotifySettings(
 	Core::App().notifications().checkDelayed();
 }
 
-void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
+void ApiWrap::gotStickerSet(
+		uint64 setId,
+		const MTPmessages_StickerSet &result) {
 	_stickerSetRequests.remove(setId);
 	result.match([&](const MTPDmessages_stickerSet &data) {
 		_session->data().stickers().feedSetFull(data);
@@ -2156,18 +2178,20 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
 	});
 }
 
-void ApiWrap::requestWebPageDelayed(WebPageData *page) {
-	if (page->pendingTill <= 0) return;
-	_webPagesPending.insert(page, 0);
+void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
+	if (page->pendingTill <= 0) {
+		return;
+	}
+	_webPagesPending.emplace(page, 0);
 	auto left = (page->pendingTill - base::unixtime::now()) * 1000;
 	if (!_webPagesTimer.isActive() || left <= _webPagesTimer.remainingTime()) {
 		_webPagesTimer.callOnce((left < 0 ? 0 : left) + 1);
 	}
 }
 
-void ApiWrap::clearWebPageRequest(WebPageData *page) {
+void ApiWrap::clearWebPageRequest(not_null<WebPageData*> page) {
 	_webPagesPending.remove(page);
-	if (_webPagesPending.isEmpty() && _webPagesTimer.isActive()) {
+	if (_webPagesPending.empty() && _webPagesTimer.isActive()) {
 		_webPagesTimer.cancel();
 	}
 }
@@ -2185,11 +2209,12 @@ void ApiWrap::resolveWebPages() {
 
 	ids.reserve(_webPagesPending.size());
 	int32 t = base::unixtime::now(), m = INT_MAX;
-	for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) {
-		if (i.value() > 0) continue;
-		if (i.key()->pendingTill <= t) {
-			const auto item = _session->data().findWebPageItem(i.key());
-			if (item) {
+	for (auto &[page, requestId] : _webPagesPending) {
+		if (requestId > 0) {
+			continue;
+		}
+		if (page->pendingTill <= t) {
+			if (const auto item = _session->data().findWebPageItem(page)) {
 				if (const auto channel = item->history()->peer->asChannel()) {
 					auto channelMap = idsByChannel.find(channel);
 					if (channelMap == idsByChannel.cend()) {
@@ -2204,14 +2229,14 @@ void ApiWrap::resolveWebPages() {
 						channelMap->second.second.push_back(
 							MTP_inputMessageID(MTP_int(item->id)));
 					}
-					i.value() = -channelMap->second.first - 2;
+					requestId = -channelMap->second.first - 2;
 				} else {
 					ids.push_back(MTP_inputMessageID(MTP_int(item->id)));
-					i.value() = -1;
+					requestId = -1;
 				}
 			}
 		} else {
-			m = qMin(m, i.key()->pendingTill - t);
+			m = std::min(m, page->pendingTill - t);
 		}
 	}
 
@@ -2237,9 +2262,10 @@ void ApiWrap::resolveWebPages() {
 		}).afterDelay(kSmallDelayMs).send();
 	}
 	if (requestId || !reqsByIndex.isEmpty()) {
-		for (auto &pendingRequestId : _webPagesPending) {
-			if (pendingRequestId > 0) continue;
-			if (pendingRequestId < 0) {
+		for (auto &[page, pendingRequestId] : _webPagesPending) {
+			if (pendingRequestId > 0) {
+				continue;
+			} else if (pendingRequestId < 0) {
 				if (pendingRequestId == -1) {
 					pendingRequestId = requestId;
 				} else {
@@ -2248,9 +2274,8 @@ void ApiWrap::resolveWebPages() {
 			}
 		}
 	}
-
 	if (m < INT_MAX) {
-		_webPagesTimer.callOnce(m * 1000);
+		_webPagesTimer.callOnce(std::min(m, 86400) * crl::time(1000));
 	}
 }
 
@@ -2441,10 +2466,10 @@ void ApiWrap::refreshFileReference(
 void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req) {
 	WebPageData::ApplyChanges(_session, channel, result);
 	for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
-		if (i.value() == req) {
-			if (i.key()->pendingTill > 0) {
-				i.key()->pendingTill = -1;
-				_session->data().notifyWebPageUpdateDelayed(i.key());
+		if (i->second == req) {
+			if (i->first->pendingTill > 0) {
+				i->first->pendingTill = -1;
+				_session->data().notifyWebPageUpdateDelayed(i->first);
 			}
 			i = _webPagesPending.erase(i);
 		} else {
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 2585b3056..0ee848577 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -32,6 +32,7 @@ class WallPaper;
 struct ResolvedForwardDraft;
 enum class DefaultNotify;
 enum class StickersType : uchar;
+class ForumTopic;
 } // namespace Data
 
 namespace InlineBots {
@@ -219,8 +220,8 @@ public:
 		not_null<ChannelData*> channel,
 		not_null<PeerData*> from);
 
-	void requestWebPageDelayed(WebPageData *page);
-	void clearWebPageRequest(WebPageData *page);
+	void requestWebPageDelayed(not_null<WebPageData*> page);
+	void clearWebPageRequest(not_null<WebPageData*> page);
 	void clearWebPageRequests();
 
 	void scheduleStickerSetRequest(uint64 setId, uint64 access);
@@ -244,8 +245,10 @@ public:
 	void leaveChannel(not_null<ChannelData*> channel);
 
 	void requestNotifySettings(const MTPInputNotifyPeer &peer);
+	void updateNotifySettingsDelayed(
+		not_null<const Data::ForumTopic*> topic);
 	void updateNotifySettingsDelayed(not_null<const PeerData*> peer);
-	void updateDefaultNotifySettingsDelayed(Data::DefaultNotify type);
+	void updateNotifySettingsDelayed(Data::DefaultNotify type);
 	void saveDraftToCloudDelayed(not_null<History*> history);
 
 	static int OnlineTillFromStatus(
@@ -523,7 +526,7 @@ private:
 		not_null<ChannelData*> channel);
 	void migrateFail(not_null<PeerData*> peer, const QString &error);
 
-	not_null<Main::Session*> _session;
+	const not_null<Main::Session*> _session;
 
 	base::flat_map<QString, int> _modifyRequests;
 
@@ -541,13 +544,29 @@ private:
 		not_null<History*>,
 		std::pair<mtpRequestId,Fn<void()>>> _historyArchivedRequests;
 
-	QMap<WebPageData*, mtpRequestId> _webPagesPending;
+	base::flat_map<not_null<WebPageData*>, mtpRequestId> _webPagesPending;
 	base::Timer _webPagesTimer;
 
-	QMap<uint64, QPair<uint64, mtpRequestId> > _stickerSetRequests;
+	struct StickerSetRequest {
+		uint64 accessHash = 0;
+		mtpRequestId id = 0;
+	};
+	base::flat_map<uint64, StickerSetRequest> _stickerSetRequests;
+
+	base::flat_map<
+		not_null<ChannelData*>,
+		mtpRequestId> _channelAmInRequests;
+
+	struct NotifySettingsKey {
+		PeerId peerId = 0;
+		MsgId topicRootId = 0;
+
+		friend inline constexpr auto operator<=>(
+			NotifySettingsKey,
+			NotifySettingsKey) = default;
+	};
+	base::flat_map<NotifySettingsKey, mtpRequestId> _notifySettingRequests;
 
-	QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
-	base::flat_map<PeerId, mtpRequestId> _notifySettingRequests;
 	base::flat_map<not_null<History*>, mtpRequestId> _draftsSaveRequestIds;
 	base::Timer _draftsSaveTimer;
 
@@ -610,9 +629,11 @@ private:
 	TimeId _topPromotionNextRequestTime = TimeId(0);
 	base::Timer _topPromotionTimer;
 
-	base::flat_set<not_null<const PeerData*>> _updateNotifySettingsPeers;
-	base::flat_set<Data::DefaultNotify> _updateNotifySettingsDefaults;
-	base::Timer _updateNotifySettingsTimer;
+	base::flat_set<not_null<const Data::ForumTopic*>> _updateNotifyTopics;
+	base::flat_set<not_null<const PeerData*>> _updateNotifyPeers;
+	base::flat_set<Data::DefaultNotify> _updateNotifyDefaults;
+	base::Timer _updateNotifyTimer;
+	rpl::lifetime _updateNotifyQueueLifetime;
 
 	std::map<
 		Data::FileOrigin,
diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h
index 5f5334057..51c7085fc 100644
--- a/Telegram/SourceFiles/data/data_changes.h
+++ b/Telegram/SourceFiles/data/data_changes.h
@@ -148,8 +148,9 @@ struct TopicUpdate {
 		UnreadView = (1U << 1),
 		UnreadMentions = (1U << 2),
 		UnreadReactions = (1U << 3),
+		Notifications = (1U << 4),
 
-		LastUsedBit = (1U << 3),
+		LastUsedBit = (1U << 4),
 	};
 	using Flags = base::flags<Flag>;
 	friend inline constexpr auto is_flag_type(Flag) { return true; }
diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp
index d77b47c02..6a433db9a 100644
--- a/Telegram/SourceFiles/data/data_chat_filters.cpp
+++ b/Telegram/SourceFiles/data/data_chat_filters.cpp
@@ -207,7 +207,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
 	return false
 		|| ((_flags & flag)
 			&& (!(_flags & Flag::NoMuted)
-				|| !history->mute()
+				|| !history->muted()
 				|| (history->unreadMentions().has()
 					&& history->folderKnown()
 					&& !history->folder()))
diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp
index 069505c48..b8469436b 100644
--- a/Telegram/SourceFiles/data/data_forum_topic.cpp
+++ b/Telegram/SourceFiles/data/data_forum_topic.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_forum.h"
 #include "data/data_histories.h"
 #include "data/data_replies_list.h"
+#include "data/notify/data_notify_settings.h"
 #include "data/data_session.h"
 #include "data/stickers/data_custom_emoji.h"
 #include "dialogs/dialogs_main_list.h"
@@ -141,7 +142,8 @@ ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
 , _forum(history->peer->forum())
 , _list(_forum->topicsList())
 , _replies(std::make_shared<RepliesList>(history, rootId))
-, _rootId(rootId) {
+, _rootId(rootId)
+, _flags(owner().notifySettings().isMuted(this) ? Flag::Muted : Flag(0)) {
 	_replies->unreadCountValue(
 	) | rpl::combine_previous(
 	) | rpl::filter([=] {
@@ -487,6 +489,32 @@ int ForumTopic::unreadCountForBadge() const {
 	return (!result && unreadMark()) ? 1 : result;
 }
 
+bool ForumTopic::muted() const {
+	return (_flags & Flag::Muted);
+}
+
+bool ForumTopic::changeMuted(bool muted) {
+	if (this->muted() == muted) {
+		return false;
+	}
+	const auto refresher = gsl::finally([&] {
+		if (inChatList()) {
+			updateChatListEntry();
+		}
+		session().changes().topicUpdated(
+			this,
+			Data::TopicUpdate::Flag::Notifications);
+	});
+	const auto notify = (unreadCountForBadge() > 0);
+	const auto notifier = unreadStateChangeNotifier(notify);
+	if (muted) {
+		_flags |= Flag::Muted;
+	} else {
+		_flags &= ~Flag::Muted;
+	}
+	return true;
+}
+
 bool ForumTopic::unreadCountKnown() const {
 	return _replies->unreadCountKnown();
 }
@@ -531,7 +559,7 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
 		bool known) const {
 	auto result = Dialogs::UnreadState();
 	const auto mark = !count && unreadMark();
-	const auto muted = history()->mute();
+	const auto muted = this->muted();
 	result.messages = count;
 	result.messagesMuted = muted ? count : 0;
 	result.chats = count ? 1 : 0;
@@ -547,7 +575,7 @@ bool ForumTopic::chatListUnreadMark() const {
 }
 
 bool ForumTopic::chatListMutedBadge() const {
-	return history()->mute();
+	return muted();
 }
 
 HistoryItem *ForumTopic::chatListMessage() const {
diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h
index 61c0bdc16..dd3ae7760 100644
--- a/Telegram/SourceFiles/data/data_forum_topic.h
+++ b/Telegram/SourceFiles/data/data_forum_topic.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "dialogs/dialogs_entry.h"
 #include "dialogs/ui/dialogs_message_view.h"
+#include "data/notify/data_peer_notify_settings.h"
 #include "base/flags.h"
 
 class ChannelData;
@@ -95,6 +96,13 @@ public:
 	void applyItemAdded(not_null<HistoryItem*> item);
 	void applyItemRemoved(MsgId id);
 
+	[[nodiscard]] Data::PeerNotifySettings &notify() {
+		return _notify;
+	}
+	[[nodiscard]] const Data::PeerNotifySettings &notify() const {
+		return _notify;
+	}
+
 	void loadUserpic() override;
 	void paintUserpic(
 		Painter &p,
@@ -105,6 +113,8 @@ public:
 	[[nodiscard]] bool unreadCountKnown() const;
 
 	[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
+	[[nodiscard]] bool muted() const;
+	bool changeMuted(bool muted);
 
 	void setUnreadMark(bool unread);
 	[[nodiscard]] bool unreadMark() const;
@@ -114,7 +124,8 @@ public:
 
 private:
 	enum class Flag : uchar {
-		UnreadMark = 0x01,
+		UnreadMark = (1 << 0),
+		Muted = (1 << 1),
 	};
 	friend inline constexpr bool is_flag_type(Flag) { return true; }
 
@@ -137,6 +148,8 @@ private:
 	std::shared_ptr<RepliesList> _replies;
 	MsgId _rootId = 0;
 
+	Data::PeerNotifySettings _notify;
+
 	QString _title;
 	DocumentId _iconId = 0;
 	base::flat_set<QString> _titleWords;
@@ -151,7 +164,7 @@ private:
 	std::optional<HistoryItem*> _lastServerMessage;
 	std::optional<HistoryItem*> _chatListMessage;
 	base::flat_set<FullMsgId> _requestedGroups;
-	base::flags<Flag> _flags;
+	base::flags<Flag> _flags; // Initializer accesses _notify, be careful.
 
 	rpl::lifetime _lifetime;
 
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index f6af6bfc2..329ec2c3f 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -202,29 +202,11 @@ public:
 		not_null<const HistoryItem*> item) const;
 	[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;
 
-	[[nodiscard]] std::optional<TimeId> notifyMuteUntil() const {
-		return _notify.muteUntil();
+	[[nodiscard]] Data::PeerNotifySettings &notify() {
+		return _notify;
 	}
-	bool notifyChange(const MTPPeerNotifySettings &settings) {
-		return _notify.change(settings);
-	}
-	bool notifyChange(
-			Data::MuteValue muteForSeconds,
-			std::optional<bool> silentPosts,
-			std::optional<Data::NotifySound> sound) {
-		return _notify.change(muteForSeconds, silentPosts, sound);
-	}
-	[[nodiscard]] bool notifySettingsUnknown() const {
-		return _notify.settingsUnknown();
-	}
-	[[nodiscard]] std::optional<bool> notifySilentPosts() const {
-		return _notify.silentPosts();
-	}
-	[[nodiscard]] std::optional<Data::NotifySound> notifySound() const {
-		return _notify.sound();
-	}
-	[[nodiscard]] MTPinputPeerNotifySettings notifySerialize() const {
-		return _notify.serialize();
+	[[nodiscard]] const Data::PeerNotifySettings &notify() const {
+		return _notify;
 	}
 
 	[[nodiscard]] bool canWrite() const;
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index ec51889d1..d6fcce1e7 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -3892,7 +3892,9 @@ void Session::removeChatListEntry(Dialogs::Key key) {
 			_contactsNoChatsList.addByName(key);
 		}
 	}
-	if (const auto history = key.history()) {
+	if (const auto topic = key.topic()) {
+		Core::App().notifications().clearFromTopic(topic);
+	} else if (const auto history = key.history()) {
 		Core::App().notifications().clearFromHistory(history);
 	}
 }
diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
index d32550340..ea4982c77 100644
--- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
+++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_file_origin.h"
 #include "data/data_peer.h"
+#include "data/data_forum_topic.h"
 #include "data/data_session.h"
 #include "data/data_user.h"
 #include "main/main_session.h"
@@ -25,11 +26,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/notifications_manager.h"
 
 namespace Data {
-
 namespace {
 
 constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
 
+[[nodiscard]] bool MutedFromUntil(TimeId until, crl::time *changesIn) {
+	const auto now = base::unixtime::now();
+	const auto result = (until > now) ? (until - now) : 0;
+	if (changesIn) {
+		*changesIn = (result > 0)
+			? std::min(result * crl::time(1000), kMaxNotifyCheckDelay)
+			: kMaxNotifyCheckDelay;
+	}
+	return (result > 0);
+}
+
 } // namespace
 
 NotifySettings::NotifySettings(not_null<Session*> owner)
@@ -38,7 +49,7 @@ NotifySettings::NotifySettings(not_null<Session*> owner)
 }
 
 void NotifySettings::request(not_null<PeerData*> peer) {
-	if (peer->notifySettingsUnknown()) {
+	if (peer->notify().settingsUnknown()) {
 		peer->session().api().requestNotifySettings(
 			MTP_inputNotifyPeer(peer->input));
 	}
@@ -51,6 +62,16 @@ void NotifySettings::request(not_null<PeerData*> peer) {
 	}
 }
 
+void NotifySettings::request(not_null<Data::ForumTopic*> topic) {
+	if (topic->notify().settingsUnknown()) {
+		topic->session().api().requestNotifySettings(
+			MTP_inputNotifyForumTopic(
+				topic->channel()->input,
+				MTP_int(topic->rootId())));
+	}
+	request(topic->channel());
+}
+
 void NotifySettings::apply(
 		const MTPNotifyPeer &notifyPeer,
 		const MTPPeerNotifySettings &settings) {
@@ -66,7 +87,7 @@ void NotifySettings::apply(
 	case mtpc_notifyPeer: {
 		const auto &data = notifyPeer.c_notifyPeer();
 		if (const auto peer = _owner->peerLoaded(peerFromMTP(data.vpeer()))) {
-			if (peer->notifyChange(settings)) {
+			if (peer->notify().change(settings)) {
 				updateLocal(peer);
 			}
 		}
@@ -74,12 +95,37 @@ void NotifySettings::apply(
 	}
 }
 
+void NotifySettings::update(
+		not_null<Data::ForumTopic*> topic,
+		Data::MuteValue muteForSeconds,
+		std::optional<NotifySound> sound) {
+	if (topic->notify().change(muteForSeconds, std::nullopt, sound)) {
+		updateLocal(topic);
+		topic->session().api().updateNotifySettingsDelayed(topic);
+	}
+}
+
+void NotifySettings::resetToDefault(not_null<Data::ForumTopic*> topic) {
+	const auto empty = MTP_peerNotifySettings(
+		MTP_flags(0),
+		MTPBool(),
+		MTPBool(),
+		MTPint(),
+		MTPNotificationSound(),
+		MTPNotificationSound(),
+		MTPNotificationSound());
+	if (topic->notify().change(empty)) {
+		updateLocal(topic);
+		topic->session().api().updateNotifySettingsDelayed(topic);
+	}
+}
+
 void NotifySettings::update(
 		not_null<PeerData*> peer,
 		Data::MuteValue muteForSeconds,
 		std::optional<bool> silentPosts,
 		std::optional<NotifySound> sound) {
-	if (peer->notifyChange(muteForSeconds, silentPosts, sound)) {
+	if (peer->notify().change(muteForSeconds, silentPosts, sound)) {
 		updateLocal(peer);
 		peer->session().api().updateNotifySettingsDelayed(peer);
 	}
@@ -94,7 +140,7 @@ void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
 		MTPNotificationSound(),
 		MTPNotificationSound(),
 		MTPNotificationSound());
-	if (peer->notifyChange(empty)) {
+	if (peer->notify().change(empty)) {
 		updateLocal(peer);
 		peer->session().api().updateNotifySettingsDelayed(peer);
 	}
@@ -136,15 +182,34 @@ void NotifySettings::defaultUpdate(
 	auto &settings = defaultValue(type).settings;
 	if (settings.change(muteForSeconds, silentPosts, sound)) {
 		updateLocal(type);
-		_owner->session().api().updateDefaultNotifySettingsDelayed(type);
+		_owner->session().api().updateNotifySettingsDelayed(type);
 	}
 }
 
+void NotifySettings::updateLocal(not_null<Data::ForumTopic*> topic) {
+	auto changesIn = crl::time(0);
+	const auto muted = isMuted(topic, &changesIn);
+	topic->changeMuted(muted);
+	if (muted) {
+		auto &lifetime = _mutedTopics.emplace(
+			topic,
+			rpl::lifetime()).first->second;
+		topic->destroyed() | rpl::start_with_next([=] {
+			_mutedTopics.erase(topic);
+		}, lifetime);
+		unmuteByFinishedDelayed(changesIn);
+		Core::App().notifications().clearIncomingFromTopic(topic);
+	} else {
+		_mutedTopics.erase(topic);
+	}
+	cacheSound(topic->notify().sound());
+}
+
 void NotifySettings::updateLocal(not_null<PeerData*> peer) {
 	const auto history = _owner->historyLoaded(peer->id);
 	auto changesIn = crl::time(0);
 	const auto muted = isMuted(peer, &changesIn);
-	if (history && history->changeMute(muted)) {
+	if (history && history->changeMuted(muted)) {
 		// Notification already sent.
 	} else {
 		peer->session().changes().peerUpdated(
@@ -161,7 +226,7 @@ void NotifySettings::updateLocal(not_null<PeerData*> peer) {
 	} else {
 		_mutedPeers.erase(peer);
 	}
-	cacheSound(peer->notifySound());
+	cacheSound(peer->notify().sound());
 }
 
 void NotifySettings::cacheSound(DocumentId id) {
@@ -206,10 +271,11 @@ void NotifySettings::updateLocal(DefaultNotify type) {
 	const auto goodForUpdate = [&](
 			not_null<const PeerData*> peer,
 			const PeerNotifySettings &settings) {
-		return !peer->notifySettingsUnknown()
-			&& ((!peer->notifyMuteUntil() && settings.muteUntil())
-				|| (!peer->notifySilentPosts() && settings.silentPosts())
-				|| (!peer->notifySound() && settings.sound()));
+		auto &peers = peer->notify();
+		return !peers.settingsUnknown()
+			&& ((!peers.muteUntil() && settings.muteUntil())
+				|| (!peers.silentPosts() && settings.silentPosts())
+				|| (!peers.sound() && settings.sound()));
 	};
 
 	const auto callback = [&](not_null<PeerData*> peer) {
@@ -252,7 +318,7 @@ void NotifySettings::unmuteByFinished() {
 		const auto muted = isMuted(*i, &changesIn);
 		if (muted) {
 			if (history) {
-				history->changeMute(true);
+				history->changeMuted(true);
 			}
 			if (!changesInMin || changesInMin > changesIn) {
 				changesInMin = changesIn;
@@ -260,35 +326,71 @@ void NotifySettings::unmuteByFinished() {
 			++i;
 		} else {
 			if (history) {
-				history->changeMute(false);
+				history->changeMuted(false);
 			}
 			i = _mutedPeers.erase(i);
 		}
 	}
+	for (auto i = begin(_mutedTopics); i != end(_mutedTopics);) {
+		auto changesIn = crl::time(0);
+		const auto topic = i->first;
+		const auto muted = isMuted(topic, &changesIn);
+		if (muted) {
+			topic->changeMuted(true);
+			if (!changesInMin || changesInMin > changesIn) {
+				changesInMin = changesIn;
+			}
+			++i;
+		} else {
+			topic->changeMuted(false);
+			i = _mutedTopics.erase(i);
+		}
+	}
 	if (changesInMin) {
 		unmuteByFinishedDelayed(changesInMin);
 	}
 }
 
+bool NotifySettings::isMuted(
+		not_null<const Data::ForumTopic*> topic,
+		crl::time *changesIn) const {
+	const auto until = topic->notify().muteUntil();
+	return until
+		? MutedFromUntil(*until, changesIn)
+		: isMuted(topic->channel(), changesIn);
+}
+
+bool NotifySettings::isMuted(not_null<const Data::ForumTopic*> topic) const {
+	return isMuted(topic, nullptr);
+}
+
+NotifySound NotifySettings::sound(
+		not_null<const Data::ForumTopic*> topic) const {
+	const auto sound = topic->notify().sound();
+	return sound ? *sound : this->sound(topic->channel());
+}
+
+bool NotifySettings::muteUnknown(
+		not_null<const Data::ForumTopic*> topic) const {
+	return topic->notify().settingsUnknown()
+		|| (!topic->notify().muteUntil().has_value()
+			&& muteUnknown(topic->channel()));
+}
+
+bool NotifySettings::soundUnknown(
+		not_null<const Data::ForumTopic*> topic) const {
+	return topic->notify().settingsUnknown()
+		|| (!topic->notify().sound().has_value()
+			&& soundUnknown(topic->channel()));
+}
+
 bool NotifySettings::isMuted(
 		not_null<const PeerData*> peer,
 		crl::time *changesIn) const {
-	const auto resultFromUntil = [&](TimeId until) {
-		const auto now = base::unixtime::now();
-		const auto result = (until > now) ? (until - now) : 0;
-		if (changesIn) {
-			*changesIn = (result > 0)
-				? std::min(result * crl::time(1000), kMaxNotifyCheckDelay)
-				: kMaxNotifyCheckDelay;
-		}
-		return (result > 0);
-	};
-	if (const auto until = peer->notifyMuteUntil()) {
-		return resultFromUntil(*until);
-	}
-	const auto &settings = defaultSettings(peer);
-	if (const auto until = settings.muteUntil()) {
-		return resultFromUntil(*until);
+	if (const auto until = peer->notify().muteUntil()) {
+		return MutedFromUntil(*until, changesIn);
+	} else if (const auto until = defaultSettings(peer).muteUntil()) {
+		return MutedFromUntil(*until, changesIn);
 	}
 	return true;
 }
@@ -298,54 +400,41 @@ bool NotifySettings::isMuted(not_null<const PeerData*> peer) const {
 }
 
 bool NotifySettings::silentPosts(not_null<const PeerData*> peer) const {
-	if (const auto silent = peer->notifySilentPosts()) {
+	if (const auto silent = peer->notify().silentPosts()) {
 		return *silent;
-	}
-	const auto &settings = defaultSettings(peer);
-	if (const auto silent = settings.silentPosts()) {
+	} else if (const auto silent = defaultSettings(peer).silentPosts()) {
 		return *silent;
 	}
 	return false;
 }
 
 NotifySound NotifySettings::sound(not_null<const PeerData*> peer) const {
-	if (const auto sound = peer->notifySound()) {
+	if (const auto sound = peer->notify().sound()) {
 		return *sound;
-	}
-	const auto &settings = defaultSettings(peer);
-	if (const auto sound = settings.sound()) {
+	} else if (const auto sound = defaultSettings(peer).sound()) {
 		return *sound;
 	}
 	return {};
 }
 
 bool NotifySettings::muteUnknown(not_null<const PeerData*> peer) const {
-	if (peer->notifySettingsUnknown()) {
-		return true;
-	} else if (const auto nonDefault = peer->notifyMuteUntil()) {
-		return false;
-	}
-	return defaultSettings(peer).settingsUnknown();
+	return peer->notify().settingsUnknown()
+		|| (!peer->notify().muteUntil().has_value()
+			&& defaultSettings(peer).settingsUnknown());
 }
 
 bool NotifySettings::silentPostsUnknown(
 		not_null<const PeerData*> peer) const {
-	if (peer->notifySettingsUnknown()) {
-		return true;
-	} else if (const auto nonDefault = peer->notifySilentPosts()) {
-		return false;
-	}
-	return defaultSettings(peer).settingsUnknown();
+	return peer->notify().settingsUnknown()
+		|| (!peer->notify().silentPosts().has_value()
+			&& defaultSettings(peer).settingsUnknown());
 }
 
 bool NotifySettings::soundUnknown(
 		not_null<const PeerData*> peer) const {
-	if (peer->notifySettingsUnknown()) {
-		return true;
-	} else if (const auto nonDefault = peer->notifySound()) {
-		return false;
-	}
-	return defaultSettings(peer).settingsUnknown();
+	return peer->notify().settingsUnknown()
+		|| (!peer->notify().sound().has_value()
+			&& defaultSettings(peer).settingsUnknown());
 }
 
 bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
@@ -354,6 +443,11 @@ bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
 		|| soundUnknown(peer);
 }
 
+bool NotifySettings::settingsUnknown(
+		not_null<const Data::ForumTopic*> topic) const {
+	return muteUnknown(topic) || soundUnknown(topic);
+}
+
 rpl::producer<> NotifySettings::defaultUpdates(DefaultNotify type) const {
 	return defaultValue(type).updates.events();
 }
diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h
index a1a3dfd17..b07dd2f1e 100644
--- a/Telegram/SourceFiles/data/notify/data_notify_settings.h
+++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h
@@ -17,6 +17,7 @@ namespace Data {
 
 class DocumentMedia;
 class Session;
+class ForumTopic;
 
 enum class DefaultNotify {
 	User,
@@ -29,9 +30,15 @@ public:
 	NotifySettings(not_null<Session*> owner);
 
 	void request(not_null<PeerData*> peer);
+	void request(not_null<Data::ForumTopic*> topic);
 	void apply(
 		const MTPNotifyPeer &notifyPeer,
 		const MTPPeerNotifySettings &settings);
+	void update(
+		not_null<Data::ForumTopic*> topic,
+		Data::MuteValue muteForSeconds,
+		std::optional<NotifySound> sound = std::nullopt);
+	void resetToDefault(not_null<Data::ForumTopic*> topic);
 	void update(
 		not_null<PeerData*> peer,
 		Data::MuteValue muteForSeconds,
@@ -57,6 +64,15 @@ public:
 		std::optional<bool> silentPosts = std::nullopt,
 		std::optional<NotifySound> sound = std::nullopt);
 
+	[[nodiscard]] bool isMuted(
+		not_null<const Data::ForumTopic*> topic) const;
+	[[nodiscard]] NotifySound sound(
+		not_null<const Data::ForumTopic*> topic) const;
+	[[nodiscard]] bool muteUnknown(
+		not_null<const Data::ForumTopic*> topic) const;
+	[[nodiscard]] bool soundUnknown(
+		not_null<const Data::ForumTopic*> topic) const;
+
 	[[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const;
 	[[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const;
 	[[nodiscard]] NotifySound sound(not_null<const PeerData*> peer) const;
@@ -73,6 +89,9 @@ private:
 
 	void cacheSound(const std::optional<NotifySound> &sound);
 
+	[[nodiscard]] bool isMuted(
+		not_null<const Data::ForumTopic*> peer,
+		crl::time *changesIn) const;
 	[[nodiscard]] bool isMuted(
 		not_null<const PeerData*> peer,
 		crl::time *changesIn) const;
@@ -82,9 +101,12 @@ private:
 	[[nodiscard]] const PeerNotifySettings &defaultSettings(
 		not_null<const PeerData*> peer) const;
 	[[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const;
+	[[nodiscard]] bool settingsUnknown(
+		not_null<const Data::ForumTopic*> topic) const;
 
 	void unmuteByFinished();
 	void unmuteByFinishedDelayed(crl::time delay);
+	void updateLocal(not_null<Data::ForumTopic*> topic);
 	void updateLocal(not_null<PeerData*> peer);
 	void updateLocal(DefaultNotify type);
 
@@ -92,6 +114,9 @@ private:
 
 	DefaultValue _defaultValues[3];
 	std::unordered_set<not_null<const PeerData*>> _mutedPeers;
+	std::unordered_map<
+		not_null<Data::ForumTopic*>,
+		rpl::lifetime> _mutedTopics;
 	base::Timer _unmuteByFinishedTimer;
 
 	struct {
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index b0ece2b77..13edacfb7 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -74,7 +74,7 @@ History::History(not_null<Data::Session*> owner, PeerId peerId)
 , peer(owner->peer(peerId))
 , cloudDraftTextCache(st::dialogsTextWidthMin)
 , _delegateMixin(HistoryInner::DelegateMixin())
-, _mute(owner->notifySettings().isMuted(peer))
+, _flags(owner->notifySettings().isMuted(peer) ? Flag::Muted : Flag(0))
 , _chatListNameSortKey(owner->nameSortKey(peer->name()))
 , _sendActionPainter(this) {
 	if (const auto user = peer->asUser()) {
@@ -1768,12 +1768,12 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
 	return _fakeUnreadWhileOpened;
 }
 
-bool History::mute() const {
-	return _mute;
+bool History::muted() const {
+	return (_flags & Flag::Muted);
 }
 
-bool History::changeMute(bool newMute) {
-	if (_mute == newMute) {
+bool History::changeMuted(bool muted) {
+	if (this->muted() == muted) {
 		return false;
 	}
 	const auto refresher = gsl::finally([&] {
@@ -1787,7 +1787,11 @@ bool History::changeMute(bool newMute) {
 	});
 	const auto notify = (unreadCountForBadge() > 0);
 	const auto notifier = unreadStateChangeNotifier(notify);
-	_mute = newMute;
+	if (muted) {
+		_flags |= Flag::Muted;
+	} else {
+		_flags &= ~Flag::Muted;
+	}
 	return true;
 }
 
@@ -2072,14 +2076,14 @@ bool History::chatListUnreadMark() const {
 }
 
 bool History::chatListMutedBadge() const {
-	return mute();
+	return muted();
 }
 
 Dialogs::UnreadState History::chatListUnreadState() const {
 	auto result = Dialogs::UnreadState();
 	const auto count = _unreadCount.value_or(0);
 	const auto mark = !count && _unreadMark;
-	const auto muted = mute();
+	const auto muted = this->muted();
 	result.messages = count;
 	result.messagesMuted = muted ? count : 0;
 	result.chats = count ? 1 : 0;
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index f164a421d..3fb2617fb 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -262,8 +262,8 @@ public:
 	void setFakeUnreadWhileOpened(bool enabled);
 	[[nodiscard]] bool fakeUnreadWhileOpened() const;
 	[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
-	[[nodiscard]] bool mute() const;
-	bool changeMute(bool newMute);
+	[[nodiscard]] bool muted() const;
+	bool changeMuted(bool muted);
 	void addUnreadBar();
 	void destroyUnreadBar();
 	[[nodiscard]] Element *unreadBar() const;
@@ -472,6 +472,7 @@ private:
 
 	enum class Flag : uchar {
 		HasPendingResizedItems = (1 << 0),
+		Muted = (1 << 1),
 	};
 	using Flags = base::flags<Flag>;
 	friend inline constexpr auto is_flag_type(Flag) {
@@ -588,7 +589,6 @@ private:
 	const std::unique_ptr<HistoryMainElementDelegateMixin> _delegateMixin;
 
 	Flags _flags = 0;
-	bool _mute = false;
 	int _width = 0;
 	int _height = 0;
 	Element *_unreadBarView = nullptr;
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 5f47c6f89..153c86dc9 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -2448,7 +2448,7 @@ void HistoryWidget::updateNotifyControls() {
 		return;
 	}
 
-	_muteUnmute->setText((_history->mute()
+	_muteUnmute->setText((_history->muted()
 		? tr::lng_channel_unmute(tr::now)
 		: tr::lng_channel_mute(tr::now)).toUpper());
 	if (!session().data().notifySettings().silentPostsUnknown(_peer)) {
@@ -3797,7 +3797,7 @@ void HistoryWidget::joinChannel() {
 }
 
 void HistoryWidget::toggleMuteUnmute() {
-	const auto wasMuted = !!_history->mute();
+	const auto wasMuted = _history->muted();
 	const auto muteForSeconds = Data::MuteValue{
 		.unmute = wasMuted,
 		.forever = !wasMuted,
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index 865381415..b82b6fa21 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/call_delayed.h"
 #include "base/qt/qt_key_modifiers.h"
 #include "core/file_utilities.h"
+#include "core/application.h"
 #include "main/main_session.h"
 #include "data/data_session.h"
 #include "data/data_user.h"
@@ -69,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "inline_bots/inline_bot_result.h"
 #include "lang/lang_keys.h"
 #include "facades.h"
+#include "window/notifications_manager.h"
 #include "styles/style_chat.h"
 #include "styles/style_window.h"
 #include "styles/style_info.h"
@@ -1954,6 +1956,9 @@ void RepliesWidget::readTill(not_null<HistoryItem*> item) {
 			_readRequestTimer.callOnce(0);
 		}
 	}
+	if (_topic) {
+		Core::App().notifications().clearIncomingFromTopic(_topic);
+	}
 }
 
 void RepliesWidget::listMarkReadTill(not_null<HistoryItem*> item) {
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h
index b6a20bff3..9bf999af5 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h
@@ -320,7 +320,7 @@ private:
 };
 
 
-class RepliesMemento : public Window::SectionMemento {
+class RepliesMemento final : public Window::SectionMemento {
 public:
 	RepliesMemento(
 		not_null<History*> history,
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
index b31065ab8..3ef5d9729 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
@@ -180,6 +180,23 @@ rpl::producer<const ChannelLocation*> LocationValue(
 	});
 }
 
+rpl::producer<bool> NotificationsEnabledValue(
+		not_null<Data::ForumTopic*> topic) {
+	return rpl::merge(
+		topic->session().changes().topicFlagsValue(
+			topic,
+			Data::TopicUpdate::Flag::Notifications
+		) | rpl::to_empty,
+		topic->session().changes().peerUpdates(
+			topic->channel(),
+			UpdateFlag::Notifications
+		) | rpl::to_empty,
+		topic->owner().notifySettings().defaultUpdates(topic->channel())
+	) | rpl::map([=] {
+		return !topic->owner().notifySettings().isMuted(topic);
+	}) | rpl::distinct_until_changed();
+}
+
 rpl::producer<bool> NotificationsEnabledValue(not_null<PeerData*> peer) {
 	return rpl::merge(
 		peer->session().changes().peerFlagsValue(
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h
index 9f73c4053..6a011a64e 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.h
@@ -62,6 +62,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
 	not_null<ChannelData*> channel);
 [[nodiscard]] rpl::producer<bool> NotificationsEnabledValue(
 	not_null<PeerData*> peer);
+[[nodiscard]] rpl::producer<bool> NotificationsEnabledValue(
+	not_null<Data::ForumTopic*> topic);
 [[nodiscard]] rpl::producer<bool> IsContactValue(not_null<UserData*> user);
 [[nodiscard]] rpl::producer<QString> InviteToChatButton(
 	not_null<UserData*> user);
diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
index 1eb4d8b80..91a63daa1 100644
--- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/platform/linux/base_linux_dbus_utilities.h"
 #include "core/application.h"
 #include "core/core_settings.h"
+#include "data/data_forum_topic.h"
 #include "history/history.h"
 #include "history/history_item.h"
 #include "main/main_session.h"
@@ -794,11 +795,12 @@ public:
 		DisplayOptions options);
 	void clearAll();
 	void clearFromItem(not_null<HistoryItem*> item);
+	void clearFromTopic(not_null<Data::ForumTopic*> topic);
 	void clearFromHistory(not_null<History*> history);
 	void clearFromSession(not_null<Main::Session*> session);
 	void clearNotification(NotificationId id);
 
-	bool inhibited() {
+	[[nodiscard]] bool inhibited() const {
 		return _inhibited;
 	}
 
@@ -808,7 +810,7 @@ private:
 	const not_null<Manager*> _manager;
 
 	base::flat_map<
-		FullPeer,
+		ContextId,
 		base::flat_map<MsgId, Notification>> _notifications;
 
 	Window::Notifications::CachedUserpics _cachedUserpics;
@@ -892,17 +894,22 @@ Manager::Private::Private(not_null<Manager*> manager, Type type)
 
 void Manager::Private::showNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
 		const QString &subtitle,
 		const QString &msg,
 		DisplayOptions options) {
-	const auto key = FullPeer{
+	const auto key = ContextId{
 		.sessionId = peer->session().uniqueId(),
-		.peerId = peer->id
+		.peerId = peer->id,
+		.topicRootId = topicRootId,
+	};
+	const auto notificationId = NotificationId{
+		.contextId = key,
+		.msgId = msgId,
 	};
-	const auto notificationId = NotificationId{ .full = key, .msgId = msgId };
 	auto notification = std::make_unique<NotificationData>(
 		_manager,
 		notificationId);
@@ -951,9 +958,10 @@ void Manager::Private::clearAll() {
 }
 
 void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
-	const auto key = FullPeer{
+	const auto key = ContextId{
 		.sessionId = item->history()->session().uniqueId(),
-		.peerId = item->history()->peer->id
+		.peerId = item->history()->peer->id,
+		.topicRootId = item->topicRootId(),
 	};
 	const auto i = _notifications.find(key);
 	if (i == _notifications.cend()) {
@@ -971,10 +979,10 @@ void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
 	taken->close();
 }
 
-void Manager::Private::clearFromHistory(not_null<History*> history) {
-	const auto key = FullPeer{
-		.sessionId = history->session().uniqueId(),
-		.peerId = history->peer->id
+void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
+	const auto key = ContextId{
+		.sessionId = topic->session().uniqueId(),
+		.peerId = topic->history()->peer->id
 	};
 	const auto i = _notifications.find(key);
 	if (i != _notifications.cend()) {
@@ -987,13 +995,31 @@ void Manager::Private::clearFromHistory(not_null<History*> history) {
 	}
 }
 
+void Manager::Private::clearFromHistory(not_null<History*> history) {
+	const auto sessionId = history->session().uniqueId();
+	const auto peerId = history->peer->id;
+	auto i = _notifications.lower_bound(ContextId{
+		.sessionId = sessionId,
+		.peerId = peerId,
+	});
+	while (i != _notifications.cend()
+		&& i->first.sessionId == sessionId
+		&& i->first.peerId == peerId) {
+		const auto temp = base::take(i->second);
+		i = _notifications.erase(i);
+
+		for (const auto &[msgId, notification] : temp) {
+			notification->close();
+		}
+	}
+}
+
 void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
 	const auto sessionId = session->uniqueId();
-	for (auto i = _notifications.begin(); i != _notifications.end();) {
-		if (i->first.sessionId != sessionId) {
-			++i;
-			continue;
-		}
+	auto i = _notifications.lower_bound(ContextId{
+		.sessionId = sessionId,
+	});
+	while (i != _notifications.cend() && i->first.sessionId == sessionId) {
 		const auto temp = base::take(i->second);
 		i = _notifications.erase(i);
 
@@ -1004,7 +1030,7 @@ void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
 }
 
 void Manager::Private::clearNotification(NotificationId id) {
-	auto i = _notifications.find(id.full);
+	auto i = _notifications.find(id.contextId);
 	if (i != _notifications.cend()) {
 		if (i->second.remove(id.msgId) && i->second.empty()) {
 			_notifications.erase(i);
@@ -1059,6 +1085,10 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
 	_private->clearFromItem(item);
 }
 
+void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
+	_private->clearFromTopic(topic);
+}
+
 void Manager::doClearFromHistory(not_null<History*> history) {
 	_private->clearFromHistory(history);
 }
diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h
index 4dcc870df..2212fa93b 100644
--- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h
+++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h
@@ -29,6 +29,7 @@ protected:
 		DisplayOptions options) override;
 	void doClearAllFast() override;
 	void doClearFromItem(not_null<HistoryItem*> item) override;
+	void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
 	void doClearFromHistory(not_null<History*> history) override;
 	void doClearFromSession(not_null<Main::Session*> session) override;
 	bool doSkipAudio() const override;
diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h
index 50bb84100..718adc1e0 100644
--- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h
+++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h
@@ -21,6 +21,7 @@ public:
 protected:
 	void doShowNativeNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -29,6 +30,7 @@ protected:
 		DisplayOptions options) override;
 	void doClearAllFast() override;
 	void doClearFromItem(not_null<HistoryItem*> item) override;
+	void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
 	void doClearFromHistory(not_null<History*> history) override;
 	void doClearFromSession(not_null<Main::Session*> session) override;
 	QString accountNameSeparator() override;
diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm
index 920f190a3..ebff047e3 100644
--- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm
+++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm
@@ -106,16 +106,23 @@ using Manager = Platform::Notifications::Manager;
 		LOG(("App Error: A notification with unknown peer was received"));
 		return;
 	}
+	NSNumber *topicObject = [notificationUserInfo objectForKey:@"topic"];
+	if (!topicObject) {
+		LOG(("App Error: A notification with unknown topic was received"));
+		return;
+	}
+	const auto notificationTopicRootId = [topicObject longlongValue];
 
 	NSNumber *msgObject = [notificationUserInfo objectForKey:@"msgid"];
 	const auto notificationMsgId = msgObject ? [msgObject longLongValue] : 0LL;
 
 	const auto my = Window::Notifications::Manager::NotificationId{
-		.full = Manager::FullPeer{
+		.contextId = Manager::ContextId{
 			.sessionId = notificationSessionId,
-			.peerId = PeerId(notificationPeerId)
+			.peerId = PeerId(notificationPeerId),
+			.topicRootId = MsgId(notificationTopicRootId),
 		},
-		.msgId = notificationMsgId
+		.msgId = notificationMsgId,
 	};
 	if (notification.activationType == NSUserNotificationActivationTypeReplied) {
 		const auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]);
@@ -180,6 +187,7 @@ public:
 
 	void showNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -188,6 +196,7 @@ public:
 		DisplayOptions options);
 	void clearAll();
 	void clearFromItem(not_null<HistoryItem*> item);
+	void clearFromTopic(not_null<Data::ForumTopic*> topic);
 	void clearFromHistory(not_null<History*> history);
 	void clearFromSession(not_null<Main::Session*> session);
 	void updateDelegate();
@@ -212,8 +221,11 @@ private:
 	struct ClearFromItem {
 		NotificationId id;
 	};
+	struct ClearFromTopic {
+		ContextId contextId;
+	}
 	struct ClearFromHistory {
-		FullPeer fullPeer;
+		ContextId partialContextId;
 	};
 	struct ClearFromSession {
 		uint64 sessionId = 0;
@@ -224,6 +236,7 @@ private:
 	};
 	using ClearTask = std::variant<
 		ClearFromItem,
+		ClearFromTopic,
 		ClearFromHistory,
 		ClearFromSession,
 		ClearAll,
@@ -250,6 +263,7 @@ Manager::Private::Private(Manager *manager)
 
 void Manager::Private::showNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -274,6 +288,8 @@ void Manager::Private::showNotification(
 			@"session",
 			[NSNumber numberWithUnsignedLongLong:peer->id.value],
 			@"peer",
+			[NSNumber numberWithLongLong:topicRootId.bare],
+			@"topic",
 			[NSNumber numberWithLongLong:msgId.bare],
 			@"msgid",
 			[NSNumber numberWithUnsignedLongLong:_managerId],
@@ -312,7 +328,8 @@ void Manager::Private::clearingThreadLoop() {
 	while (!finished) {
 		auto clearAll = false;
 		auto clearFromItems = base::flat_set<NotificationId>();
-		auto clearFromPeers = base::flat_set<FullPeer>();
+		auto clearFromTopics = base::flat_set<ContextId>();
+		auto clearFromHistories = base::flat_set<ContextId>();
 		auto clearFromSessions = base::flat_set<uint64>();
 		{
 			std::unique_lock<std::mutex> lock(_clearingMutex);
@@ -327,8 +344,10 @@ void Manager::Private::clearingThreadLoop() {
 					clearAll = true;
 				}, [&](const ClearFromItem &value) {
 					clearFromItems.emplace(value.id);
+				}, [&](Const ClearFromTopic &value) {
+					clearFromTopics.emplace(value.contextId);
 				}, [&](const ClearFromHistory &value) {
-					clearFromPeers.emplace(value.fullPeer);
+					clearFromHistories.emplace(value.partialContextId);
 				}, [&](const ClearFromSession &value) {
 					clearFromSessions.emplace(value.sessionId);
 				});
@@ -349,15 +368,26 @@ void Manager::Private::clearingThreadLoop() {
 			if (!notificationPeerId) {
 				return true;
 			}
+			NSNumber *topicObject = [notificationUserInfo objectForKey:@"topic"];
+			if (!topicObject) {
+				return true;
+			}
+			const auto notificationTopicRootId = [topicObject longLongValue];
 			NSNumber *msgObject = [notificationUserInfo objectForKey:@"msgid"];
 			const auto msgId = msgObject ? [msgObject longLongValue] : 0LL;
-			const auto full = FullPeer{
+			const auto partialContextId = ContextId{
 				.sessionId = notificationSessionId,
-				.peerId = PeerId(notificationPeerId)
+				.peerId = PeerId(notificationPeerId),
 			};
-			const auto id = NotificationId{ full, MsgId(msgId) };
+			const auto contextId = ContextId{
+				.sessionId = notificationSessionId,
+				.peerId = PeerId(notificationPeerId),
+				.topicRootId = MsgId(notificationTopicRootId),
+			};
+			const auto id = NotificationId{ contextId, MsgId(msgId) };
 			return clearFromSessions.contains(notificationSessionId)
-				|| clearFromPeers.contains(full)
+				|| clearFromHistories.contains(partialContextId)
+				|| clearFromTopics.contains(contextId)
 				|| (msgId && clearFromItems.contains(id));
 		};
 
@@ -394,21 +424,30 @@ void Manager::Private::clearAll() {
 }
 
 void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
-	putClearTask(ClearFromItem { FullPeer{
+	putClearTask(ClearFromItem{ ContextId{
 		.sessionId = item->history()->session().uniqueId(),
-		.peerId = item->history()->peer->id
+		.peerId = item->history()->peer->id,
+		.topicRootId = item->topicRootId(),
 	}, item->id });
 }
 
+void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
+	putClearTask(ClearFromTopic{ ContextId{
+		.sessionId = topic->session().uniqueId(),
+		.peerId = topic->history()->peer->id,
+		.topicRootId = topic->rootId(),
+	} });
+}
+
 void Manager::Private::clearFromHistory(not_null<History*> history) {
-	putClearTask(ClearFromHistory { FullPeer{
+	putClearTask(ClearFromHistory{ ContextId{
 		.sessionId = history->session().uniqueId(),
-		.peerId = history->peer->id
+		.peerId = history->peer->id,
 	} });
 }
 
 void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
-	putClearTask(ClearFromSession { session->uniqueId() });
+	putClearTask(ClearFromSession{ session->uniqueId() });
 }
 
 void Manager::Private::updateDelegate() {
@@ -434,6 +473,7 @@ Manager::~Manager() = default;
 
 void Manager::doShowNativeNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -442,6 +482,7 @@ void Manager::doShowNativeNotification(
 		DisplayOptions options) {
 	_private->showNotification(
 		peer,
+		topicRootId,
 		userpicView,
 		msgId,
 		title,
@@ -458,6 +499,10 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
 	_private->clearFromItem(item);
 }
 
+void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
+	_private->clearFromTopic(topic);
+}
+
 void Manager::doClearFromHistory(not_null<History*> history) {
 	_private->clearFromHistory(history);
 }
diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm
index b514b0013..0e026ee69 100644
--- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm
+++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm
@@ -100,7 +100,7 @@ QImage UnreadBadge(not_null<PeerData*> peer) {
 		: QString::number(count);
 	Dialogs::Ui::UnreadBadgeStyle unreadSt;
 	unreadSt.sizeId = Dialogs::Ui::UnreadBadgeSize::TouchBar;
-	unreadSt.muted = history->mute();
+	unreadSt.muted = history->muted();
 	// Use constant values to draw badge regardless of cConfigScale().
 	unreadSt.size = kUnreadBadgeSize * cRetinaFactor();
 	unreadSt.padding = 4 * cRetinaFactor();
diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
index 97a7d2437..a1f273da1 100644
--- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
+++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "platform/win/windows_toast_activator.h"
 #include "platform/win/windows_dlls.h"
 #include "platform/win/specific_win.h"
+#include "data/data_forum_topic.h"
 #include "history/history.h"
 #include "history/history_item.h"
 #include "core/application.h"
@@ -411,6 +412,7 @@ public:
 
 	bool showNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -419,6 +421,7 @@ public:
 		DisplayOptions options);
 	void clearAll();
 	void clearFromItem(not_null<HistoryItem*> item);
+	void clearFromTopic(not_null<Data::ForumTopic*> topic);
 	void clearFromHistory(not_null<History*> history);
 	void clearFromSession(not_null<Main::Session*> session);
 	void beforeNotificationActivated(NotificationId id);
@@ -434,6 +437,7 @@ public:
 private:
 	bool showNotificationInTryCatch(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -450,7 +454,7 @@ private:
 	ToastNotifier _notifier = nullptr;
 
 	base::flat_map<
-		FullPeer,
+		ContextId,
 		base::flat_map<MsgId, ToastNotification>> _notifications;
 	rpl::lifetime _lifetime;
 
@@ -496,9 +500,10 @@ void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
 		return;
 	}
 
-	auto i = _notifications.find(FullPeer{
+	auto i = _notifications.find(ContextId{
 		.sessionId = item->history()->session().uniqueId(),
-		.peerId = item->history()->peer->id
+		.peerId = item->history()->peer->id,
+		.topicRootId = item->topicRootId(),
 	});
 	if (i == _notifications.cend()) {
 		return;
@@ -515,18 +520,42 @@ void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
 	tryHide(taken);
 }
 
+void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
+	if (!_notifier) {
+		return;
+	}
+
+	const auto i = _notifications.find(ContextId{
+		.sessionId = topic->session().uniqueId(),
+		.peerId = topic->history()->peer->id,
+		.topicRootId = topic->rootId(),
+	});
+	if (i != _notifications.cend()) {
+		const auto temp = base::take(i->second);
+		_notifications.erase(i);
+
+		for (const auto &[msgId, notification] : temp) {
+			tryHide(notification);
+		}
+	}
+}
+
 void Manager::Private::clearFromHistory(not_null<History*> history) {
 	if (!_notifier) {
 		return;
 	}
 
-	auto i = _notifications.find(FullPeer{
-		.sessionId = history->session().uniqueId(),
-		.peerId = history->peer->id
+	const auto sessionId = history->session().uniqueId();
+	const auto peerId = history->peer->id;
+	auto i = _notifications.lower_bound(ContextId{
+		.sessionId = sessionId,
+		.peerId = peerId,
 	});
-	if (i != _notifications.cend()) {
+	while (i != _notifications.cend()
+		&& i->first.sessionId == sessionId
+		&& i->first.peerId == peerId) {
 		const auto temp = base::take(i->second);
-		_notifications.erase(i);
+		i = _notifications.erase(i);
 
 		for (const auto &[msgId, notification] : temp) {
 			tryHide(notification);
@@ -540,13 +569,12 @@ void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
 	}
 
 	const auto sessionId = session->uniqueId();
-	for (auto i = _notifications.begin(); i != _notifications.end();) {
-		if (i->first.sessionId != sessionId) {
-			++i;
-			continue;
-		}
+	auto i = _notifications.lower_bound(ContextId{
+		.sessionId = sessionId,
+	});
+	while (i != _notifications.cend() && i->first.sessionId == sessionId) {
 		const auto temp = base::take(i->second);
-		_notifications.erase(i);
+		i = _notifications.erase(i);
 
 		for (const auto &[msgId, notification] : temp) {
 			tryHide(notification);
@@ -565,7 +593,7 @@ void Manager::Private::afterNotificationActivated(
 }
 
 void Manager::Private::clearNotification(NotificationId id) {
-	auto i = _notifications.find(id.full);
+	auto i = _notifications.find(id.contextId);
 	if (i != _notifications.cend()) {
 		i->second.remove(id.msgId);
 		if (i->second.empty()) {
@@ -589,13 +617,14 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
 	}
 	const auto action = parsed.value("action");
 	const auto id = NotificationId{
-		.full = FullPeer{
+		.contextId = ContextId{
 			.sessionId = parsed.value("session").toULongLong(),
 			.peerId = PeerId(parsed.value("peer").toULongLong()),
+			.topicRootId = MsgId(parsed.value("topic").toLongLong())
 		},
 		.msgId = MsgId(parsed.value("msg").toLongLong()),
 	};
-	if (!id.full.sessionId || !id.full.peerId || !id.msgId) {
+	if (!id.contextId.sessionId || !id.contextId.peerId || !id.msgId) {
 		DEBUG_LOG(("Toast Info: Got activation \"%1\", my %1, skipping."
 			).arg(activation.args
 			).arg(pid));
@@ -610,7 +639,7 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
 			text.text = entry.value;
 		}
 	}
-	const auto i = _notifications.find(id.full);
+	const auto i = _notifications.find(id.contextId);
 	if (i == _notifications.cend() || !i->second.contains(id.msgId)) {
 		return;
 	}
@@ -627,6 +656,7 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
 
 bool Manager::Private::showNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -640,6 +670,7 @@ bool Manager::Private::showNotification(
 	return base::WinRT::Try([&] {
 		return showNotificationInTryCatch(
 			peer,
+			topicRootId,
 			userpicView,
 			msgId,
 			title,
@@ -660,6 +691,7 @@ std::wstring Manager::Private::ensureSendButtonIcon() {
 
 bool Manager::Private::showNotificationInTryCatch(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -669,18 +701,20 @@ bool Manager::Private::showNotificationInTryCatch(
 	const auto withSubtitle = !subtitle.isEmpty();
 	auto toastXml = XmlDocument();
 
-	const auto key = FullPeer{
+	const auto key = ContextId{
 		.sessionId = peer->session().uniqueId(),
 		.peerId = peer->id,
+		.topicRootId = topicRootId,
 	};
 	const auto notificationId = NotificationId{
-		.full = key,
+		.contextId = key,
 		.msgId = msgId
 	};
-	const auto idString = u"pid=%1&session=%2&peer=%3&msg=%4"_q
+	const auto idString = u"pid=%1&session=%2&peer=%3&topic=%4&msg=%5"_q
 		.arg(GetCurrentProcessId())
 		.arg(key.sessionId)
 		.arg(key.peerId.value)
+		.arg(topicRootId.bare)
 		.arg(msgId.bare);
 
 	const auto modern = Platform::IsWindows10OrGreater();
@@ -855,6 +889,7 @@ Manager::~Manager() = default;
 
 void Manager::doShowNativeNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -863,6 +898,7 @@ void Manager::doShowNativeNotification(
 		DisplayOptions options) {
 	_private->showNotification(
 		peer,
+		topicRootId,
 		userpicView,
 		msgId,
 		title,
@@ -879,6 +915,10 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
 	_private->clearFromItem(item);
 }
 
+void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
+	_private->clearFromTopic(topic);
+}
+
 void Manager::doClearFromHistory(not_null<History*> history) {
 	_private->clearFromHistory(history);
 }
diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.h b/Telegram/SourceFiles/platform/win/notifications_manager_win.h
index 5ae34cc0f..0eaaa2e83 100644
--- a/Telegram/SourceFiles/platform/win/notifications_manager_win.h
+++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.h
@@ -29,6 +29,7 @@ public:
 protected:
 	void doShowNativeNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -37,6 +38,7 @@ protected:
 		DisplayOptions options) override;
 	void doClearAllFast() override;
 	void doClearFromItem(not_null<HistoryItem*> item) override;
+	void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
 	void doClearFromHistory(not_null<History*> history) override;
 	void doClearFromSession(not_null<Main::Session*> session) override;
 	void onBeforeNotificationActivated(NotificationId id) override;
diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp
index e6ced7591..60ea325d4 100644
--- a/Telegram/SourceFiles/storage/storage_shared_media.cpp
+++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp
@@ -71,7 +71,7 @@ void SharedMedia::add(SharedMediaAddSlice &&query) {
 }
 
 void SharedMedia::remove(SharedMediaRemoveOne &&query) {
-	auto peerIt = _lists.find({ query.peerId, MsgId(0) });
+	auto peerIt = _lists.lower_bound({ query.peerId, MsgId(0) });
 	while (peerIt != end(_lists) && peerIt->first.peerId == query.peerId) {
 		for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
 			auto type = static_cast<SharedMediaType>(index);
@@ -85,7 +85,7 @@ void SharedMedia::remove(SharedMediaRemoveOne &&query) {
 }
 
 void SharedMedia::remove(SharedMediaRemoveAll &&query) {
-	auto peerIt = _lists.find({ query.peerId, MsgId(0) });
+	auto peerIt = _lists.lower_bound({ query.peerId, MsgId(0) });
 	while (peerIt != end(_lists) && peerIt->first.peerId == query.peerId) {
 		for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
 			auto type = static_cast<SharedMediaType>(index);
@@ -99,7 +99,7 @@ void SharedMedia::remove(SharedMediaRemoveAll &&query) {
 }
 
 void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) {
-	auto peerIt = _lists.find({ query.peerId, MsgId(0) });
+	auto peerIt = _lists.lower_bound({ query.peerId, MsgId(0) });
 	while (peerIt != end(_lists) && peerIt->first.peerId == query.peerId) {
 		for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
 			peerIt->second[index].invalidateBottom();
diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp
index 84aaaf22d..a6a84c643 100644
--- a/Telegram/SourceFiles/window/notifications_manager.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager.cpp
@@ -14,12 +14,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mtproto/mtproto_config.h"
 #include "history/history.h"
 #include "history/history_item_components.h"
+#include "history/view/history_view_replies_section.h"
 #include "lang/lang_keys.h"
 #include "data/notify/data_notify_settings.h"
 #include "data/stickers/data_custom_emoji.h"
 #include "data/data_document_media.h"
 #include "data/data_session.h"
 #include "data/data_channel.h"
+#include "data/data_forum_topic.h"
 #include "data/data_user.h"
 #include "data/data_document.h"
 #include "data/data_poll.h"
@@ -327,6 +329,22 @@ void System::clearAll() {
 	_settingWaiters.clear();
 }
 
+void System::clearFromTopic(not_null<Data::ForumTopic*> topic) {
+	if (_manager) {
+		_manager->clearFromTopic(topic);
+	}
+
+	// #TODO forum notifications
+	//topic->clearNotifications();
+	//_whenMaps.remove(topic);
+	//_whenAlerts.remove(topic);
+	//_waiters.remove(topic);
+	//_settingWaiters.remove(topic);
+
+	_waitTimer.cancel();
+	showNext();
+}
+
 void System::clearFromHistory(not_null<History*> history) {
 	if (_manager) {
 		_manager->clearFromHistory(history);
@@ -381,6 +399,15 @@ void System::clearIncomingFromHistory(not_null<History*> history) {
 	_whenAlerts.remove(history);
 }
 
+void System::clearIncomingFromTopic(not_null<Data::ForumTopic*> topic) {
+	if (_manager) {
+		_manager->clearFromTopic(topic);
+	}
+	// #TODO forum notifications
+	//topic->clearIncomingNotifications();
+	//_whenAlerts.remove(topic);
+}
+
 void System::clearFromItem(not_null<HistoryItem*> item) {
 	if (_manager) {
 		_manager->clearFromItem(item);
@@ -922,14 +949,16 @@ void Manager::notificationActivated(
 		NotificationId id,
 		const TextWithTags &reply) {
 	onBeforeNotificationActivated(id);
-	if (const auto session = system()->findSession(id.full.sessionId)) {
+	if (const auto session = system()->findSession(id.contextId.sessionId)) {
 		if (session->windows().empty()) {
 			Core::App().domain().activate(&session->account());
 		}
 		if (!session->windows().empty()) {
 			const auto window = session->windows().front();
-			const auto history = session->data().history(id.full.peerId);
+			const auto history = session->data().history(
+				id.contextId.peerId);
 			if (!reply.text.isEmpty()) {
+				// #TODO forum notifications
 				const auto replyToId = (id.msgId > 0
 					&& !history->peer->isUser())
 					? id.msgId
@@ -961,45 +990,57 @@ void Manager::notificationActivated(
 void Manager::openNotificationMessage(
 		not_null<History*> history,
 		MsgId messageId) {
-	const auto openExactlyMessage = [&] {
-		const auto peer = history->peer;
-		if (peer->isBroadcast()) {
-			return false;
+	const auto item = history->owner().message(history->peer, messageId);
+	const auto openExactlyMessage = !history->peer->isBroadcast()
+		&& item
+		&& item->isRegular()
+		&& (item->out() || (item->mentionsMe() && !history->peer->isUser()));
+	const auto topic = item ? history->peer->forumTopicFor(item) : nullptr;
+	const auto separate = Core::App().separateWindowForPeer(history->peer);
+	const auto window = separate
+		? separate->sessionController()
+		: history->session().tryResolveWindow();
+	const auto itemId = openExactlyMessage ? messageId : ShowAtUnreadMsgId;
+	if (window) {
+		if (topic) {
+			window->showSection(
+				std::make_shared<HistoryView::RepliesMemento>(
+					history,
+					topic->rootId(),
+					itemId),
+				SectionShow::Way::Forward);
+		} else {
+			window->showPeerHistory(
+				history->peer->id,
+				SectionShow::Way::Forward,
+				itemId);
 		}
-		const auto item = history->owner().message(history->peer, messageId);
-		if (!item
-			|| !item->isRegular()
-			|| (!item->out() && (!item->mentionsMe() || peer->isUser()))) {
-			return false;
-		}
-		return true;
-	}();
-	if (openExactlyMessage) {
-		Ui::showPeerHistory(history, messageId);
-	} else {
-		Ui::showPeerHistory(history, ShowAtUnreadMsgId);
 	}
-	system()->clearFromHistory(history);
+	if (topic) {
+		system()->clearFromTopic(topic);
+	} else {
+		system()->clearFromHistory(history);
+	}
 }
 
 void Manager::notificationReplied(
 		NotificationId id,
 		const TextWithTags &reply) {
-	if (!id.full.sessionId || !id.full.peerId) {
+	if (!id.contextId.sessionId || !id.contextId.peerId) {
 		return;
 	}
 
-	const auto session = system()->findSession(id.full.sessionId);
+	const auto session = system()->findSession(id.contextId.sessionId);
 	if (!session) {
 		return;
 	}
-	const auto history = session->data().history(id.full.peerId);
+	const auto history = session->data().history(id.contextId.peerId);
 
 	auto message = Api::MessageToSend(Api::SendAction(history));
 	message.textWithTags = reply;
 	message.action.replyTo = (id.msgId > 0 && !history->peer->isUser())
 		? id.msgId
-		: 0;
+		: id.contextId.topicRootId;
 	message.action.clearDraft = false;
 	history->session().api().sendMessage(std::move(message));
 
@@ -1053,6 +1094,7 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
 	auto userpicView = item->history()->peer->createUserpicView();
 	doShowNativeNotification(
 		item->history()->peer,
+		item->topicRootId(),
 		userpicView,
 		item->id,
 		scheduled ? WrapFromScheduled(fullTitle) : fullTitle,
diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h
index ae3259474..ec71281f0 100644
--- a/Telegram/SourceFiles/window/notifications_manager.h
+++ b/Telegram/SourceFiles/window/notifications_manager.h
@@ -18,6 +18,7 @@ enum class ItemNotificationType;
 namespace Data {
 class Session;
 class CloudImageView;
+class ForumTopic;
 } // namespace Data
 
 namespace Main {
@@ -90,7 +91,9 @@ public:
 
 	void checkDelayed();
 	void schedule(ItemNotification notification);
+	void clearFromTopic(not_null<Data::ForumTopic*> topic);
 	void clearFromHistory(not_null<History*> history);
+	void clearIncomingFromTopic(not_null<Data::ForumTopic*> topic);
 	void clearIncomingFromHistory(not_null<History*> history);
 	void clearFromSession(not_null<Main::Session*> session);
 	void clearFromItem(not_null<HistoryItem*> item);
@@ -202,24 +205,22 @@ private:
 
 class Manager {
 public:
-	struct FullPeer {
+	struct ContextId {
 		uint64 sessionId = 0;
 		PeerId peerId = 0;
+		MsgId topicRootId = 0;
 
-		friend inline bool operator<(const FullPeer &a, const FullPeer &b) {
-			return std::tie(a.sessionId, a.peerId)
-				< std::tie(b.sessionId, b.peerId);
-		}
+		friend inline auto operator<=>(
+			const ContextId&,
+			const ContextId&) = default;
 	};
 	struct NotificationId {
-		FullPeer full;
+		ContextId contextId;
 		MsgId msgId = 0;
 
-		friend inline bool operator<(
-				const NotificationId &a,
-				const NotificationId &b) {
-			return std::tie(a.full, a.msgId) < std::tie(b.full, b.msgId);
-		}
+		friend inline auto operator<=>(
+			const NotificationId&,
+			const NotificationId&) = default;
 	};
 	struct NotificationFields {
 		not_null<HistoryItem*> item;
@@ -246,6 +247,9 @@ public:
 	void clearFromItem(not_null<HistoryItem*> item) {
 		doClearFromItem(item);
 	}
+	void clearFromTopic(not_null<Data::ForumTopic*> topic) {
+		doClearFromTopic(topic);
+	}
 	void clearFromHistory(not_null<History*> history) {
 		doClearFromHistory(history);
 	}
@@ -294,7 +298,7 @@ public:
 	virtual ~Manager() = default;
 
 protected:
-	not_null<System*> system() const {
+	[[nodiscard]] not_null<System*> system() const {
 		return _system;
 	}
 
@@ -303,6 +307,7 @@ protected:
 	virtual void doClearAll() = 0;
 	virtual void doClearAllFast() = 0;
 	virtual void doClearFromItem(not_null<HistoryItem*> item) = 0;
+	virtual void doClearFromTopic(not_null<Data::ForumTopic*> topic) = 0;
 	virtual void doClearFromHistory(not_null<History*> history) = 0;
 	virtual void doClearFromSession(not_null<Main::Session*> session) = 0;
 	virtual bool doSkipAudio() const = 0;
@@ -349,6 +354,7 @@ protected:
 
 	virtual void doShowNativeNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -369,6 +375,7 @@ public:
 protected:
 	void doShowNativeNotification(
 		not_null<PeerData*> peer,
+		MsgId topicRootId,
 		std::shared_ptr<Data::CloudImageView> &userpicView,
 		MsgId msgId,
 		const QString &title,
@@ -380,6 +387,8 @@ protected:
 	}
 	void doClearFromItem(not_null<HistoryItem*> item) override {
 	}
+	void doClearFromTopic(not_null<Data::ForumTopic*> topic) override {
+	}
 	void doClearFromHistory(not_null<History*> history) override {
 	}
 	void doClearFromSession(not_null<Main::Session*> session) override {
diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp
index 4e04d4ec0..0d526f8f8 100644
--- a/Telegram/SourceFiles/window/notifications_manager_default.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/painter.h"
 #include "ui/ui_utility.h"
 #include "data/data_session.h"
+#include "data/data_forum_topic.h"
 #include "data/stickers/data_custom_emoji.h"
 #include "dialogs/ui/dialogs_layout.h"
 #include "window/window_controller.h"
@@ -82,6 +83,7 @@ Manager::Manager(System *system)
 
 Manager::QueuedNotification::QueuedNotification(NotificationFields &&fields)
 : history(fields.item->history())
+, topicRootId(fields.item->topicRootId())
 , peer(history->peer)
 , reaction(fields.reactionId)
 , author(!fields.reactionFrom
@@ -236,6 +238,7 @@ void Manager::showNextFromQueue() {
 		_notifications.push_back(std::make_unique<Notification>(
 			this,
 			queued.history,
+			queued.topicRootId,
 			queued.peer,
 			queued.author,
 			queued.item,
@@ -372,6 +375,24 @@ void Manager::doClearAllFast() {
 	base::take(_hideAll);
 }
 
+void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
+	const auto history = topic->history();
+	const auto topicRootId = topic->rootId();
+	for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
+		if (i->history == history && i->topicRootId == topicRootId) {
+			i = _queuedNotifications.erase(i);
+		} else {
+			++i;
+		}
+	}
+	for (const auto &notification : _notifications) {
+		if (notification->unlinkHistory(history, topicRootId)) {
+			_positionsOutdated = true;
+		}
+	}
+	showNextFromQueue();
+}
+
 void Manager::doClearFromHistory(not_null<History*> history) {
 	for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
 		if (i->history == history) {
@@ -601,6 +622,7 @@ void Background::paintEvent(QPaintEvent *e) {
 Notification::Notification(
 	not_null<Manager*> manager,
 	not_null<History*> history,
+	MsgId topicRootId,
 	not_null<PeerData*> peer,
 	const QString &author,
 	HistoryItem *item,
@@ -614,6 +636,7 @@ Notification::Notification(
 , _peer(peer)
 , _started(crl::now())
 , _history(history)
+, _topicRootId(topicRootId)
 , _userpicView(_peer->createUserpicView())
 , _author(author)
 , _reaction(reaction)
@@ -1061,9 +1084,10 @@ Notifications::Manager::NotificationId Notification::myId() const {
 	if (!_history) {
 		return {};
 	}
-	return { .full = {
+	return { .contextId = {
 		.sessionId = _history->session().uniqueId(),
-		.peerId = _history->peer->id
+		.peerId = _history->peer->id,
+		.topicRootId = _topicRootId,
 	}, .msgId = _item ? _item->id : ShowAtUnreadMsgId };
 }
 
@@ -1071,8 +1095,10 @@ void Notification::changeHeight(int newHeight) {
 	manager()->changeNotificationHeight(this, newHeight);
 }
 
-bool Notification::unlinkHistory(History *history) {
-	const auto unlink = _history && (history == _history || !history);
+bool Notification::unlinkHistory(History *history, MsgId topicRootId) {
+	const auto unlink = _history
+		&& (history == _history || !history)
+		&& (topicRootId == _topicRootId || !topicRootId);
 	if (unlink) {
 		hideFast();
 		_history = nullptr;
diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h
index 28a9a9bfe..6637695a8 100644
--- a/Telegram/SourceFiles/window/notifications_manager_default.h
+++ b/Telegram/SourceFiles/window/notifications_manager_default.h
@@ -72,6 +72,7 @@ private:
 	void doShowNotification(NotificationFields &&fields) override;
 	void doClearAll() override;
 	void doClearAllFast() override;
+	void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
 	void doClearFromHistory(not_null<History*> history) override;
 	void doClearFromSession(not_null<Main::Session*> session) override;
 	void doClearFromItem(not_null<HistoryItem*> item) override;
@@ -112,6 +113,7 @@ private:
 		QueuedNotification(NotificationFields &&fields);
 
 		not_null<History*> history;
+		MsgId topicRootId = 0;
 		not_null<PeerData*> peer;
 		Data::ReactionId reaction;
 		QString author;
@@ -205,6 +207,7 @@ public:
 	Notification(
 		not_null<Manager*> manager,
 		not_null<History*> history,
+		MsgId topicRootId,
 		not_null<PeerData*> peer,
 		const QString &author,
 		HistoryItem *item,
@@ -233,7 +236,7 @@ public:
 
 	// Called only by Manager.
 	bool unlinkItem(HistoryItem *del);
-	bool unlinkHistory(History *history = nullptr);
+	bool unlinkHistory(History *history = nullptr, MsgId topicRootId = 0);
 	bool unlinkSession(not_null<Main::Session*> session);
 	bool checkLastInput(
 		bool hasReplyingNotifications,
@@ -282,6 +285,7 @@ private:
 	crl::time _started;
 
 	History *_history = nullptr;
+	MsgId _topicRootId = 0;
 	std::shared_ptr<Data::CloudImageView> _userpicView;
 	QString _author;
 	Data::ReactionId _reaction;
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 0ab888f34..70637fd6d 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -141,7 +141,7 @@ void PeerMenuAddMuteSubmenuAction(
 	if (isMuted) {
 		const auto text = tr::lng_context_unmute(tr::now)
 			+ '\t'
-			+ Ui::FormatMuteForTiny(peer->notifyMuteUntil().value_or(0)
+			+ Ui::FormatMuteForTiny(peer->notify().muteUntil().value_or(0)
 				- base::unixtime::now());
 		addAction(text, [=] {
 			peer->owner().notifySettings().update(peer, { .unmute = true });
diff --git a/Telegram/lib_base b/Telegram/lib_base
index 8d9d8e86b..2a94813b6 160000
--- a/Telegram/lib_base
+++ b/Telegram/lib_base
@@ -1 +1 @@
-Subproject commit 8d9d8e86b9abea58ed5a218e60d9f79b47085c63
+Subproject commit 2a94813b6aa1f8645fd9de88a78981356259fab7