diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 53ba504b0..a0b39cbc8 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -154,7 +154,6 @@ constexpr auto kSaveDraftTimeout = 1000;
 constexpr auto kSaveDraftAnywayTimeout = 5000;
 constexpr auto kSaveCloudDraftIdleTimeout = 14000;
 constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
-constexpr auto kItemRevealDuration = crl::time(200);
 constexpr auto kCommonModifiers = 0
 	| Qt::ShiftModifier
 	| Qt::MetaModifier
@@ -4688,6 +4687,10 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
 		_itemRevealAnimations.erase(i);
 		revealItemsCallback();
 	}
+	const auto j = _itemRevealPending.find(item);
+	if (j != _itemRevealPending.end()) {
+		_itemRevealPending.erase(j);
+	}
 }
 
 void HistoryWidget::itemEdited(not_null<HistoryItem*> item) {
@@ -4904,35 +4907,35 @@ void HistoryWidget::revealItemsCallback() {
 	}
 }
 
-void HistoryWidget::updateListSize() {
-	_list->recountHistoryGeometry();
-	auto washidden = _scroll->isHidden();
-	if (washidden) {
-		_scroll->show();
-	}
+void HistoryWidget::startItemRevealAnimations() {
 	for (const auto item : base::take(_itemRevealPending)) {
 		if (const auto view = item->mainView()) {
 			if (const auto top = _list->itemTop(view); top >= 0) {
 				if (const auto height = view->height()) {
 					if (!_itemRevealAnimations.contains(item)) {
 						auto &animation = _itemRevealAnimations[item];
-						if (!animation.animation.animating()) {
-							animation.startHeight
-								= animation.currentHeight
-								= height;
-							_itemsRevealHeight += height;
-							animation.animation.start(
-								[=] { revealItemsCallback(); },
-								0.,
-								1.,
-								kItemRevealDuration,
-								anim::easeOutCirc);
-						}
+						animation.startHeight = height;
+						_itemsRevealHeight += height;
+						animation.animation.start(
+							[=] { revealItemsCallback(); },
+							0.,
+							1.,
+							HistoryView::kItemRevealDuration,
+							anim::easeOutCirc);
 					}
 				}
 			}
 		}
 	}
+}
+
+void HistoryWidget::updateListSize() {
+	_list->recountHistoryGeometry();
+	auto washidden = _scroll->isHidden();
+	if (washidden) {
+		_scroll->show();
+	}
+	startItemRevealAnimations();
 	_list->setItemsRevealHeight(_itemsRevealHeight);
 	_list->updateSize();
 	if (washidden) {
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index b298bd204..88469d6c7 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -323,9 +323,8 @@ private:
 		bool active = false;
 	};
 	struct ItemRevealAnimation {
-		int startHeight = 0;
-		int currentHeight = 0;
 		Ui::Animations::Simple animation;
+		int startHeight = 0;
 	};
 	enum class TextUpdateEvent {
 		SaveDraft = (1 << 0),
@@ -536,6 +535,7 @@ private:
 
 	void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
 	void updateListSize();
+	void startItemRevealAnimations();
 	void revealItemsCallback();
 
 	// Does any of the shown histories has this flag set.
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index e1be2af7c..294b66f70 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -340,14 +340,24 @@ void ListWidget::refreshViewer() {
 		_idsLimit,
 		_idsLimit
 	) | rpl::start_with_next([=](Data::MessagesSlice &&slice) {
-		_slice = std::move(slice);
-		refreshRows();
+		std::swap(_slice, slice);
+		refreshRows(slice);
 	}, _viewerLifetime);
 }
 
-void ListWidget::refreshRows() {
+void ListWidget::refreshRows(const Data::MessagesSlice &old) {
 	saveScrollState();
 
+	const auto addedToEndFrom = (old.skippedAfter == 0
+		&& (_slice.skippedAfter == 0)
+		&& !old.ids.empty())
+		? ranges::find(_slice.ids, old.ids.back())
+		: end(_slice.ids);
+	const auto addedToEndCount = std::max(
+		int(end(_slice.ids) - addedToEndFrom),
+		1
+	) - 1;
+
 	_items.clear();
 	_items.reserve(_slice.ids.size());
 	auto nearestIndex = -1;
@@ -359,12 +369,17 @@ void ListWidget::refreshRows() {
 			_items.push_back(enforceViewForItem(item));
 		}
 	}
+	for (auto e = end(_items), i = e - addedToEndCount; i != e; ++i) {
+		_itemRevealPending.emplace(*i);
+	}
 	updateAroundPositionFromNearest(nearestIndex);
 
 	updateItemsGeometry();
 	checkUnreadBarCreation();
 	restoreScrollState();
-	mouseActionUpdate(QCursor::pos());
+	if (!_itemsRevealHeight) {
+		mouseActionUpdate(QCursor::pos());
+	}
 	_delegate->listContentRefreshed();
 }
 
@@ -1124,7 +1139,9 @@ bool ListWidget::loadedAtBottom() const {
 }
 
 bool ListWidget::isEmpty() const {
-	return loadedAtTop() && loadedAtBottom();
+	return loadedAtTop()
+		&& loadedAtBottom()
+		&& (_itemsHeight + _itemsRevealHeight == 0);
 }
 
 int ListWidget::itemMinimalHeight() const {
@@ -1405,6 +1422,60 @@ void ListWidget::resizeToWidth(int newWidth, int minHeight) {
 	restoreScrollPosition();
 }
 
+void ListWidget::startItemRevealAnimations() {
+	for (const auto view : base::take(_itemRevealPending)) {
+		if (const auto height = view->height()) {
+			if (!_itemRevealAnimations.contains(view)) {
+				auto &animation = _itemRevealAnimations[view];
+				animation.startHeight = height;
+				_itemsRevealHeight += height;
+				animation.animation.start(
+					[=] { revealItemsCallback(); },
+					0.,
+					1.,
+					kItemRevealDuration,
+					anim::easeOutCirc);
+			}
+		}
+	}
+}
+
+void ListWidget::revealItemsCallback() {
+	auto revealHeight = 0;
+	for (auto i = begin(_itemRevealAnimations)
+		; i != end(_itemRevealAnimations);) {
+		if (!i->second.animation.animating()) {
+			i = _itemRevealAnimations.erase(i);
+		} else {
+			revealHeight += anim::interpolate(
+				i->second.startHeight,
+				0,
+				i->second.animation.value(1.));
+			++i;
+		}
+	}
+	if (_itemsRevealHeight != revealHeight) {
+		saveScrollState();
+		const auto old = std::exchange(_itemsRevealHeight, revealHeight);
+		const auto delta = old - _itemsRevealHeight;
+		_itemsHeight += delta;
+		_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
+			? (_minHeight - _itemsHeight - st::historyPaddingBottom)
+			: 0;
+		const auto wasHeight = height();
+		const auto nowHeight = std::min(_minHeight, wasHeight + delta);
+		if (wasHeight != nowHeight) {
+			resize(width(), nowHeight);
+		}
+		update();
+		restoreScrollState();
+
+		if (!_itemsRevealHeight) {
+			mouseActionUpdate(QCursor::pos());
+		}
+	}
+}
+
 int ListWidget::resizeGetHeight(int newWidth) {
 	update();
 
@@ -1423,8 +1494,9 @@ int ListWidget::resizeGetHeight(int newWidth) {
 			itemMinimalHeight(),
 			newHeight / int(_items.size()));
 	}
+	startItemRevealAnimations();
 	_itemsWidth = newWidth;
-	_itemsHeight = newHeight;
+	_itemsHeight = newHeight - _itemsRevealHeight;
 	_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
 		? (_minHeight - _itemsHeight - st::historyPaddingBottom)
 		: 0;
@@ -2726,6 +2798,23 @@ void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
 			_bar.element->createUnreadBar(_barText.value());
 		}
 	}
+	const auto i = _itemRevealPending.find(was);
+	if (i != end(_itemRevealPending)) {
+		_itemRevealPending.erase(i);
+		if (now) {
+			_itemRevealPending.emplace(now);
+		}
+	}
+	const auto j = _itemRevealAnimations.find(was);
+	if (j != end(_itemRevealAnimations)) {
+		auto data = std::move(j->second);
+		_itemRevealAnimations.erase(j);
+		if (now) {
+			_itemRevealAnimations.emplace(now, std::move(data));
+		} else {
+			revealItemsCallback();
+		}
+	}
 }
 
 void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h
index 0c26dd368..a46041f58 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h
@@ -143,6 +143,8 @@ private:
 
 };
 
+inline constexpr auto kItemRevealDuration = crl::time(150);
+
 class ListWidget final
 	: public Ui::RpWidget
 	, public ElementDelegate
@@ -296,7 +298,10 @@ private:
 		inline bool operator!=(const MouseState &other) const {
 			return !(*this == other);
 		}
-
+	};
+	struct ItemRevealAnimation {
+		Ui::Animations::Simple animation;
+		int startHeight = 0;
 	};
 	enum class Direction {
 		Up,
@@ -329,7 +334,7 @@ private:
 
 	void refreshViewer();
 	void updateAroundPositionFromNearest(int nearestIndex);
-	void refreshRows();
+	void refreshRows(const Data::MessagesSlice &old);
 	ScrollTopState countScrollState() const;
 	void saveScrollState();
 	void restoreScrollState();
@@ -458,6 +463,8 @@ private:
 	void checkUnreadBarCreation();
 	void applyUpdatedScrollState();
 	void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo);
+	void startItemRevealAnimations();
+	void revealItemsCallback();
 
 	void updateHighlightedMessage();
 	void clearHighlightedMessage();
@@ -505,6 +512,11 @@ private:
 	int _itemsWidth = 0;
 	int _itemsHeight = 0;
 	int _itemAverageHeight = 0;
+	base::flat_set<not_null<Element*>> _itemRevealPending;
+	base::flat_map<
+		not_null<Element*>,
+		ItemRevealAnimation> _itemRevealAnimations;
+	int _itemsRevealHeight = 0;
 	base::flat_set<FullMsgId> _animatedStickersPlayed;
 	base::flat_map<
 		not_null<PeerData*>,